diff --git a/.gn b/.gn
index 50a8a2f..375c60c 100644
--- a/.gn
+++ b/.gn
@@ -113,7 +113,6 @@
   "//extensions/renderer:unit_tests",
   "//extensions/shell:browser_tests",
   "//extensions/shell:unit_tests",
-  "//extensions/utility:unit_tests",
   "//gin/*",
   "//google_apis/*",
   "//google_update/*",
diff --git a/DEPS b/DEPS
index dd0cdcf..25ab214 100644
--- a/DEPS
+++ b/DEPS
@@ -79,7 +79,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'b537879c7214efd87840c9a7267ab3b3facda873',
+  'skia_revision': '8c4cbf4cfb6d9236dfd69273bff7e8384744c29a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -103,7 +103,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '3baef5c6daf58cec2df193714b5727802d0bd42e',
+  'pdfium_revision': '69da36c5f841e8c6e5ded6c704d9ef58c57d532a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -135,7 +135,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'b90b97cf8dec1060b14197b68a7c862bab7e4ed5',
+  'catapult_revision': '21ff400bb4884e9a601168e7fa08559a76e06a75',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index fafc895..d9435634 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2180,15 +2180,21 @@
   results = []
   # First, check for new / deleted .pydeps.
   for f in input_api.AffectedFiles(include_deletes=True):
-    if f.LocalPath().endswith('.pydeps'):
-      if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
-        results.append(output_api.PresubmitError(
-            'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
-            'remove %s' % f.LocalPath()))
-      elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES:
-        results.append(output_api.PresubmitError(
-            'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
-            'include %s' % f.LocalPath()))
+    # Check whether we are running the presubmit check for a file in src.
+    # f.LocalPath is relative to repo (src, or internal repo).
+    # os_path.exists is relative to src repo.
+    # Therefore if os_path.exists is true, it means f.LocalPath is relative
+    # to src and we can conclude that the pydeps is in src.
+    if input_api.os_path.exists(f.LocalPath()):
+      if f.LocalPath().endswith('.pydeps'):
+        if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
+          results.append(output_api.PresubmitError(
+              'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
+              'remove %s' % f.LocalPath()))
+        elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES:
+          results.append(output_api.PresubmitError(
+              'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
+              'include %s' % f.LocalPath()))
 
   if results:
     return results
diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py
index bcca909f..b83d58ad 100755
--- a/PRESUBMIT_test.py
+++ b/PRESUBMIT_test.py
@@ -656,10 +656,21 @@
       MockAffectedFile('new.pydeps', [], action='A'),
     ]
 
+    self.mock_input_api.CreateMockFileInPath(
+        [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
+            include_deletes=True)])
     results = self._RunCheck()
     self.assertEqual(1, len(results))
     self.assertTrue('PYDEPS_FILES' in str(results[0]))
 
+  def testPydepNotInSrc(self):
+    self.mock_input_api.files = [
+      MockAffectedFile('new.pydeps', [], action='A'),
+    ]
+    self.mock_input_api.CreateMockFileInPath([])
+    results = self._RunCheck()
+    self.assertEqual(0, len(results))
+
   def testRemovedPydep(self):
     # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android.
     if self.mock_input_api.platform != 'linux2':
@@ -668,7 +679,9 @@
     self.mock_input_api.files = [
       MockAffectedFile(PRESUBMIT._ALL_PYDEPS_FILES[0], [], action='D'),
     ]
-
+    self.mock_input_api.CreateMockFileInPath(
+        [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
+            include_deletes=True)])
     results = self._RunCheck()
     self.assertEqual(1, len(results))
     self.assertTrue('PYDEPS_FILES' in str(results[0]))
diff --git a/PRESUBMIT_test_mocks.py b/PRESUBMIT_test_mocks.py
index 732da0e..846f8413 100644
--- a/PRESUBMIT_test_mocks.py
+++ b/PRESUBMIT_test_mocks.py
@@ -51,6 +51,7 @@
 
     return errors
 
+
 class MockInputApi(object):
   """Mock class for the InputApi class.
 
@@ -75,6 +76,9 @@
     self.change = MockChange([])
     self.presubmit_local_path = os.path.dirname(__file__)
 
+  def CreateMockFileInPath(self, f_list):
+    self.os_path.exists = lambda x: x in f_list
+
   def AffectedFiles(self, file_filter=None, include_deletes=False):
     for file in self.files:
       if file_filter and not file_filter(file):
diff --git a/WATCHLISTS b/WATCHLISTS
index 23c7b43a..e6f16160 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1880,7 +1880,8 @@
                       'shimazu+worker@chromium.org'],
     'blink_wtf': ['blink-reviews-wtf@chromium.org',
                   'mikhail.pozdnyakov@intel.com'],
-    'blink_xml': ['dominicc+watchlist@chromium.org', 'joelhockey@chromium.org'],
+    'blink_xml': ['dominicc+watchlist@chromium.org',
+                  'joelhockey+watch@chromium.org'],
     'bookmarks': ['tfarina@chromium.org'],
     'bottombar': ['donnd+watch@chromium.org',
                   'mdjones+watch@chromium.org'],
@@ -2018,7 +2019,8 @@
                 'nhiroki@chromium.org',
                 'tzik@chromium.org'],
     'filebrowse': ['rginda+watch@chromium.org'],
-    'filesapp': ['fukino+watch@chromium.org',
+    'filesapp': ['filesapp-reviews@chromium.org',
+                 'fukino+watch@chromium.org',
                  'oka+watch@chromium.org',
                  'yamaguchi+watch@chromium.org'],
     'fsp': ['mtomasz+watch@chromium.org'],
diff --git a/android_webview/browser/aw_browser_terminator.cc b/android_webview/browser/aw_browser_terminator.cc
index 8a0d1c3..eb284e68 100644
--- a/android_webview/browser/aw_browser_terminator.cc
+++ b/android_webview/browser/aw_browser_terminator.cc
@@ -17,6 +17,7 @@
 #include "base/task_scheduler/post_task.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/child_process_data.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_process_host.h"
@@ -106,7 +107,7 @@
 void AwBrowserTerminator::OnChildStart(
     int process_host_id,
     content::PosixFileDescriptorInfo* mappings) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(content::CurrentlyOnProcessLauncherTaskRunner());
 
   base::AutoLock auto_lock(process_host_id_to_pipe_lock_);
   DCHECK(!ContainsKey(process_host_id_to_pipe_, process_host_id));
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 50a014d..cdd3378 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -298,6 +298,8 @@
     "login/ui/login_button.h",
     "login/ui/login_data_dispatcher.cc",
     "login/ui/login_data_dispatcher.h",
+    "login/ui/login_detachable_base_model.cc",
+    "login/ui/login_detachable_base_model.h",
     "login/ui/login_password_view.cc",
     "login/ui/login_password_view.h",
     "login/ui/login_pin_view.cc",
@@ -1107,6 +1109,7 @@
   deps = [
     "//ash/autoclick/common:autoclick",
     "//ash/components/autoclick/public/mojom",
+    "//ash/components/cursor",
     "//ash/components/fast_ink",
     "//ash/components/quick_launch/public/mojom",
     "//ash/touch_hud",
@@ -1454,6 +1457,8 @@
     "login/login_screen_controller_unittest.cc",
     "login/mock_login_screen_client.cc",
     "login/mock_login_screen_client.h",
+    "login/ui/fake_login_detachable_base_model.cc",
+    "login/ui/fake_login_detachable_base_model.h",
     "login/ui/lock_contents_view_unittest.cc",
     "login/ui/lock_screen_sanity_unittest.cc",
     "login/ui/lock_window_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 4e0b8ea5..3c2406f 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -1026,6 +1026,10 @@
     AshTestBase::SetUp();
     Shell::Get()->lock_state_controller()->set_animator_for_test(
         new TestSessionStateAnimator);
+    Shell::Get()->power_button_controller()->OnGetSwitchStates(
+        chromeos::PowerManagerClient::SwitchStates{
+            chromeos::PowerManagerClient::LidState::OPEN,
+            chromeos::PowerManagerClient::TabletMode::ON});
   }
 
  private:
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 41b39e9..375c514 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -535,6 +535,11 @@
         No recent items
       </message>
 
+      <!-- Window Selector -->
+      <message name="IDS_ASH_WINDOW_SELECTOR_INPUT_FILTER_ACCESSIBLE_NAME" desc="The accessible name for the window selector filter search input box.">
+        Type the name of an app or document
+      </message>
+
       <!-- Status tray charging strings. -->
       <message name="IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE" desc="The title of a notification indicating that a low-current USB charger has been connected.">
         Low-power charger connected
diff --git a/ash/components/cursor/BUILD.gn b/ash/components/cursor/BUILD.gn
new file mode 100644
index 0000000..8184365
--- /dev/null
+++ b/ash/components/cursor/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("cursor") {
+  sources = [
+    "cursor_view.cc",
+    "cursor_view.h",
+  ]
+
+  deps = [
+    "//ash/components/fast_ink",
+    "//base",
+    "//cc",
+    "//components/viz/common",
+    "//skia",
+    "//ui/aura",
+    "//ui/events",
+    "//ui/events/ozone:events_ozone",
+    "//ui/gfx",
+    "//ui/views:views",
+  ]
+}
diff --git a/ash/components/cursor/DEPS b/ash/components/cursor/DEPS
new file mode 100644
index 0000000..f95cf84
--- /dev/null
+++ b/ash/components/cursor/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+  "+ash/components/fast_ink",
+  "+base",
+  "+cc/paint",
+  "+components/viz/common",
+  "+ui/aura",
+  "+ui/events",
+  "+ui/gfx",
+  "+ui/views",
+]
diff --git a/ash/components/cursor/OWNERS b/ash/components/cursor/OWNERS
new file mode 100644
index 0000000..6701591
--- /dev/null
+++ b/ash/components/cursor/OWNERS
@@ -0,0 +1,2 @@
+reveman@chromium.org
+oshima@chromium.org
diff --git a/ash/components/cursor/cursor_view.cc b/ash/components/cursor/cursor_view.cc
new file mode 100644
index 0000000..dcb993e
--- /dev/null
+++ b/ash/components/cursor/cursor_view.cc
@@ -0,0 +1,339 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/components/cursor/cursor_view.h"
+
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cc/paint/paint_canvas.h"
+#include "ui/aura/window.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/widget/widget.h"
+
+namespace cursor {
+namespace {
+
+// Amount of time without cursor movement before entering stationary state.
+const int kStationaryDelayMs = 500;
+
+// Clamp velocity to this value.
+const float kVelocityMax = 5000.0f;
+
+// Interpolation factor used to compute responsive velocity. Valid range
+// is 0.0 to 1.0, where 1.0 takes only current velocity into account.
+const float kResponsiveVelocityFactor = 0.75f;
+
+// Interpolation factor used to compute smooth velocity. Valid range
+// is 0.0 to 1.0, where 1.0 takes only current velocity into account.
+const float kSmoothVelocityFactor = 0.25f;
+
+// Interpolation factor used to compute cursor movement. Valid range
+// is 0.0 to 1.0, where 1.0 takes only smooth velocity into account.
+const float kMovementFactor = 0.25f;
+
+// Minimum movement for motion blur to be added.
+const float kMinimumMovementForMotionBlur = 2.0f;
+
+// Clamp motion blur sigma to this value.
+const float kSigmaMax = 48.0f;
+
+// Offset relative to VSYNC at which to request a redraw.
+const int kVSyncOffsetMs = -4;
+
+gfx::Vector2dF InterpolateBetween(const gfx::Vector2dF& start,
+                                  const gfx::Vector2dF& end,
+                                  float f) {
+  return start + gfx::ScaleVector2d(end - start, f);
+}
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// CursorView, public:
+
+CursorView::CursorView(aura::Window* container,
+                       const gfx::Point& initial_location,
+                       bool is_motion_blur_enabled)
+    : fast_ink::FastInkView(
+          container,
+          base::BindRepeating(&CursorView::DidPresentCompositorFrame,
+                              base::Unretained(this))),
+      is_motion_blur_enabled_(is_motion_blur_enabled),
+      ui_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      paint_task_runner_(base::CreateSingleThreadTaskRunnerWithTraits(
+          {base::TaskPriority::USER_BLOCKING,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+      new_location_(initial_location),
+      stationary_timer_(new base::Timer(
+          FROM_HERE,
+          base::TimeDelta::FromMilliseconds(kStationaryDelayMs),
+          base::BindRepeating(&CursorView::StationaryOnPaintThread,
+                              base::Unretained(this)),
+          /*is_repeating=*/false)),
+      weak_ptr_factory_(this) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+
+  // Detach sequence checker for future usage on paint thread.
+  DETACH_FROM_SEQUENCE(paint_sequence_checker_);
+
+  // Create update surface callback that will be posted from paint thread
+  // to UI thread.
+  update_surface_callback_ = base::BindRepeating(
+      &CursorView::UpdateSurface, weak_ptr_factory_.GetWeakPtr());
+
+  // Create transform used to convert cursor controller coordinates to screen
+  // coordinates.
+  bool rv =
+      screen_to_buffer_transform_.GetInverse(&buffer_to_screen_transform_);
+  DCHECK(rv);
+
+  ui::CursorController::GetInstance()->AddCursorObserver(this);
+}
+
+CursorView::~CursorView() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+
+  ui::CursorController::GetInstance()->RemoveCursorObserver(this);
+}
+
+void CursorView::SetCursorImage(const gfx::ImageSkia& cursor_image,
+                                const gfx::Size& cursor_size,
+                                const gfx::Point& cursor_hotspot) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+
+  {
+    base::AutoLock lock(lock_);
+
+    new_cursor_image_ = cursor_image;
+    new_cursor_size_ = cursor_size;
+    new_cursor_hotspot_ = cursor_hotspot;
+  }
+
+  // Unretained is safe as |paint_task_runner_| uses SKIP_ON_SHUTDOWN.
+  paint_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&CursorView::SetActiveOnPaintThread,
+                                base::Unretained(this), true));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ui::CursorController::CursorObserver overrides:
+
+void CursorView::OnCursorLocationChanged(const gfx::PointF& location) {
+  gfx::PointF new_location_f = location;
+  buffer_to_screen_transform_.TransformPoint(&new_location_f);
+  gfx::Point new_location = gfx::ToRoundedPoint(new_location_f);
+
+  {
+    base::AutoLock lock(lock_);
+
+    if (new_location_ == new_location)
+      return;
+    new_location_ = new_location;
+  }
+
+  // Unretained is safe as |paint_task_runner_| uses SKIP_ON_SHUTDOWN.
+  paint_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&CursorView::SetActiveOnPaintThread,
+                                base::Unretained(this), true));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// viz::DelayBasedTimeSourceClient overrides:
+
+void CursorView::OnTimerTick() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(paint_sequence_checker_);
+
+  gfx::Point old_location = location_;
+
+  {
+    base::AutoLock lock(lock_);
+
+    location_ = new_location_;
+    cursor_size_ = new_cursor_size_;
+    cursor_image_ = new_cursor_image_;
+    cursor_hotspot_ = new_cursor_hotspot_;
+  }
+
+  // Restart stationary timer if pointer location changed.
+  if (location_ != old_location)
+    stationary_timer_->Reset();
+
+  base::TimeDelta interval = time_source_->Interval();
+  // Compute velocity unless this is the first tick.
+  if (time_source_->LastTickTime() == next_tick_time_) {
+    // Velocity is pixels/second as interval might change.
+    velocity_ = gfx::ScaleVector2d(old_location - location_,
+                                   1.f / interval.InSecondsF());
+    velocity_.SetToMin(gfx::Vector2dF(kVelocityMax, kVelocityMax));
+  }
+
+  // Save next tick time.
+  next_tick_time_ = time_source_->NextTickTime();
+
+  // Use "Complementary Filter" algorithm to determine velocity.
+  // This allows us to be responsive in the short term and accurate
+  // in the long term.
+  responsive_velocity_ = InterpolateBetween(responsive_velocity_, velocity_,
+                                            kResponsiveVelocityFactor);
+  smooth_velocity_ =
+      InterpolateBetween(smooth_velocity_, velocity_, kSmoothVelocityFactor);
+
+  // Estimate movement over one time source (VSYNC) interval.
+  gfx::Vector2dF movement =
+      gfx::ScaleVector2d(InterpolateBetween(responsive_velocity_,
+                                            smooth_velocity_, kMovementFactor),
+                         interval.InSecondsF());
+
+  float distance = movement.Length();
+  if (is_motion_blur_enabled_ && distance >= kMinimumMovementForMotionBlur) {
+    float sigma = std::min(distance / 3.f, kSigmaMax);
+
+    // Create directional blur filter for |sigma|.
+    motion_blur_filter_ = sk_make_sp<cc::BlurPaintFilter>(
+        sigma, 0.f, SkBlurImageFilter::TileMode::kClampToBlack_TileMode,
+        nullptr);
+
+    // Compute blur offset.
+    motion_blur_offset_ =
+        gfx::ScaleVector2d(movement, std::ceil(sigma * 3.f) / distance);
+
+    // Determine angle of movement.
+    SkScalar angle = SkScalarATan2(SkFloatToScalar(movement.y()),
+                                   SkFloatToScalar(movement.x()));
+    SkScalar cos_angle = SkScalarCos(angle);
+    SkScalar sin_angle = SkScalarSin(angle);
+
+    // Create transformation matrices for blur space.
+    motion_blur_matrix_.setSinCos(-sin_angle, cos_angle);
+    motion_blur_inverse_matrix_.setSinCos(sin_angle, cos_angle);
+  } else {
+    motion_blur_filter_.reset();
+    responsive_velocity_ = gfx::Vector2dF();
+    smooth_velocity_ = gfx::Vector2dF();
+    time_source_->SetActive(false);
+  }
+
+  // Damage is the union of old and new cursor rectangles.
+  gfx::Rect damage_rect = cursor_rect_;
+  cursor_rect_ = CalculateCursorRectOnPaintThread();
+  damage_rect.Union(cursor_rect_);
+
+  // Paint damaged area now that all parameters have been determined.
+  {
+    TRACE_EVENT1("ui", "CursorView::Paint", "damage_rect",
+                 damage_rect.ToString());
+
+    ScopedPaint paint(gpu_memory_buffer_.get(), screen_to_buffer_transform_,
+                      damage_rect);
+    cc::PaintCanvas* sk_canvas = paint.canvas().sk_canvas();
+    sk_canvas->translate(SkIntToScalar(location_.x() - cursor_hotspot_.x()),
+                         SkIntToScalar(location_.y() - cursor_hotspot_.y()));
+
+    if (motion_blur_filter_) {
+      sk_canvas->translate(SkIntToScalar(motion_blur_offset_.x()),
+                           SkIntToScalar(motion_blur_offset_.y()));
+
+      sk_canvas->concat(motion_blur_inverse_matrix_);
+      SkRect blur_rect = SkRect::MakeWH(SkIntToScalar(cursor_size_.width()),
+                                        SkIntToScalar(cursor_size_.height()));
+      motion_blur_matrix_.mapRect(&blur_rect);
+      cc::PaintFlags flags;
+      flags.setImageFilter(motion_blur_filter_);
+      sk_canvas->saveLayer(&blur_rect, &flags);
+      sk_canvas->concat(motion_blur_matrix_);
+      paint.canvas().DrawImageInt(cursor_image_, 0, 0);
+      sk_canvas->restore();
+    } else {
+      // Fast path for when motion blur is not present.
+      paint.canvas().DrawImageInt(cursor_image_, 0, 0);
+    }
+  }
+
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(update_surface_callback_, cursor_rect_, damage_rect,
+                     /*auto_refresh=*/stationary_timer_->IsRunning()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CursorView, private:
+
+void CursorView::StationaryOnPaintThread() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(paint_sequence_checker_);
+
+  stationary_timer_->Stop();
+  ui_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(update_surface_callback_, cursor_rect_,
+                                /*damage_rect=*/gfx::Rect(),
+                                /*auto_refresh=*/false));
+}
+
+gfx::Rect CursorView::CalculateCursorRectOnPaintThread() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(paint_sequence_checker_);
+
+  if (cursor_size_.IsEmpty())
+    return gfx::Rect();
+
+  SkRect cursor_rect = SkRect::MakeWH(SkIntToScalar(cursor_size_.width()),
+                                      SkIntToScalar(cursor_size_.height()));
+
+  if (motion_blur_filter_) {
+    // Map curser rectangle to blur space.
+    motion_blur_matrix_.mapRect(&cursor_rect);
+
+    // Expand rectangle using current blur filter.
+    cc::PaintFlags flags;
+    flags.setImageFilter(motion_blur_filter_);
+    DCHECK(flags.ToSkPaint().canComputeFastBounds());
+    flags.ToSkPaint().computeFastBounds(cursor_rect, &cursor_rect);
+
+    // Map rectangle back to cursor space.
+    motion_blur_inverse_matrix_.mapRect(&cursor_rect);
+
+    // Add motion blur offset.
+    cursor_rect.offset(SkIntToScalar(motion_blur_offset_.x()),
+                       SkIntToScalar(motion_blur_offset_.y()));
+  }
+
+  cursor_rect.offset(SkIntToScalar(location_.x() - cursor_hotspot_.x()),
+                     SkIntToScalar(location_.y() - cursor_hotspot_.y()));
+
+  return gfx::ToEnclosingRect(gfx::SkRectToRectF(cursor_rect));
+}
+
+void CursorView::SetActiveOnPaintThread(bool active) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(paint_sequence_checker_);
+
+  // Create time source if it doesn't exist.
+  if (!time_source_) {
+    time_source_ =
+        std::make_unique<viz::DelayBasedTimeSource>(paint_task_runner_.get());
+    time_source_->SetClient(this);
+  }
+  time_source_->SetActive(active);
+}
+
+void CursorView::SetTimebaseAndIntervalOnPaintThread(base::TimeTicks timebase,
+                                                     base::TimeDelta interval) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(paint_sequence_checker_);
+
+  DCHECK(time_source_);
+  time_source_->SetTimebaseAndInterval(
+      timebase + base::TimeDelta::FromMilliseconds(kVSyncOffsetMs), interval);
+}
+
+void CursorView::DidPresentCompositorFrame(base::TimeTicks time,
+                                           base::TimeDelta refresh,
+                                           uint32_t flags) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+
+  // Unretained is safe as |paint_task_runner_| uses SKIP_ON_SHUTDOWN.
+  paint_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CursorView::SetTimebaseAndIntervalOnPaintThread,
+                     base::Unretained(this), time, refresh));
+}
+
+}  // namespace cursor
diff --git a/ash/components/cursor/cursor_view.h b/ash/components/cursor/cursor_view.h
new file mode 100644
index 0000000..600b5c8
--- /dev/null
+++ b/ash/components/cursor/cursor_view.h
@@ -0,0 +1,94 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_COMPONENTS_CURSOR_CURSOR_VIEW_H_
+#define ASH_COMPONENTS_CURSOR_CURSOR_VIEW_H_
+
+#include "ash/components/fast_ink/fast_ink_view.h"
+#include "base/sequence_checker.h"
+#include "components/viz/common/frame_sinks/delay_based_time_source.h"
+#include "ui/events/ozone/chromeos/cursor_controller.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}  // namespace base
+
+namespace cursor {
+
+// CursorView class can be used to display a cursor image with minimal
+// latency/jank and optional motion blur.
+class CursorView : public fast_ink::FastInkView,
+                   public viz::DelayBasedTimeSourceClient,
+                   public ui::CursorController::CursorObserver {
+ public:
+  CursorView(aura::Window* container,
+             const gfx::Point& initial_location,
+             bool is_motion_blur_enabled);
+  ~CursorView() override;
+
+  void SetCursorLocation(const gfx::Point& new_location);
+  void SetCursorImage(const gfx::ImageSkia& cursor_image,
+                      const gfx::Size& cursor_size,
+                      const gfx::Point& cursor_hotspot);
+
+  // ui::CursorController::CursorObserver overrides:
+  void OnCursorLocationChanged(const gfx::PointF& location) override;
+
+  // viz::DelayBasedTimeSourceClient overrides:
+  void OnTimerTick() override;
+
+ private:
+  void StationaryOnPaintThread();
+  gfx::Rect CalculateCursorRectOnPaintThread() const;
+  void SetActiveOnPaintThread(bool active);
+  void SetTimebaseAndIntervalOnPaintThread(base::TimeTicks timebase,
+                                           base::TimeDelta interval);
+  void DidPresentCompositorFrame(base::TimeTicks time,
+                                 base::TimeDelta refresh,
+                                 uint32_t flags);
+
+  // Constants that can be used on any thread.
+  const bool is_motion_blur_enabled_;
+  const scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+  const scoped_refptr<base::SingleThreadTaskRunner> paint_task_runner_;
+  gfx::Transform buffer_to_screen_transform_;
+
+  base::Lock lock_;
+  // Shared state protected by |lock_|.
+  gfx::Point new_location_;
+  gfx::Size new_cursor_size_;
+  gfx::ImageSkia new_cursor_image_;
+  gfx::Point new_cursor_hotspot_;
+
+  // Paint thread state.
+  gfx::Point location_;
+  gfx::ImageSkia cursor_image_;
+  gfx::Size cursor_size_;
+  gfx::Point cursor_hotspot_;
+  gfx::Rect cursor_rect_;
+  base::TimeTicks next_tick_time_;
+  gfx::Vector2dF velocity_;
+  gfx::Vector2dF responsive_velocity_;
+  gfx::Vector2dF smooth_velocity_;
+  sk_sp<cc::PaintFilter> motion_blur_filter_;
+  gfx::Vector2dF motion_blur_offset_;
+  SkMatrix motion_blur_matrix_;
+  SkMatrix motion_blur_inverse_matrix_;
+  std::unique_ptr<viz::DelayBasedTimeSource> time_source_;
+  std::unique_ptr<base::Timer> stationary_timer_;
+  base::RepeatingCallback<void(const gfx::Rect&, const gfx::Rect&, bool)>
+      update_surface_callback_;
+  SEQUENCE_CHECKER(paint_sequence_checker_);
+
+  // UI thread state.
+  ui::Compositor* compositor_ = nullptr;
+  SEQUENCE_CHECKER(ui_sequence_checker_);
+  base::WeakPtrFactory<CursorView> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CursorView);
+};
+
+}  // namespace cursor
+
+#endif  // ASH_COMPONENTS_CURSOR_CURSOR_VIEW_H_
diff --git a/ash/components/fast_ink/fast_ink_view.cc b/ash/components/fast_ink/fast_ink_view.cc
index ced5301..e567adf 100644
--- a/ash/components/fast_ink/fast_ink_view.cc
+++ b/ash/components/fast_ink/fast_ink_view.cc
@@ -220,7 +220,10 @@
   void DidPresentCompositorFrame(uint32_t presentation_token,
                                  base::TimeTicks time,
                                  base::TimeDelta refresh,
-                                 uint32_t flags) override {}
+                                 uint32_t flags) override {
+    if (view_)
+      view_->DidPresentCompositorFrame(time, refresh, flags);
+  }
   void DidDiscardCompositorFrame(uint32_t presentation_token) override {}
   void DidLoseLayerTreeFrameSink() override {
     exported_resources_.clear();
@@ -265,7 +268,9 @@
   DISALLOW_COPY_AND_ASSIGN(LayerTreeFrameSinkHolder);
 };
 
-FastInkView::FastInkView(aura::Window* container) : weak_ptr_factory_(this) {
+FastInkView::FastInkView(aura::Window* container,
+                         const PresentationCallback& presentation_callback)
+    : presentation_callback_(presentation_callback), weak_ptr_factory_(this) {
   widget_.reset(new views::Widget);
   views::Widget::InitParams params;
   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
@@ -476,6 +481,13 @@
       viz::BeginFrameAck::CreateManualAckWithDamage();
   frame.metadata.device_scale_factor = device_scale_factor;
 
+  if (!presentation_callback_.is_null()) {
+    // If overflow happens, we increase it again.
+    if (!++presentation_token_)
+      ++presentation_token_;
+    frame.metadata.presentation_token = presentation_token_;
+  }
+
   viz::TextureDrawQuad* texture_quad =
       render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
   float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f};
@@ -514,6 +526,13 @@
   }
 }
 
+void FastInkView::DidPresentCompositorFrame(base::TimeTicks time,
+                                            base::TimeDelta refresh,
+                                            uint32_t flags) {
+  DCHECK(!presentation_callback_.is_null());
+  presentation_callback_.Run(time, refresh, flags);
+}
+
 void FastInkView::ReclaimResource(std::unique_ptr<Resource> resource) {
   returned_resources_.push_back(std::move(resource));
 }
diff --git a/ash/components/fast_ink/fast_ink_view.h b/ash/components/fast_ink/fast_ink_view.h
index 75143935..5d7f0e5 100644
--- a/ash/components/fast_ink/fast_ink_view.h
+++ b/ash/components/fast_ink/fast_ink_view.h
@@ -35,10 +35,16 @@
 // when possible and trigger continious updates.
 class FastInkView : public views::View {
  public:
+  using PresentationCallback =
+      base::RepeatingCallback<void(base::TimeTicks presentation_time,
+                                   base::TimeDelta refresh,
+                                   uint32_t flags)>;
+
   // Creates a FastInkView filling the bounds of |root_window|.
   // If |root_window| is resized (e.g. due to a screen size change),
   // a new instance of FastInkView should be created.
-  explicit FastInkView(aura::Window* container);
+  FastInkView(aura::Window* container,
+              const PresentationCallback& presentation_callback);
   ~FastInkView() override;
 
  protected:
@@ -67,6 +73,7 @@
                      bool auto_refresh);
 
   // Constants initialized in constructor.
+  const PresentationCallback presentation_callback_;
   gfx::Transform screen_to_buffer_transform_;
   gfx::Size buffer_size_;
   std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer_;
@@ -79,6 +86,9 @@
   void SubmitPendingCompositorFrame();
   void ReclaimResource(std::unique_ptr<Resource> resource);
   void DidReceiveCompositorFrameAck();
+  void DidPresentCompositorFrame(base::TimeTicks time,
+                                 base::TimeDelta refresh,
+                                 uint32_t flags);
 
   std::unique_ptr<views::Widget> widget_;
   gfx::Rect content_rect_;
@@ -87,6 +97,7 @@
   bool pending_compositor_frame_ = false;
   bool pending_compositor_frame_ack_ = false;
   int next_resource_id_ = 1;
+  uint32_t presentation_token_ = 0;
   std::vector<std::unique_ptr<Resource>> returned_resources_;
   std::unique_ptr<LayerTreeFrameSinkHolder> frame_sink_holder_;
   base::WeakPtrFactory<FastInkView> weak_ptr_factory_;
diff --git a/ash/components/shortcut_viewer_strings.grdp b/ash/components/shortcut_viewer_strings.grdp
index 36cb6417..4a8b59f6 100644
--- a/ash/components/shortcut_viewer_strings.grdp
+++ b/ash/components/shortcut_viewer_strings.grdp
@@ -271,7 +271,7 @@
     Highlight the bookmarks bar (if shown)
   </message>
   <message name="IDS_KSV_DESCRIPTION_IDC_FOCUS_SEARCH" desc="Description of the command in keyboard shortcut viewer.">
-    Focus address bar on search
+    Place focus in search address bar
   </message>
   <message name="IDS_KSV_SHORTCUT_IDC_FOCUS_SEARCH" desc="Description of the command in keyboard shortcut viewer.">
     <ph name="ctrl">$1<ex>Ctrl</ex></ph><ph name="separator">$2<ex>+</ex></ph><ph name="k">$3<ex>v</ex></ph> or <ph name="e">$4<ex>v</ex></ph>
@@ -295,7 +295,7 @@
     Paste content from the clipboard
   </message>
   <message name="IDS_KSV_DESCRIPTION_SELECT_NUMBERED_TAB" desc="Description of the command in keyboard shortcut viewer.">
-    Go to numbered tab
+    Go to tabs 1 through 8
   </message>
   <message name="IDS_KSV_SHORTCUT_SELECT_NUMBERED_TAB" desc="Human readable version of the keyboard shortcut.">
     <ph name="ctrl">$1<ex>Ctrl</ex></ph><ph name="separator">$2<ex>+</ex></ph> 1 through 8
@@ -454,19 +454,19 @@
     <ph name="shift1">$1<ex>Shift</ex></ph><ph name="separator1">$2<ex>+</ex></ph><ph name="alt">$3<ex>Alt</ex></ph><ph name="separator2">$4<ex>+</ex></ph><ph name="l">$5<ex>v</ex></ph>, then <ph name="shift2">$6<ex>Shift</ex></ph><ph name="separator3">$7<ex>+</ex></ph><ph name="tab">$8<ex>v</ex></ph> or <ph name="left">$9<ex>v</ex></ph>
   </message>
   <message name="IDS_KSV_DESCRIPTION_OPEN_HIGHLIGHTED_ITEM_ON_SHELF" desc="Description of the command in keyboard shortcut viewer.">
-    Open the highlighted button on your shelf
+    Open the highlighted item on your shelf
   </message>
   <message name="IDS_KSV_SHORTCUT_OPEN_HIGHLIGHTED_ITEM_ON_SHELF" desc="Human readable version of the keyboard shortcut.">
     <ph name="shift">$1<ex>Shift</ex></ph><ph name="separator1">$2<ex>+</ex></ph><ph name="alt">$3<ex>Alt</ex></ph><ph name="separator2">$4<ex>+</ex></ph><ph name="l">$5<ex>v</ex></ph>, then <ph name="space">$6<ex>v</ex></ph> or <ph name="enter">$7<ex>v</ex></ph>
   </message>
   <message name="IDS_KSV_DESCRIPTION_REMOVE_HIGHLIGHT_ON_SHELF" desc="Description of the command in keyboard shortcut viewer.">
-    Remove the highlight from a button on your shelf
+    Remove the highlight from an item on your shelf
   </message>
   <message name="IDS_KSV_SHORTCUT_REMOVE_HIGHLIGHT_ON_SHELF" desc="Human readable version of the keyboard shortcut.">
     <ph name="shift">$1<ex>Shift</ex></ph><ph name="separator1">$2<ex>+</ex></ph><ph name="alt">$3<ex>Alt</ex></ph><ph name="separator2">$4<ex>+</ex></ph><ph name="l">$5<ex>v</ex></ph>, then <ph name="esc">$6<ex>v</ex></ph>
   </message>
   <message name="IDS_KSV_DESCRIPTION_SWITCH_FOCUS" desc="Description of the command in keyboard shortcut viewer.">
-    Switch focus between: Status area (where your account picture appears) Launcher Address bar Bookmarks bar (if visible) The webpage that's open Downloads bar (if visible)
+    Switch focus between: Status area (where your account picture appears), Launcher, Address bar, Bookmarks bar (if visible), The webpage that's open, and Downloads bar (if visible).
   </message>
   <message name="IDS_KSV_SHORTCUT_SWITCH_FOCUS" desc="Human readable version of the keyboard shortcut.">
     <ph name="ctrl1">$1<ex>Ctrl</ex></ph><ph name="separator1">$2<ex>+</ex></ph><ph name="left">$3<ex>v</ex></ph> or <ph name="ctrl2">$4<ex>Ctrl</ex></ph><ph name="separator2">$5<ex>+</ex></ph><ph name="right">$6<ex>v</ex></ph>
diff --git a/ash/display/DEPS b/ash/display/DEPS
index d68ef13f..c67ef1e 100644
--- a/ash/display/DEPS
+++ b/ash/display/DEPS
@@ -10,4 +10,7 @@
   "screen_orientation_controller_(chromeos|test_api)\.h": [
     "+third_party/WebKit/public/platform/modules/screen_orientation/WebScreenOrientationLockType.h",
   ],
+  "cursor_window_controller.cc": [
+    "+ash/components/cursor",
+  ],
 }
diff --git a/ash/display/cursor_window_controller.cc b/ash/display/cursor_window_controller.cc
index 22dfc73..754fa0c 100644
--- a/ash/display/cursor_window_controller.cc
+++ b/ash/display/cursor_window_controller.cc
@@ -5,14 +5,17 @@
 #include "ash/display/cursor_window_controller.h"
 
 #include "ash/ash_constants.h"
+#include "ash/components/cursor/cursor_view.h"
 #include "ash/display/mirror_window_controller.h"
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/magnifier/magnification_controller.h"
 #include "ash/public/cpp/ash_pref_names.h"
+#include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
+#include "base/command_line.h"
 #include "components/prefs/pref_service.h"
 #include "services/ui/public/interfaces/window_tree_constants.mojom.h"
 #include "ui/aura/env.h"
@@ -30,6 +33,7 @@
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash {
 namespace {
@@ -92,7 +96,10 @@
 };
 
 CursorWindowController::CursorWindowController()
-    : delegate_(new CursorWindowDelegate()) {}
+    : delegate_(new CursorWindowDelegate()),
+      is_cursor_motion_blur_enabled_(
+          base::CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kAshEnableCursorMotionBlur)) {}
 
 CursorWindowController::~CursorWindowController() {
   SetContainer(NULL);
@@ -115,6 +122,9 @@
 }
 
 bool CursorWindowController::ShouldEnableCursorCompositing() {
+  if (is_cursor_motion_blur_enabled_)
+    return true;
+
   // During startup, we may not have a preference service yet. We need to check
   // display manager state first so that we don't accidentally ignore it while
   // early outing when there isn't a PrefService yet.
@@ -192,20 +202,20 @@
 
   SetContainer(RootWindowController::ForWindow(root_window)
                    ->GetContainer(kShellWindowId_MouseCursorContainer));
-  SetBoundsInScreen(display.bounds());
+  SetBoundsInScreenAndRotation(display.bounds(), display.rotation());
   // Updates the hot point based on the current display.
   UpdateCursorImage();
 }
 
 void CursorWindowController::UpdateLocation() {
-  if (!cursor_window_)
-    return;
   gfx::Point point = aura::Env::GetInstance()->last_mouse_location();
   if (!is_cursor_compositing_enabled_) {
     Shell::GetPrimaryRootWindow()->GetHost()->ConvertDIPToPixels(&point);
   } else {
     point.Offset(-bounds_in_screen_.x(), -bounds_in_screen_.y());
   }
+  if (!cursor_window_)
+    return;
   point.Offset(-hot_point_.x(), -hot_point_.y());
   gfx::Rect bounds = cursor_window_->bounds();
   bounds.set_origin(point);
@@ -236,27 +246,41 @@
   container_ = container;
   if (!container) {
     cursor_window_.reset();
+    cursor_view_.reset();
     return;
   }
 
-  // Reusing the window does not work when the display is disconnected.
-  // Just creates a new one instead. crbug.com/384218.
-  cursor_window_.reset(new aura::Window(delegate_.get()));
-  cursor_window_->SetTransparent(true);
-  cursor_window_->Init(ui::LAYER_TEXTURED);
-  cursor_window_->SetEventTargetingPolicy(
-      ui::mojom::EventTargetingPolicy::NONE);
-  cursor_window_->set_owned_by_parent(false);
-  // Call UpdateCursorImage() to figure out |cursor_window_|'s desired size.
-  UpdateCursorImage();
+  bounds_in_screen_ = display_.bounds();
+  rotation_ = display_.rotation();
 
-  container->AddChild(cursor_window_.get());
+  if (is_cursor_motion_blur_enabled_) {
+    UpdateCursorView();
+  } else {
+    // Reusing the window does not work when the display is disconnected.
+    // Just creates a new one instead. crbug.com/384218.
+    cursor_window_.reset(new aura::Window(delegate_.get()));
+    cursor_window_->SetTransparent(true);
+    cursor_window_->Init(ui::LAYER_TEXTURED);
+    cursor_window_->SetEventTargetingPolicy(
+        ui::mojom::EventTargetingPolicy::NONE);
+    cursor_window_->set_owned_by_parent(false);
+    // Call UpdateCursorImage() to figure out |cursor_window_|'s desired size.
+    UpdateCursorImage();
+    container->AddChild(cursor_window_.get());
+  }
   UpdateCursorVisibility();
-  SetBoundsInScreen(container->bounds());
+  UpdateLocation();
 }
 
-void CursorWindowController::SetBoundsInScreen(const gfx::Rect& bounds) {
+void CursorWindowController::SetBoundsInScreenAndRotation(
+    const gfx::Rect& bounds,
+    display::Display::Rotation rotation) {
+  if (bounds == bounds_in_screen_ && rotation == rotation_)
+    return;
   bounds_in_screen_ = bounds;
+  rotation_ = rotation;
+  if (cursor_view_)
+    UpdateCursorView();
   UpdateLocation();
 }
 
@@ -337,22 +361,38 @@
     hot_point_ = gfx::ConvertPointToDIP(cursor_scale, hot_point_);
   }
 
+  if (cursor_view_) {
+    cursor_view_->SetCursorImage(delegate_->cursor_image(), delegate_->size(),
+                                 hot_point_);
+  }
   if (cursor_window_) {
     cursor_window_->SetBounds(gfx::Rect(delegate_->size()));
     cursor_window_->SchedulePaintInRect(
         gfx::Rect(cursor_window_->bounds().size()));
-    UpdateLocation();
   }
+  UpdateLocation();
 }
 
 void CursorWindowController::UpdateCursorVisibility() {
-  if (!cursor_window_)
-    return;
   bool visible = (visible_ && cursor_type_ != ui::CursorType::kNone);
-  if (visible)
-    cursor_window_->Show();
-  else
-    cursor_window_->Hide();
+  if (visible) {
+    if (cursor_view_)
+      cursor_view_->GetWidget()->Show();
+    if (cursor_window_)
+      cursor_window_->Show();
+  } else {
+    if (cursor_view_)
+      cursor_view_->GetWidget()->Hide();
+    if (cursor_window_)
+      cursor_window_->Hide();
+  }
+}
+
+void CursorWindowController::UpdateCursorView() {
+  cursor_view_.reset(new cursor::CursorView(
+      container_, aura::Env::GetInstance()->last_mouse_location(),
+      is_cursor_motion_blur_enabled_));
+  UpdateCursorImage();
 }
 
 const gfx::ImageSkia& CursorWindowController::GetCursorImageForTest() const {
diff --git a/ash/display/cursor_window_controller.h b/ash/display/cursor_window_controller.h
index 9614cb7..829e072 100644
--- a/ash/display/cursor_window_controller.h
+++ b/ash/display/cursor_window_controller.h
@@ -14,6 +14,10 @@
 #include "ui/base/cursor/cursor.h"
 #include "ui/display/display.h"
 
+namespace cursor {
+class CursorView;
+}
+
 namespace ash {
 
 class CursorWindowControllerTest;
@@ -64,8 +68,9 @@
   // Closes the cursor window if |container| is NULL.
   void SetContainer(aura::Window* container);
 
-  // Sets the bounds of the container in screen coordinates.
-  void SetBoundsInScreen(const gfx::Rect& bounds);
+  // Sets the bounds of the container in screen coordinates and rotation.
+  void SetBoundsInScreenAndRotation(const gfx::Rect& bounds,
+                                    display::Display::Rotation rotation);
 
   // Updates cursor image based on current cursor state.
   void UpdateCursorImage();
@@ -73,6 +78,9 @@
   // Hides/shows cursor window based on current cursor state.
   void UpdateCursorVisibility();
 
+  // Updates cursor view based on current cursor state.
+  void UpdateCursorView();
+
   const gfx::ImageSkia& GetCursorImageForTest() const;
 
   aura::Window* container_ = nullptr;
@@ -83,6 +91,9 @@
   // The bounds of the container in screen coordinates.
   gfx::Rect bounds_in_screen_;
 
+  // The rotation of the container.
+  display::Display::Rotation rotation_ = display::Display::ROTATE_0;
+
   // The native_type of the cursor, see definitions in cursor.h
   ui::CursorType cursor_type_ = ui::CursorType::kNone;
 
@@ -100,6 +111,9 @@
 
   std::unique_ptr<aura::Window> cursor_window_;
   std::unique_ptr<CursorWindowDelegate> delegate_;
+  std::unique_ptr<cursor::CursorView> cursor_view_;
+
+  const bool is_cursor_motion_blur_enabled_;
 
   DISALLOW_COPY_AND_ASSIGN(CursorWindowController);
 };
diff --git a/ash/display/persistent_window_controller.cc b/ash/display/persistent_window_controller.cc
index 3a6ef8a..f7cb7bb 100644
--- a/ash/display/persistent_window_controller.cc
+++ b/ash/display/persistent_window_controller.cc
@@ -5,7 +5,7 @@
 #include "ash/display/persistent_window_controller.h"
 
 #include "ash/display/persistent_window_info.h"
-#include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -30,10 +30,8 @@
 // Returns true when window cycle list can be processed to perform save/restore
 // operations on observing display changes.
 bool ShouldProcessWindowList() {
-  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kAshEnablePersistentWindowBounds)) {
+  if (!features::IsPersistentWindowBoundsEnabled())
     return false;
-  }
 
   // Window cycle list exists in active user session only.
   if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted())
@@ -45,7 +43,54 @@
   return true;
 }
 
-void MaybeRestorePersistentWindowBounds() {
+}  // namespace
+
+constexpr char PersistentWindowController::kNumOfWindowsRestoredHistogramName[];
+
+PersistentWindowController::PersistentWindowController() {
+  display::Screen::GetScreen()->AddObserver(this);
+  Shell::Get()->session_controller()->AddObserver(this);
+  Shell::Get()->window_tree_host_manager()->AddObserver(this);
+}
+
+PersistentWindowController::~PersistentWindowController() {
+  Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
+  Shell::Get()->session_controller()->RemoveObserver(this);
+  display::Screen::GetScreen()->RemoveObserver(this);
+}
+
+void PersistentWindowController::OnWillProcessDisplayChanges() {
+  if (!ShouldProcessWindowList())
+    return;
+
+  for (auto* window : GetWindowList()) {
+    wm::WindowState* window_state = wm::GetWindowState(window);
+    // This implies that we keep the first persistent info until they're valid
+    // to restore, or until they're cleared by user-invoked bounds change.
+    if (window_state->persistent_window_info())
+      continue;
+    window_state->SetPersistentWindowInfo(PersistentWindowInfo(window));
+  }
+}
+
+void PersistentWindowController::OnDisplayAdded(
+    const display::Display& new_display) {
+  restore_callback_ = base::BindOnce(
+      &PersistentWindowController::MaybeRestorePersistentWindowBounds,
+      base::Unretained(this));
+}
+
+void PersistentWindowController::OnSessionStateChanged(
+    session_manager::SessionState state) {
+  MaybeRestorePersistentWindowBounds();
+}
+
+void PersistentWindowController::OnDisplayConfigurationChanged() {
+  if (restore_callback_)
+    std::move(restore_callback_).Run();
+}
+
+void PersistentWindowController::MaybeRestorePersistentWindowBounds() {
   if (!ShouldProcessWindowList())
     return;
 
@@ -93,43 +138,5 @@
   }
 }
 
-}  // namespace
-
-constexpr char PersistentWindowController::kNumOfWindowsRestoredHistogramName[];
-
-PersistentWindowController::PersistentWindowController() {
-  display::Screen::GetScreen()->AddObserver(this);
-  Shell::Get()->session_controller()->AddObserver(this);
-  Shell::Get()->window_tree_host_manager()->AddObserver(this);
-}
-
-PersistentWindowController::~PersistentWindowController() {
-  Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
-  Shell::Get()->session_controller()->RemoveObserver(this);
-  display::Screen::GetScreen()->RemoveObserver(this);
-}
-
-void PersistentWindowController::OnWillProcessDisplayChanges() {
-  if (!ShouldProcessWindowList())
-    return;
-
-  for (auto* window : GetWindowList()) {
-    wm::WindowState* window_state = wm::GetWindowState(window);
-    // This implies that we keep the first persistent info until they're valid
-    // to restore, or until they're cleared by user-invoked bounds change.
-    if (window_state->persistent_window_info())
-      continue;
-    window_state->SetPersistentWindowInfo(PersistentWindowInfo(window));
-  }
-}
-
-void PersistentWindowController::OnSessionStateChanged(
-    session_manager::SessionState state) {
-  MaybeRestorePersistentWindowBounds();
-}
-
-void PersistentWindowController::OnDisplayConfigurationChanged() {
-  MaybeRestorePersistentWindowBounds();
-}
 
 }  // namespace ash
diff --git a/ash/display/persistent_window_controller.h b/ash/display/persistent_window_controller.h
index fa6edcdab..044e842d 100644
--- a/ash/display/persistent_window_controller.h
+++ b/ash/display/persistent_window_controller.h
@@ -8,6 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/session/session_observer.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "ui/display/display_observer.h"
 
@@ -30,6 +31,7 @@
  private:
   // display::DisplayObserver:
   void OnWillProcessDisplayChanges() override;
+  void OnDisplayAdded(const display::Display& new_display) override;
 
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
@@ -37,6 +39,12 @@
   // WindowTreeHostManager::Observer:
   void OnDisplayConfigurationChanged() override;
 
+  // Called when restoring persistent window placement is wanted.
+  void MaybeRestorePersistentWindowBounds();
+
+  // Callback binded on display added and run on display configuration changed.
+  base::OnceCallback<void()> restore_callback_;
+
   DISALLOW_COPY_AND_ASSIGN(PersistentWindowController);
 };
 
diff --git a/ash/display/persistent_window_controller_unittest.cc b/ash/display/persistent_window_controller_unittest.cc
index 51e8dd4..5a014093 100644
--- a/ash/display/persistent_window_controller_unittest.cc
+++ b/ash/display/persistent_window_controller_unittest.cc
@@ -7,15 +7,14 @@
 #include "ash/display/display_move_window_util.h"
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/public/cpp/ash_features.h"
-#include "ash/public/cpp/ash_switches.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
-#include "base/command_line.h"
 #include "base/test/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "ui/display/test/display_manager_test_api.h"
 
 using session_manager::SessionState;
 
@@ -28,10 +27,11 @@
 
   // AshTestBase:
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kDisplayMoveWindowAccels);
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        switches::kAshEnablePersistentWindowBounds);
+    // Explicitly enable persistent window bounds and displays move window
+    // accels features for the tests.
+    scoped_feature_list_.InitWithFeatures(
+        {features::kPersistentWindowBounds, features::kDisplayMoveWindowAccels},
+        {});
     AshTestBase::SetUp();
   }
 
@@ -398,4 +398,37 @@
       PersistentWindowController::kNumOfWindowsRestoredHistogramName, 1);
 }
 
+// Tests that swapping primary display shall not do persistent window restore.
+TEST_F(PersistentWindowControllerTest, SwapPrimaryDisplay) {
+  const int64_t internal_display_id =
+      display::test::DisplayManagerTestApi(display_manager())
+          .SetFirstDisplayAsInternalDisplay();
+  const display::ManagedDisplayInfo native_display_info =
+      display::CreateDisplayInfo(internal_display_id,
+                                 gfx::Rect(0, 0, 500, 500));
+  const display::ManagedDisplayInfo secondary_display_info =
+      display::CreateDisplayInfo(10, gfx::Rect(1, 1, 400, 400));
+
+  std::vector<display::ManagedDisplayInfo> display_info_list;
+  display_info_list.push_back(native_display_info);
+  display_info_list.push_back(secondary_display_info);
+  display_manager()->OnNativeDisplaysChanged(display_info_list);
+
+  aura::Window* w1 =
+      CreateTestWindowInShellWithBounds(gfx::Rect(200, 0, 100, 200));
+  aura::Window* w2 =
+      CreateTestWindowInShellWithBounds(gfx::Rect(501, 0, 200, 100));
+  EXPECT_EQ(gfx::Rect(200, 0, 100, 200), w1->GetBoundsInScreen());
+  EXPECT_EQ(gfx::Rect(501, 0, 200, 100), w2->GetBoundsInScreen());
+
+  // Swaps primary display and check window bounds.
+  SwapPrimaryDisplay();
+  ASSERT_EQ(gfx::Rect(-500, 0, 500, 500),
+            display_manager()->GetDisplayForId(internal_display_id).bounds());
+  ASSERT_EQ(gfx::Rect(0, 0, 400, 400),
+            display_manager()->GetDisplayForId(10).bounds());
+  EXPECT_EQ(gfx::Rect(200, 0, 100, 200), w1->GetBoundsInScreen());
+  EXPECT_EQ(gfx::Rect(-499, 0, 200, 100), w2->GetBoundsInScreen());
+}
+
 }  // namespace ash
diff --git a/ash/highlighter/highlighter_view.cc b/ash/highlighter/highlighter_view.cc
index 7eb937f..4707a0d 100644
--- a/ash/highlighter/highlighter_view.cc
+++ b/ash/highlighter/highlighter_view.cc
@@ -71,7 +71,7 @@
 
 HighlighterView::HighlighterView(base::TimeDelta presentation_delay,
                                  aura::Window* container)
-    : FastInkView(container),
+    : FastInkView(container, PresentationCallback()),
       points_(base::TimeDelta()),
       predicted_points_(base::TimeDelta()),
       presentation_delay_(presentation_delay),
diff --git a/ash/laser/laser_pointer_view.cc b/ash/laser/laser_pointer_view.cc
index 49ada7ae..12aa9a8 100644
--- a/ash/laser/laser_pointer_view.cc
+++ b/ash/laser/laser_pointer_view.cc
@@ -160,7 +160,7 @@
                                    base::TimeDelta presentation_delay,
                                    base::TimeDelta stationary_point_delay,
                                    aura::Window* container)
-    : FastInkView(container),
+    : FastInkView(container, PresentationCallback()),
       laser_points_(life_duration),
       predicted_laser_points_(life_duration),
       presentation_delay_(presentation_delay),
diff --git a/ash/login/ui/fake_login_detachable_base_model.cc b/ash/login/ui/fake_login_detachable_base_model.cc
new file mode 100644
index 0000000..6f9d187
--- /dev/null
+++ b/ash/login/ui/fake_login_detachable_base_model.cc
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/login/ui/fake_login_detachable_base_model.h"
+
+#include "ash/login/ui/login_data_dispatcher.h"
+#include "ash/public/interfaces/user_info.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+FakeLoginDetachableBaseModel::FakeLoginDetachableBaseModel(
+    LoginDataDispatcher* data_dispatcher)
+    : data_dispatcher_(data_dispatcher) {}
+
+FakeLoginDetachableBaseModel::~FakeLoginDetachableBaseModel() = default;
+
+void FakeLoginDetachableBaseModel::InitLastUsedBases(
+    const std::map<AccountId, std::string>& last_used_bases) {
+  ASSERT_TRUE(last_used_bases_.empty());
+  last_used_bases_ = last_used_bases;
+}
+
+std::string FakeLoginDetachableBaseModel::GetLastUsedBase(
+    const AccountId& account_id) {
+  auto it = last_used_bases_.find(account_id);
+  if (it == last_used_bases_.end())
+    return "";
+  return it->second;
+}
+
+void FakeLoginDetachableBaseModel::SetPairingStatus(
+    DetachableBasePairingStatus pairing_status,
+    const std::string& base_id) {
+  ASSERT_EQ(pairing_status == DetachableBasePairingStatus::kAuthenticated,
+            !base_id.empty());
+
+  current_authenticated_base_ = base_id;
+  pairing_status_ = pairing_status;
+  data_dispatcher_->SetDetachableBasePairingStatus(pairing_status);
+}
+
+DetachableBasePairingStatus FakeLoginDetachableBaseModel::GetPairingStatus() {
+  return pairing_status_;
+}
+
+bool FakeLoginDetachableBaseModel::PairedBaseMatchesLastUsedByUser(
+    const mojom::UserInfo& user_info) {
+  EXPECT_FALSE(current_authenticated_base_.empty());
+
+  std::string last_used = GetLastUsedBase(user_info.account_id);
+  return last_used.empty() || last_used == current_authenticated_base_;
+}
+
+bool FakeLoginDetachableBaseModel::SetPairedBaseAsLastUsedByUser(
+    const mojom::UserInfo& user_info) {
+  if (current_authenticated_base_.empty())
+    return false;
+
+  last_used_bases_[user_info.account_id] = current_authenticated_base_;
+  return true;
+}
+
+}  // namespace ash
diff --git a/ash/login/ui/fake_login_detachable_base_model.h b/ash/login/ui/fake_login_detachable_base_model.h
new file mode 100644
index 0000000..eb4fe845
--- /dev/null
+++ b/ash/login/ui/fake_login_detachable_base_model.h
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
+#define ASH_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
+
+#include <map>
+#include <string>
+
+#include "ash/detachable_base/detachable_base_pairing_status.h"
+#include "ash/login/ui/login_detachable_base_model.h"
+#include "base/macros.h"
+#include "components/signin/core/account_id/account_id.h"
+
+namespace ash {
+
+class LoginDataDispatcher;
+
+// Fake LoginDetachableBaseModel implementation. To be used in tests to hide
+// dependency on DetachableBaseHandler.
+class FakeLoginDetachableBaseModel : public LoginDetachableBaseModel {
+ public:
+  // |data_dispatcher| - the dispatcher to which the pairing status changes
+  // should be forwarded.
+  explicit FakeLoginDetachableBaseModel(LoginDataDispatcher* data_dispatcher);
+  ~FakeLoginDetachableBaseModel() override;
+
+  // Sets the initial mapping for user -> last used detachable base.
+  // It will assert if called while |last_used_bases_| is not empty.
+  void InitLastUsedBases(
+      const std::map<AccountId, std::string>& last_used_bases);
+
+  // Gets the last recorded base for the user with the provided account id.
+  // Returns empty string if the user does not have a recorded detcachable base
+  // usage.
+  std::string GetLastUsedBase(const AccountId& account_id);
+
+  // Changes current detachable base pairing status.
+  // |pairing_status| - the new pairing status.
+  // |base_id| - the authenticated base ID. It is expect to be set if and only
+  //             if pairing_status is kAuthenticated.
+  void SetPairingStatus(DetachableBasePairingStatus pairing_status,
+                        const std::string& base_id);
+
+  // LoginDetachableBaseModel:
+  DetachableBasePairingStatus GetPairingStatus() override;
+  bool PairedBaseMatchesLastUsedByUser(
+      const mojom::UserInfo& user_info) override;
+  bool SetPairedBaseAsLastUsedByUser(const mojom::UserInfo& user_info) override;
+
+ private:
+  LoginDataDispatcher* data_dispatcher_;
+
+  // Current pairing status.
+  DetachableBasePairingStatus pairing_status_ =
+      DetachableBasePairingStatus::kNone;
+
+  // The ID if the currently authenticated detachable base.
+  std::string current_authenticated_base_;
+
+  // Maps user account Id to the ID of the last used detachable base.
+  std::map<AccountId, std::string> last_used_bases_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeLoginDetachableBaseModel);
+};
+
+}  // namespace ash
+
+#endif  // ASH_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 2f939080..90b346d 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -4,8 +4,11 @@
 
 #include "ash/login/ui/lock_contents_view.h"
 
+#include <algorithm>
 #include <memory>
+#include <utility>
 
+#include "ash/detachable_base/detachable_base_pairing_status.h"
 #include "ash/focus_cycler.h"
 #include "ash/ime/ime_controller.h"
 #include "ash/keyboard/keyboard_observer_register.h"
@@ -14,6 +17,7 @@
 #include "ash/login/ui/lock_screen.h"
 #include "ash/login/ui/login_auth_user_view.h"
 #include "ash/login/ui/login_bubble.h"
+#include "ash/login/ui/login_detachable_base_model.h"
 #include "ash/login/ui/login_user_view.h"
 #include "ash/login/ui/non_accessible_view.h"
 #include "ash/login/ui/note_action_launch_button.h"
@@ -46,6 +50,7 @@
 #include "ui/views/focus/focus_search.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/style/typography.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -196,6 +201,14 @@
   return view_->tooltip_bubble_.get();
 }
 
+LoginBubble* LockContentsView::TestApi::auth_error_bubble() const {
+  return view_->auth_error_bubble_.get();
+}
+
+LoginBubble* LockContentsView::TestApi::detachable_base_error_bubble() const {
+  return view_->detachable_base_error_bubble_.get();
+}
+
 views::View* LockContentsView::TestApi::dev_channel_info() const {
   return view_->dev_channel_info_;
 }
@@ -209,9 +222,11 @@
 
 LockContentsView::LockContentsView(
     mojom::TrayActionState initial_note_action_state,
-    LoginDataDispatcher* data_dispatcher)
+    LoginDataDispatcher* data_dispatcher,
+    std::unique_ptr<LoginDetachableBaseModel> detachable_base_model)
     : NonAccessibleView(kLockContentsViewName),
       data_dispatcher_(data_dispatcher),
+      detachable_base_model_(std::move(detachable_base_model)),
       display_observer_(this),
       session_observer_(this),
       keyboard_observer_(this) {
@@ -219,7 +234,8 @@
   display_observer_.Add(display::Screen::GetScreen());
   Shell::Get()->login_screen_controller()->AddLockScreenAppsFocusObserver(this);
   Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this);
-  error_bubble_ = std::make_unique<LoginBubble>();
+  auth_error_bubble_ = std::make_unique<LoginBubble>();
+  detachable_base_error_bubble_ = std::make_unique<LoginBubble>();
   tooltip_bubble_ = std::make_unique<LoginBubble>();
 
   // We reuse the focusable state on this view as a signal that focus should
@@ -496,6 +512,43 @@
   NOTIMPLEMENTED();
 }
 
+void LockContentsView::OnDetachableBasePairingStatusChanged(
+    DetachableBasePairingStatus pairing_status) {
+  const mojom::UserInfoPtr& user_info =
+      CurrentAuthUserView()->current_user()->basic_user_info;
+  // If the base is not paired, or the paired base matches the last used by the
+  // current user, the detachable base error bubble should be hidden. Otherwise,
+  // the bubble should be shown.
+  if (pairing_status == DetachableBasePairingStatus::kNone ||
+      (pairing_status == DetachableBasePairingStatus::kAuthenticated &&
+       detachable_base_model_->PairedBaseMatchesLastUsedByUser(*user_info))) {
+    detachable_base_error_bubble_->Close();
+    return;
+  }
+
+  auth_error_bubble_->Close();
+
+  base::string16 error_text =
+      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_DETACHABLE_BASE_CHANGED);
+
+  views::Label* label =
+      new views::Label(error_text, views::style::CONTEXT_MESSAGE_BOX_BODY_TEXT,
+                       views::style::STYLE_PRIMARY);
+  label->SetMultiLine(true);
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  label->SetEnabledColor(SK_ColorWHITE);
+
+  detachable_base_error_bubble_->ShowErrorBubble(
+      label, CurrentAuthUserView()->password_view() /*anchor_view*/,
+      LoginBubble::kFlagPersistent);
+
+  // Remove the focus from the password field, to make user less likely to enter
+  // the password without seeing the warning about detachable base change.
+  if (GetWidget()->IsActive())
+    GetWidget()->GetFocusManager()->ClearFocus();
+}
+
 void LockContentsView::OnFocusLeavingLockScreenApps(bool reverse) {
   if (!reverse || lock_screen_apps_active_)
     FocusNextWidget(reverse);
@@ -696,9 +749,20 @@
 
 void LockContentsView::OnAuthenticate(bool auth_success) {
   if (auth_success) {
-    error_bubble_->Close();
+    auth_error_bubble_->Close();
+    detachable_base_error_bubble_->Close();
+
+    // Now that the user has been authenticated, update the user's last used
+    // detachable base (if one is attached). This will prevent further
+    // detachable base change notifications from appearing for this base (until
+    // the user uses another detachable base).
+    if (detachable_base_model_->GetPairingStatus() ==
+        DetachableBasePairingStatus::kAuthenticated) {
+      detachable_base_model_->SetPairedBaseAsLastUsedByUser(
+          *CurrentAuthUserView()->current_user()->basic_user_info);
+    }
   } else {
-    ShowErrorMessage();
+    ShowAuthErrorMessage();
     ++unlock_attempt_;
   }
 }
@@ -778,6 +842,11 @@
     // Reset unlock attempt when the auth user changes.
     unlock_attempt_ = 0;
   }
+
+  // The new auth user might have different last used detachable base - make
+  // sure the detachable base pairing error is updated if needed.
+  OnDetachableBasePairingStatusChanged(
+      detachable_base_model_->GetPairingStatus());
 }
 
 void LockContentsView::UpdateEasyUnlockIconForUser(const AccountId& user) {
@@ -818,7 +887,7 @@
   return primary_auth_;
 }
 
-void LockContentsView::ShowErrorMessage() {
+void LockContentsView::ShowAuthErrorMessage() {
   base::string16 error_text = l10n_util::GetStringUTF16(
       unlock_attempt_ ? IDS_ASH_LOGIN_ERROR_AUTHENTICATING_2ND_TIME
                       : IDS_ASH_LOGIN_ERROR_AUTHENTICATING);
@@ -848,8 +917,11 @@
 
   views::StyledLabel* label = new views::StyledLabel(error_text, this);
   MakeSectionBold(label, error_text, bold_start, bold_length);
-  error_bubble_->ShowErrorBubble(
-      label, CurrentAuthUserView()->password_view() /*anchor_view*/);
+  label->set_auto_color_readability_enabled(false);
+
+  auth_error_bubble_->ShowErrorBubble(
+      label, CurrentAuthUserView()->password_view() /*anchor_view*/,
+      LoginBubble::kFlagsNone);
 }
 
 void LockContentsView::OnEasyUnlockIconHovered() {
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 7f9ccb70..4c0ee10 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -39,6 +39,7 @@
 
 class LoginAuthUserView;
 class LoginBubble;
+class LoginDetachableBaseModel;
 class NoteActionLaunchButton;
 class ScrollableUsersListView;
 
@@ -71,14 +72,18 @@
     ScrollableUsersListView* users_list() const;
     views::View* note_action() const;
     LoginBubble* tooltip_bubble() const;
+    LoginBubble* auth_error_bubble() const;
+    LoginBubble* detachable_base_error_bubble() const;
     views::View* dev_channel_info() const;
 
    private:
     LockContentsView* const view_;
   };
 
-  LockContentsView(mojom::TrayActionState initial_note_action_state,
-                   LoginDataDispatcher* data_dispatcher);
+  LockContentsView(
+      mojom::TrayActionState initial_note_action_state,
+      LoginDataDispatcher* data_dispatcher,
+      std::unique_ptr<LoginDetachableBaseModel> detachable_base_model);
   ~LockContentsView() override;
 
   // views::View:
@@ -111,6 +116,8 @@
                                      const base::ListValue& locales,
                                      const std::string& default_locale,
                                      bool show_advanced_view) override;
+  void OnDetachableBasePairingStatusChanged(
+      DetachableBasePairingStatus pairing_status) override;
 
   // SystemTrayFocusObserver:
   void OnFocusLeavingSystemTray(bool reverse) override;
@@ -216,7 +223,7 @@
   LoginAuthUserView* CurrentAuthUserView();
 
   // Opens an error bubble to indicate authentication failure.
-  void ShowErrorMessage();
+  void ShowAuthErrorMessage();
 
   // Called when the easy unlock icon is hovered.
   void OnEasyUnlockIconHovered();
@@ -247,6 +254,7 @@
   std::vector<UserState> users_;
 
   LoginDataDispatcher* const data_dispatcher_;  // Unowned.
+  std::unique_ptr<LoginDetachableBaseModel> detachable_base_model_;
 
   LoginAuthUserView* primary_auth_ = nullptr;
   LoginAuthUserView* opt_secondary_auth_ = nullptr;
@@ -279,7 +287,12 @@
                  keyboard::KeyboardControllerObserver>
       keyboard_observer_;
 
-  std::unique_ptr<LoginBubble> error_bubble_;
+  // Bubbles for displaying authentication error.
+  std::unique_ptr<LoginBubble> auth_error_bubble_;
+
+  // Bubble for displaying error when the user's detachable base changes.
+  std::unique_ptr<LoginBubble> detachable_base_error_bubble_;
+
   std::unique_ptr<LoginBubble> tooltip_bubble_;
 
   int unlock_attempt_ = 0;
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index a01c086..9240825 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -3,8 +3,13 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <string>
 #include <unordered_set>
+#include <utility>
 
+#include "ash/detachable_base/detachable_base_pairing_status.h"
+#include "ash/login/mock_login_screen_client.h"
+#include "ash/login/ui/fake_login_detachable_base_model.h"
 #include "ash/login/ui/lock_contents_view.h"
 #include "ash/login/ui/lock_screen.h"
 #include "ash/login/ui/login_auth_user_view.h"
@@ -16,6 +21,7 @@
 #include "ash/login/ui/login_user_view.h"
 #include "ash/login/ui/scrollable_users_list_view.h"
 #include "ash/public/interfaces/tray_action.mojom.h"
+#include "ash/shell.h"
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/display/manager/display_manager.h"
@@ -24,6 +30,8 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/widget/widget.h"
 
+using ::testing::_;
+
 namespace ash {
 
 using LockContentsViewUnitTest = LoginTestBase;
@@ -31,8 +39,9 @@
 
 TEST_F(LockContentsViewUnitTest, DisplayMode) {
   // Build lock screen with 1 user.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
@@ -76,8 +85,9 @@
 
 // Verifies that the single user view is centered.
 TEST_F(LockContentsViewUnitTest, SingleUserCentered) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
@@ -97,8 +107,9 @@
 // Verifies that the single user view is centered when lock screen notes are
 // enabled.
 TEST_F(LockContentsViewUnitTest, SingleUserCenteredNoteActionEnabled) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
@@ -120,8 +131,9 @@
 // mode.
 TEST_F(LockContentsViewUnitTest, AutoLayoutAfterRotation) {
   // Build lock screen with three users.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   LockContentsView::TestApi lock_contents(contents);
   SetUserCount(3);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
@@ -169,8 +181,9 @@
 
 TEST_F(LockContentsViewUnitTest, AutoLayoutExtraSmallUsersListAfterRotation) {
   // Build lock screen with extra small layout (> 6 users).
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(9);
   ScrollableUsersListView* users_list =
       LockContentsView::TestApi(contents).users_list();
@@ -204,8 +217,9 @@
 
 TEST_F(LockContentsViewUnitTest, AutoLayoutSmallUsersListAfterRotation) {
   // Build lock screen with small layout (3-6 users).
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(4);
   ScrollableUsersListView* users_list =
       LockContentsView::TestApi(contents).users_list();
@@ -317,8 +331,9 @@
 // Ensures that when swapping between two users, only auth method display swaps.
 TEST_F(LockContentsViewUnitTest, SwapAuthUsersInTwoUserLayout) {
   // Build lock screen with two users.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   LockContentsView::TestApi test_api(contents);
   SetUserCount(2);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
@@ -362,8 +377,9 @@
 // Ensures that when swapping from a user list, the entire user info is swapped.
 TEST_F(LockContentsViewUnitTest, SwapUserListToPrimaryAuthUser) {
   // Build lock screen with five users.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   LockContentsView::TestApi lock_contents(contents);
   SetUserCount(5);
   ScrollableUsersListView::TestApi users_list(lock_contents.users_list());
@@ -403,8 +419,9 @@
 // Test goes through different lock screen note state changes and tests that
 // the note action visibility is updated accordingly.
 TEST_F(LockContentsViewUnitTest, NoteActionButtonVisibilityChanges) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   SetWidget(CreateWidgetWithContent(contents));
 
@@ -435,8 +452,9 @@
 
 // Verifies note action view bounds.
 TEST_F(LockContentsViewUnitTest, NoteActionButtonBounds) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
@@ -469,8 +487,9 @@
 // Verifies the note action view bounds when note action is available at lock
 // contents view creation.
 TEST_F(LockContentsViewUnitTest, NoteActionButtonBoundsInitiallyAvailable) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
@@ -494,8 +513,9 @@
 
 // Verifies the dev channel info view bounds.
 TEST_F(LockContentsViewUnitTest, DevChannelInfoViewBounds) {
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
 
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
@@ -527,8 +547,9 @@
 
 // Verifies the easy unlock tooltip is automatically displayed when requested.
 TEST_F(LockContentsViewUnitTest, EasyUnlockForceTooltipCreatesTooltipWidget) {
-  auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                    data_dispatcher());
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
 
   SetUserCount(1);
   SetWidget(CreateWidgetWithContent(lock));
@@ -556,8 +577,9 @@
 // Verifies that easy unlock icon state persists when changing auth user.
 TEST_F(LockContentsViewUnitTest, EasyUnlockIconUpdatedDuringUserSwap) {
   // Build lock screen with two users.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(2);
   SetWidget(CreateWidgetWithContent(contents));
 
@@ -639,6 +661,323 @@
   EXPECT_FALSE(showing_easy_unlock_icon(secondary));
 }
 
+TEST_F(LockContentsViewUnitTest, ShowErrorBubbleOnAuthFailure) {
+  // Build lock screen with a single user.
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
+  SetUserCount(1);
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+
+  // Password submit runs mojo.
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  client->set_authenticate_user_callback_result(false);
+  EXPECT_CALL(*client,
+              AuthenticateUser_(users()[0]->basic_user_info->account_id, _, _,
+                                false, _));
+
+  // Submit password.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
+
+  // The error bubble is expected to close on a user action - e.g. if they start
+  // typing the password again.
+  generator.PressKey(ui::KeyboardCode::VKEY_B, 0);
+  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+}
+
+TEST_F(LockContentsViewUnitTest, ErrorBubbleOnUntrustedDetachableBase) {
+  auto fake_detachable_base_model =
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher());
+  FakeLoginDetachableBaseModel* detachable_base_model =
+      fake_detachable_base_model.get();
+
+  // Build lock screen with 2 users.
+  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
+                                        data_dispatcher(),
+                                        std::move(fake_detachable_base_model));
+  SetUserCount(2);
+
+  const AccountId& kFirstUserAccountId =
+      users()[0]->basic_user_info->account_id;
+  const AccountId& kSecondUserAccountId =
+      users()[1]->basic_user_info->account_id;
+
+  // Initialize the detachable base state, so the user 1 has previously used
+  // detachable base.
+  detachable_base_model->InitLastUsedBases({{kFirstUserAccountId, "1234"}});
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "1234");
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Change detachable base to a base different than the one previously used by
+  // the user - verify that a detachable base error bubble is shown.
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "5678");
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Verify that the bubble is not hidden if the user starts typing.
+  generator.PressKey(ui::KeyboardCode::VKEY_B, 0);
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Switching to the user that doesn't have previously used detachable base
+  // (and should thus not be warned about the detachable base missmatch) should
+  // hide the login bubble.
+  LoginAuthUserView::TestApi secondary_test_api(test_api.opt_secondary_auth());
+  generator.MoveMouseTo(
+      secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // The error should be shown again when switching back to the primary user.
+  LoginAuthUserView::TestApi primary_test_api(test_api.primary_auth());
+  generator.MoveMouseTo(
+      primary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(primary_test_api.password_view()->HasFocus());
+
+  EXPECT_EQ("1234",
+            detachable_base_model->GetLastUsedBase(kFirstUserAccountId));
+  EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kSecondUserAccountId));
+
+  // The current detachable base should be set as the last used one by the user
+  // after they authenticate - test for this.
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  client->set_authenticate_user_callback_result(true);
+  EXPECT_CALL(*client, AuthenticateUser_(kFirstUserAccountId, _, _, false, _));
+
+  // Submit password.
+  primary_test_api.password_view()->RequestFocus();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ("5678",
+            detachable_base_model->GetLastUsedBase(kFirstUserAccountId));
+  EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kSecondUserAccountId));
+}
+
+TEST_F(LockContentsViewUnitTest, ErrorBubbleForUnauthenticatedDetachableBase) {
+  auto fake_detachable_base_model =
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher());
+  FakeLoginDetachableBaseModel* detachable_base_model =
+      fake_detachable_base_model.get();
+
+  // Build lock screen with 2 users.
+  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
+                                        data_dispatcher(),
+                                        std::move(fake_detachable_base_model));
+  SetUserCount(2);
+
+  const AccountId& kFirstUserAccountId =
+      users()[0]->basic_user_info->account_id;
+  const AccountId& kSecondUserAccountId =
+      users()[1]->basic_user_info->account_id;
+
+  detachable_base_model->InitLastUsedBases({{kSecondUserAccountId, "5678"}});
+
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Show notification if unauthenticated base is attached.
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kNotAuthenticated, "");
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Verify that the bubble is not hidden if the user starts typing.
+  generator.PressKey(ui::KeyboardCode::VKEY_B, 0);
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Switching to another user should not hide the error bubble.
+  LoginAuthUserView::TestApi secondary_test_api(test_api.opt_secondary_auth());
+  generator.MoveMouseTo(
+      secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(secondary_test_api.password_view()->HasFocus());
+
+  // The last trusted detachable used by the user should not be overriden by
+  // user authentication.
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  client->set_authenticate_user_callback_result(true);
+  EXPECT_CALL(*client, AuthenticateUser_(kSecondUserAccountId, _, _, false, _));
+
+  // Submit password.
+  secondary_test_api.password_view()->RequestFocus();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ("", detachable_base_model->GetLastUsedBase(kFirstUserAccountId));
+  EXPECT_EQ("5678",
+            detachable_base_model->GetLastUsedBase(kSecondUserAccountId));
+}
+TEST_F(LockContentsViewUnitTest,
+       RemovingAttachedBaseHidesDetachableBaseNotification) {
+  auto fake_detachable_base_model =
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher());
+  FakeLoginDetachableBaseModel* detachable_base_model =
+      fake_detachable_base_model.get();
+
+  // Build lock screen with 2 users.
+  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
+                                        data_dispatcher(),
+                                        std::move(fake_detachable_base_model));
+  SetUserCount(1);
+
+  const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id;
+
+  // Initialize the detachable base state, as if the user has previously used
+  // detachable base.
+  detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}});
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "1234");
+
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+
+  // Change detachable base to a base different than the one previously used by
+  // the user - verify that a detachable base error bubble is shown.
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "5678");
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // The notification should be hidden if the base gets detached.
+  detachable_base_model->SetPairingStatus(DetachableBasePairingStatus::kNone,
+                                          "");
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+}
+
+TEST_F(LockContentsViewUnitTest, DetachableBaseErrorClearsAuthError) {
+  auto fake_detachable_base_model =
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher());
+  FakeLoginDetachableBaseModel* detachable_base_model =
+      fake_detachable_base_model.get();
+
+  // Build lock screen with a single user.
+  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
+                                        data_dispatcher(),
+                                        std::move(fake_detachable_base_model));
+  SetUserCount(1);
+
+  const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id;
+
+  // Initialize the detachable base state, as if the user has previously used
+  // detachable base.
+  detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}});
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "1234");
+
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Attempt and fail user auth - an auth error is expected to be shown.
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  client->set_authenticate_user_callback_result(false);
+  EXPECT_CALL(*client, AuthenticateUser_(kUserAccountId, _, _, false, _));
+
+  // Submit password.
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Change detachable base to a base different than the one previously used by
+  // the user - verify that a detachable base error bubble is shown, and the
+  // auth error is hidden.
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "5678");
+
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+}
+
+TEST_F(LockContentsViewUnitTest, AuthErrorDoesNotRemoveDetachableBaseError) {
+  auto fake_detachable_base_model =
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher());
+  FakeLoginDetachableBaseModel* detachable_base_model =
+      fake_detachable_base_model.get();
+
+  // Build lock screen with a single user.
+  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
+                                        data_dispatcher(),
+                                        std::move(fake_detachable_base_model));
+  SetUserCount(1);
+
+  const AccountId& kUserAccountId = users()[0]->basic_user_info->account_id;
+
+  // Initialize the detachable base state, as if the user has previously used
+  // detachable base.
+  detachable_base_model->InitLastUsedBases({{kUserAccountId, "1234"}});
+  SetWidget(CreateWidgetWithContent(contents));
+
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "1234");
+
+  LockContentsView::TestApi test_api(contents);
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Change detachable base to a base different than the one previously used by
+  // the user - verify that a detachable base error bubble is shown, and the
+  // auth error is hidden.
+  detachable_base_model->SetPairingStatus(
+      DetachableBasePairingStatus::kAuthenticated, "5678");
+
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // Attempt and fail user auth - an auth error is expected to be shown.
+  // Detachable base error should not be hidden.
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  client->set_authenticate_user_callback_result(false);
+  EXPECT_CALL(*client, AuthenticateUser_(kUserAccountId, _, _, false, _));
+
+  // Submit password.
+  LoginAuthUserView::TestApi(test_api.primary_auth())
+      .password_view()
+      ->RequestFocus();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+  generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+
+  // User action, like pressing a key should close the auth error bubble, but
+  // not the detachable base error bubble.
+  generator.PressKey(ui::KeyboardCode::VKEY_A, 0);
+
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+}
+
 TEST_F(LockContentsViewKeyboardUnitTest, SwitchPinAndVirtualKeyboard) {
   ASSERT_NO_FATAL_FAILURE(ShowLockScreen());
   LockContentsView* contents =
diff --git a/ash/login/ui/lock_debug_view.cc b/ash/login/ui/lock_debug_view.cc
index 12bef5f1..cd1b1b24 100644
--- a/ash/login/ui/lock_debug_view.cc
+++ b/ash/login/ui/lock_debug_view.cc
@@ -6,7 +6,7 @@
 
 #include <algorithm>
 #include <memory>
-#include <string>
+#include <utility>
 
 #include "ash/ime/ime_controller.h"
 #include "ash/login/login_screen_controller.h"
@@ -14,6 +14,7 @@
 #include "ash/login/ui/lock_contents_view.h"
 #include "ash/login/ui/lock_screen.h"
 #include "ash/login/ui/login_data_dispatcher.h"
+#include "ash/login/ui/login_detachable_base_model.h"
 #include "ash/login/ui/non_accessible_view.h"
 #include "ash/shell.h"
 #include "base/strings/utf_string_conversions.h"
@@ -232,6 +233,10 @@
       const mojom::EasyUnlockIconOptionsPtr& icon) override {
     debug_dispatcher_.ShowEasyUnlockIcon(user, icon);
   }
+  void OnDetachableBasePairingStatusChanged(
+      DetachableBasePairingStatus pairing_status) override {
+    debug_dispatcher_.SetDetachableBasePairingStatus(pairing_status);
+  }
 
  private:
   // The debug overlay UI takes ground-truth data from |root_dispatcher_|,
@@ -252,8 +257,10 @@
   DISALLOW_COPY_AND_ASSIGN(DebugDataDispatcherTransformer);
 };
 
-LockDebugView::LockDebugView(mojom::TrayActionState initial_note_action_state,
-                             LoginDataDispatcher* data_dispatcher)
+LockDebugView::LockDebugView(
+    mojom::TrayActionState initial_note_action_state,
+    LoginDataDispatcher* data_dispatcher,
+    std::unique_ptr<LoginDetachableBaseModel> detachable_base_model)
     : debug_data_dispatcher_(std::make_unique<DebugDataDispatcherTransformer>(
           initial_note_action_state,
           data_dispatcher)) {
@@ -261,7 +268,8 @@
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
 
   lock_ = new LockContentsView(initial_note_action_state,
-                               debug_data_dispatcher_->debug_dispatcher());
+                               debug_data_dispatcher_->debug_dispatcher(),
+                               std::move(detachable_base_model));
   AddChildView(lock_);
 
   debug_row_ = new NonAccessibleView();
@@ -326,7 +334,7 @@
   // Iteratively adds more info to the dev channel labels to test 7 permutations
   // and then disables the button.
   if (sender == add_dev_channel_info_) {
-    DCHECK(num_dev_channel_info_clicks_ < 7u);
+    DCHECK_LT(num_dev_channel_info_clicks_, 7u);
     ++num_dev_channel_info_clicks_;
     if (num_dev_channel_info_clicks_ == 7u)
       add_dev_channel_info_->SetEnabled(false);
diff --git a/ash/login/ui/lock_debug_view.h b/ash/login/ui/lock_debug_view.h
index b27b9c333..74acbe2f 100644
--- a/ash/login/ui/lock_debug_view.h
+++ b/ash/login/ui/lock_debug_view.h
@@ -6,8 +6,10 @@
 #define ASH_LOGIN_UI_LOCK_DEBUG_VIEW_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
+#include "ash/detachable_base/detachable_base_pairing_status.h"
 #include "ash/login/login_screen_controller.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/view.h"
@@ -19,6 +21,7 @@
 namespace ash {
 
 class LoginDataDispatcher;
+class LoginDetachableBaseModel;
 class LockContentsView;
 
 namespace mojom {
@@ -28,8 +31,10 @@
 // Contains the debug UI row (ie, add user, toggle PIN buttons).
 class LockDebugView : public views::View, public views::ButtonListener {
  public:
-  LockDebugView(mojom::TrayActionState initial_note_action_state,
-                LoginDataDispatcher* data_dispatcher);
+  LockDebugView(
+      mojom::TrayActionState initial_note_action_state,
+      LoginDataDispatcher* data_dispatcher,
+      std::unique_ptr<LoginDetachableBaseModel> detachable_base_model);
   ~LockDebugView() override;
 
   // views::View:
diff --git a/ash/login/ui/lock_screen.cc b/ash/login/ui/lock_screen.cc
index 322007d4..7d6e8d2 100644
--- a/ash/login/ui/lock_screen.cc
+++ b/ash/login/ui/lock_screen.cc
@@ -11,6 +11,7 @@
 #include "ash/login/ui/lock_debug_view.h"
 #include "ash/login/ui/lock_window.h"
 #include "ash/login/ui/login_data_dispatcher.h"
+#include "ash/login/ui/login_detachable_base_model.h"
 #include "ash/public/cpp/login_constants.h"
 #include "ash/public/interfaces/session_controller.mojom.h"
 #include "ash/root_window_controller.h"
@@ -72,17 +73,21 @@
       display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
 
   auto data_dispatcher = std::make_unique<LoginDataDispatcher>();
+  auto detachable_base_model = LoginDetachableBaseModel::Create(
+      Shell::Get()->detachable_base_handler(), data_dispatcher.get());
   auto initial_note_action_state =
-      ash::Shell::Get()->tray_action()->GetLockScreenNoteState();
+      Shell::Get()->tray_action()->GetLockScreenNoteState();
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           chromeos::switches::kShowLoginDevOverlay)) {
     auto* debug_view =
-        new LockDebugView(initial_note_action_state, data_dispatcher.get());
+        new LockDebugView(initial_note_action_state, data_dispatcher.get(),
+                          std::move(detachable_base_model));
     instance_->contents_view_ = debug_view->lock();
     instance_->window_->SetContentsView(debug_view);
   } else {
     instance_->contents_view_ =
-        new LockContentsView(initial_note_action_state, data_dispatcher.get());
+        new LockContentsView(initial_note_action_state, data_dispatcher.get(),
+                             std::move(detachable_base_model));
     instance_->window_->SetContentsView(instance_->contents_view_);
   }
 
diff --git a/ash/login/ui/lock_screen_sanity_unittest.cc b/ash/login/ui/lock_screen_sanity_unittest.cc
index 91a5de38..bcb5723f 100644
--- a/ash/login/ui/lock_screen_sanity_unittest.cc
+++ b/ash/login/ui/lock_screen_sanity_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/login/login_screen_controller.h"
 #include "ash/login/mock_login_screen_client.h"
+#include "ash/login/ui/fake_login_detachable_base_model.h"
 #include "ash/login/ui/lock_contents_view.h"
 #include "ash/login/ui/login_test_base.h"
 #include "ash/login/ui/login_test_utils.h"
@@ -102,8 +103,9 @@
 // Verifies that the password input box has focus.
 TEST_F(LockScreenSanityTest, PasswordIsInitiallyFocused) {
   // Build lock screen.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
 
   // The lock screen requires at least one user.
   SetUserCount(1);
@@ -118,8 +120,9 @@
 // Verifies submitting the password invokes mojo lock screen client.
 TEST_F(LockScreenSanityTest, PasswordSubmitCallsLoginScreenClient) {
   // Build lock screen.
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
 
   // The lock screen requires at least one user.
   SetUserCount(1);
@@ -144,8 +147,9 @@
        PasswordSubmitClearsPasswordAfterFailedAuthentication) {
   std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
 
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LoginPasswordView::TestApi password_test_api =
@@ -197,8 +201,9 @@
       session_manager::SessionState::LOCKED);
 
   // Create lock screen.
-  auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                    data_dispatcher());
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
   views::View* shelf = Shelf::ForWindow(lock->GetWidget()->GetNativeWindow())
@@ -228,8 +233,9 @@
   GetSessionControllerClient()->SetSessionState(
       session_manager::SessionState::LOCKED);
 
-  auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                    data_dispatcher());
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
   views::View* status_area =
@@ -258,8 +264,9 @@
   GetSessionControllerClient()->SetSessionState(
       session_manager::SessionState::LOCKED);
 
-  auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                    data_dispatcher());
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
 
@@ -329,8 +336,9 @@
   // Set up lock screen.
   GetSessionControllerClient()->SetSessionState(
       session_manager::SessionState::LOCKED);
-  auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                    data_dispatcher());
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
 
diff --git a/ash/login/ui/login_bubble.cc b/ash/login/ui/login_bubble.cc
index e0f1674f..50867ed 100644
--- a/ash/login/ui/login_bubble.cc
+++ b/ash/login/ui/login_bubble.cc
@@ -4,6 +4,9 @@
 
 #include "ash/login/ui/login_bubble.h"
 
+#include <memory>
+#include <utility>
+
 #include "ash/ash_constants.h"
 #include "ash/focus_cycler.h"
 #include "ash/login/ui/layout_util.h"
@@ -86,7 +89,7 @@
 
 class LoginErrorBubbleView : public LoginBaseBubbleView {
  public:
-  LoginErrorBubbleView(views::StyledLabel* label, views::View* anchor_view)
+  LoginErrorBubbleView(views::View* content, views::View* anchor_view)
       : LoginBaseBubbleView(anchor_view) {
     SetLayoutManager(std::make_unique<views::BoxLayout>(
         views::BoxLayout::kVertical, gfx::Insets(),
@@ -104,8 +107,7 @@
     alert_view->AddChildView(alert_icon);
     AddChildView(alert_view);
 
-    label->set_auto_color_readability_enabled(false);
-    AddChildView(label);
+    AddChildView(content);
   }
 
   ~LoginErrorBubbleView() override = default;
@@ -361,12 +363,15 @@
   }
 }
 
-void LoginBubble::ShowErrorBubble(views::StyledLabel* label,
-                                  views::View* anchor_view) {
+void LoginBubble::ShowErrorBubble(views::View* content,
+                                  views::View* anchor_view,
+                                  uint32_t flags) {
   if (bubble_view_)
     CloseImmediately();
 
-  bubble_view_ = new LoginErrorBubbleView(label, anchor_view);
+  flags_ = flags;
+  bubble_view_ = new LoginErrorBubbleView(content, anchor_view);
+
   Show();
 }
 
@@ -381,6 +386,7 @@
   if (bubble_view_)
     CloseImmediately();
 
+  flags_ = kFlagsNone;
   bubble_opener_ = bubble_opener;
   bubble_view_ =
       new LoginUserMenuView(this, username, email, type, is_owner, anchor_view,
@@ -400,6 +406,7 @@
   if (bubble_view_)
     CloseImmediately();
 
+  flags_ = kFlagsNone;
   bubble_view_ = new LoginTooltipView(message, anchor_view);
   Show();
 }
@@ -415,6 +422,7 @@
 void LoginBubble::OnWidgetClosing(views::Widget* widget) {
   bubble_opener_ = nullptr;
   bubble_view_ = nullptr;
+  flags_ = kFlagsNone;
   widget->RemoveObserver(this);
 }
 
@@ -447,7 +455,9 @@
   if (bubble_view_->GetWidget()->IsActive())
     return;
 
-  Close();
+  if (!(flags_ & kFlagPersistent)) {
+    Close();
+  }
 }
 
 void LoginBubble::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) {
@@ -461,9 +471,10 @@
 
 void LoginBubble::Show() {
   DCHECK(bubble_view_);
-  views::BubbleDialogDelegateView::CreateBubble(bubble_view_)->Show();
+  views::BubbleDialogDelegateView::CreateBubble(bubble_view_)->ShowInactive();
   bubble_view_->SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
   bubble_view_->GetWidget()->AddObserver(this);
+  bubble_view_->GetWidget()->StackAtTop();
 
   ScheduleAnimation(true /*visible*/);
 
@@ -498,7 +509,8 @@
       return;
   }
 
-  Close();
+  if (!(flags_ & kFlagPersistent))
+    Close();
 }
 
 void LoginBubble::ScheduleAnimation(bool visible) {
diff --git a/ash/login/ui/login_bubble.h b/ash/login/ui/login_bubble.h
index b756dab1..52be89c 100644
--- a/ash/login/ui/login_bubble.h
+++ b/ash/login/ui/login_bubble.h
@@ -13,10 +13,6 @@
 #include "ui/views/view.h"
 #include "ui/views/widget/widget_observer.h"
 
-namespace views {
-class StyledLabel;
-}
-
 namespace ash {
 class LoginButton;
 
@@ -29,12 +25,20 @@
  public:
   static const int kUserMenuRemoveUserButtonIdForTest;
 
+  // Flags passed to ShowErrorBubble().
+  static constexpr uint32_t kFlagsNone = 0;
+  // If set, the shown error bubble will not be closed due to an unrelated user
+  // action - e.g. the bubble will not be closed if the user starts typing.
+  static constexpr uint32_t kFlagPersistent = 1 << 0;
+
   LoginBubble();
   ~LoginBubble() override;
 
   // Shows an error bubble for authentication failure.
   // |anchor_view| is the anchor for placing the bubble view.
-  void ShowErrorBubble(views::StyledLabel* label, views::View* anchor_view);
+  void ShowErrorBubble(views::View* content,
+                       views::View* anchor_view,
+                       uint32_t flags);
 
   // Shows a user menu bubble.
   // |anchor_view| is the anchor for placing the bubble view.
@@ -91,6 +95,9 @@
   // Starts show/hide animation.
   void ScheduleAnimation(bool visible);
 
+  // Flags passed to ShowErrorBubble().
+  uint32_t flags_ = kFlagsNone;
+
   LoginBaseBubbleView* bubble_view_ = nullptr;
 
   // A button that could open/close the bubble.
diff --git a/ash/login/ui/login_bubble_unittest.cc b/ash/login/ui/login_bubble_unittest.cc
index 980a3c76..cbf07da7 100644
--- a/ash/login/ui/login_bubble_unittest.cc
+++ b/ash/login/ui/login_bubble_unittest.cc
@@ -3,13 +3,16 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <utility>
 
 #include "ash/login/ui/login_bubble.h"
 #include "ash/login/ui/login_button.h"
 #include "ash/login/ui/login_test_base.h"
+#include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/widget/widget.h"
 
@@ -236,4 +239,95 @@
   EXPECT_TRUE(remove_called);
 }
 
+TEST_F(LoginBubbleTest, ErrorBubbleKeyEventHandling) {
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(bubble_->IsVisible());
+  views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
+  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that key event on a view other than error closes the error bubble.
+  other_view_->RequestFocus();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
+  EXPECT_FALSE(bubble_->IsVisible());
+}
+
+TEST_F(LoginBubbleTest, ErrorBubbleMouseEventHandling) {
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(bubble_->IsVisible());
+  views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
+  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that mouse event on the bubble itself won't close the bubble.
+  generator.MoveMouseTo(
+      bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that mouse event on the other view will close the bubble.
+  generator.MoveMouseTo(other_view_->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+  EXPECT_FALSE(bubble_->IsVisible());
+}
+
+TEST_F(LoginBubbleTest, ErrorBubbleGestureEventHandling) {
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(bubble_->IsVisible());
+  views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
+  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that gesture event on the bubble itself won't close the bubble.
+  generator.GestureTapAt(
+      bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that gesture event on the other view will close the bubble.
+  generator.GestureTapAt(other_view_->GetBoundsInScreen().CenterPoint());
+  EXPECT_FALSE(bubble_->IsVisible());
+}
+
+TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) {
+  ui::test::EventGenerator& generator = GetEventGenerator();
+
+  EXPECT_FALSE(bubble_->IsVisible());
+  views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
+  bubble_->ShowErrorBubble(error_text, container_,
+                           LoginBubble::kFlagPersistent);
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that mouse event on the bubble itself won't close the bubble.
+  generator.MoveMouseTo(
+      bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that mouse event on the other view won't close the bubble.
+  generator.MoveMouseTo(other_view_->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that gesture event on the bubble itself won't close the bubble.
+  generator.GestureTapAt(
+      bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that gesture event on the other view won't close the bubble.
+  generator.GestureTapAt(other_view_->GetBoundsInScreen().CenterPoint());
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // Verifies that key event on the other view won't close the bubble.
+  other_view_->RequestFocus();
+  generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
+  EXPECT_TRUE(bubble_->IsVisible());
+
+  // LoginBubble::Close should close the persistent error bubble.
+  bubble_->Close();
+  EXPECT_FALSE(bubble_->IsVisible());
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_data_dispatcher.cc b/ash/login/ui/login_data_dispatcher.cc
index f280408c..c1177fe 100644
--- a/ash/login/ui/login_data_dispatcher.cc
+++ b/ash/login/ui/login_data_dispatcher.cc
@@ -41,6 +41,9 @@
     const std::string& default_locale,
     bool show_advanced_view) {}
 
+void LoginDataDispatcher::Observer::OnDetachableBasePairingStatusChanged(
+    DetachableBasePairingStatus pairing_status) {}
+
 LoginDataDispatcher::LoginDataDispatcher() = default;
 
 LoginDataDispatcher::~LoginDataDispatcher() = default;
@@ -111,4 +114,10 @@
   }
 }
 
+void LoginDataDispatcher::SetDetachableBasePairingStatus(
+    DetachableBasePairingStatus pairing_status) {
+  for (auto& observer : observers_)
+    observer.OnDetachableBasePairingStatusChanged(pairing_status);
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_data_dispatcher.h b/ash/login/ui/login_data_dispatcher.h
index 4fef1be..4155560 100644
--- a/ash/login/ui/login_data_dispatcher.h
+++ b/ash/login/ui/login_data_dispatcher.h
@@ -5,9 +5,12 @@
 #ifndef ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_
 #define ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_
 
+#include <memory>
+#include <string>
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "ash/detachable_base/detachable_base_pairing_status.h"
 #include "ash/public/interfaces/login_user_info.mojom.h"
 #include "ash/public/interfaces/tray_action.mojom.h"
 #include "base/macros.h"
@@ -77,6 +80,11 @@
         const base::ListValue& locales,
         const std::string& default_locale,
         bool show_advanced_view);
+
+    // Called when the pairing status of detachable base changes - e.g. when the
+    // base is attached or detached.
+    virtual void OnDetachableBasePairingStatusChanged(
+        DetachableBasePairingStatus pairing_status);
   };
 
   LoginDataDispatcher();
@@ -100,6 +108,8 @@
                                std::unique_ptr<base::ListValue> locales,
                                const std::string& default_locale,
                                bool show_advanced_view);
+  void SetDetachableBasePairingStatus(
+      DetachableBasePairingStatus pairing_status);
 
  private:
   base::ObserverList<Observer> observers_;
diff --git a/ash/login/ui/login_detachable_base_model.cc b/ash/login/ui/login_detachable_base_model.cc
new file mode 100644
index 0000000..a1fc69b
--- /dev/null
+++ b/ash/login/ui/login_detachable_base_model.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/login/ui/login_detachable_base_model.h"
+
+#include "ash/detachable_base/detachable_base_handler.h"
+#include "ash/detachable_base/detachable_base_observer.h"
+#include "ash/detachable_base/detachable_base_pairing_status.h"
+#include "ash/login/ui/login_data_dispatcher.h"
+#include "ash/public/interfaces/user_info.mojom.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+
+namespace ash {
+
+namespace {
+
+class LoginDetachableBaseModelImpl : public LoginDetachableBaseModel,
+                                     public DetachableBaseObserver {
+ public:
+  LoginDetachableBaseModelImpl(DetachableBaseHandler* detachable_base_handler,
+                               LoginDataDispatcher* login_data_dispatcher)
+      : detachable_base_handler_(detachable_base_handler),
+        detachable_base_observer_(this),
+        login_data_dispatcher_(login_data_dispatcher) {
+    detachable_base_observer_.Add(detachable_base_handler);
+  }
+
+  ~LoginDetachableBaseModelImpl() override = default;
+
+  // LoginDetachableBaseModel:
+  DetachableBasePairingStatus GetPairingStatus() override {
+    return detachable_base_handler_->GetPairingStatus();
+  }
+  bool PairedBaseMatchesLastUsedByUser(
+      const mojom::UserInfo& user_info) override {
+    return detachable_base_handler_->PairedBaseMatchesLastUsedByUser(user_info);
+  }
+  bool SetPairedBaseAsLastUsedByUser(
+      const mojom::UserInfo& user_info) override {
+    return detachable_base_handler_->SetPairedBaseAsLastUsedByUser(user_info);
+  }
+
+  // DetachableBaseObserver:
+  void OnDetachableBasePairingStatusChanged(
+      DetachableBasePairingStatus pairing_status) override {
+    login_data_dispatcher_->SetDetachableBasePairingStatus(pairing_status);
+  }
+
+ private:
+  DetachableBaseHandler* detachable_base_handler_;
+  ScopedObserver<DetachableBaseHandler, DetachableBaseObserver>
+      detachable_base_observer_;
+  LoginDataDispatcher* login_data_dispatcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(LoginDetachableBaseModelImpl);
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<LoginDetachableBaseModel> LoginDetachableBaseModel::Create(
+    DetachableBaseHandler* detachable_base_handler,
+    LoginDataDispatcher* login_data_dispatcher) {
+  return std::make_unique<LoginDetachableBaseModelImpl>(detachable_base_handler,
+                                                        login_data_dispatcher);
+}
+
+}  // namespace ash
diff --git a/ash/login/ui/login_detachable_base_model.h b/ash/login/ui/login_detachable_base_model.h
new file mode 100644
index 0000000..e21b0807
--- /dev/null
+++ b/ash/login/ui/login_detachable_base_model.h
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
+#define ASH_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class DetachableBaseHandler;
+enum class DetachableBasePairingStatus;
+class LoginDataDispatcher;
+
+namespace mojom {
+class UserInfo;
+}
+
+// Wrapper around ash::DetachableBaseHandler used by login UI. Exposed as an
+// interface to ease faking the detachable base state in login UI tests, and in
+// debug login view.
+//
+// It observes the detachable base pairing status, and informs login data
+// dispatcher of pairing status changes.
+// It provides methods for comparing the current base to the last based used by
+// a user, and setting the last base used by the user.
+class ASH_EXPORT LoginDetachableBaseModel {
+ public:
+  virtual ~LoginDetachableBaseModel() = default;
+
+  static std::unique_ptr<LoginDetachableBaseModel> Create(
+      DetachableBaseHandler* detachable_base_handler,
+      LoginDataDispatcher* login_data_dispatcher);
+
+  // Returns the current detachable base pairing status.
+  virtual DetachableBasePairingStatus GetPairingStatus() = 0;
+
+  // Checks if the currently paired base is different than the last base used by
+  // the user.
+  virtual bool PairedBaseMatchesLastUsedByUser(
+      const mojom::UserInfo& user_info) = 0;
+
+  // Sets the currently paired base as the last base used by the user.
+  virtual bool SetPairedBaseAsLastUsedByUser(
+      const mojom::UserInfo& user_info) = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
diff --git a/ash/metrics/login_metrics_recorder_unittest.cc b/ash/metrics/login_metrics_recorder_unittest.cc
index 4c845cdc..ff437773 100644
--- a/ash/metrics/login_metrics_recorder_unittest.cc
+++ b/ash/metrics/login_metrics_recorder_unittest.cc
@@ -4,8 +4,12 @@
 
 #include "ash/metrics/login_metrics_recorder.h"
 
+#include <memory>
+#include <string>
+
 #include "ash/login/login_screen_controller.h"
 #include "ash/login/mock_login_screen_client.h"
+#include "ash/login/ui/fake_login_detachable_base_model.h"
 #include "ash/login/ui/lock_contents_view.h"
 #include "ash/login/ui/lock_screen.h"
 #include "ash/login/ui/login_auth_user_view.h"
@@ -106,8 +110,9 @@
 
   std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
   client->set_authenticate_user_callback_result(false);
-  auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   LockContentsView::TestApi test_api(contents);
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
@@ -184,8 +189,9 @@
   GetSessionControllerClient()->SetSessionState(
       session_manager::SessionState::LOCKED);
 
-  auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable,
-                                        data_dispatcher());
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 882c29f..6310e535 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -19,6 +19,9 @@
 const base::Feature kNewOverviewAnimations{"NewOverviewAnimations",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kPersistentWindowBounds{"PersistentWindowBounds",
+                                            base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kSystemTrayUnified{"SystemTrayUnified",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -45,6 +48,10 @@
   return base::FeatureList::IsEnabled(kSystemTrayUnified);
 }
 
+bool IsPersistentWindowBoundsEnabled() {
+  return base::FeatureList::IsEnabled(kPersistentWindowBounds);
+}
+
 bool IsLockScreenNotificationsEnabled() {
   return base::FeatureList::IsEnabled(kLockScreenNotifications);
 }
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index 1551c70..6c2e714 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -31,6 +31,11 @@
 // https://crbug.com/801465.
 ASH_PUBLIC_EXPORT extern const base::Feature kNewOverviewAnimations;
 
+// Enables persistent window bounds in multi-displays scenario.
+// TODO(warx): Remove this after the feature is fully launched.
+// https://crbug.com/805046.
+ASH_PUBLIC_EXPORT extern const base::Feature kPersistentWindowBounds;
+
 // Enables new system menu.
 ASH_PUBLIC_EXPORT extern const base::Feature kSystemTrayUnified;
 
@@ -45,6 +50,8 @@
 
 ASH_PUBLIC_EXPORT bool IsNewOverviewAnimationsEnabled();
 
+ASH_PUBLIC_EXPORT bool IsPersistentWindowBoundsEnabled();
+
 ASH_PUBLIC_EXPORT bool IsSystemTrayUnifiedEnabled();
 
 ASH_PUBLIC_EXPORT bool IsLockScreenNotificationsEnabled();
diff --git a/ash/public/cpp/ash_pref_names.h b/ash/public/cpp/ash_pref_names.h
index 1be5a0f..b91c5b5 100644
--- a/ash/public/cpp/ash_pref_names.h
+++ b/ash/public/cpp/ash_pref_names.h
@@ -67,6 +67,8 @@
 
 ASH_PUBLIC_EXPORT extern const char kDetachableBaseDevices[];
 
+ASH_PUBLIC_EXPORT extern const char kCursorMotionBlurEnabled[];
+
 }  // namespace prefs
 
 }  // namespace ash
diff --git a/ash/public/cpp/ash_switches.cc b/ash/public/cpp/ash_switches.cc
index 5e38cf60..d30ca17 100644
--- a/ash/public/cpp/ash_switches.cc
+++ b/ash/public/cpp/ash_switches.cc
@@ -41,6 +41,9 @@
 // TODO(oshima): Remove this once the feature is launched. crbug.com/749713.
 const char kAshEnableV1AppBackButton[] = "ash-enable-v1-app-back-button";
 
+// Enable cursor motion blur.
+const char kAshEnableCursorMotionBlur[] = "ash-enable-cursor-motion-blur";
+
 // Enables key bindings to scroll magnified screen.
 const char kAshEnableMagnifierKeyScroller[] =
     "ash-enable-magnifier-key-scroller";
@@ -56,11 +59,6 @@
 const char kAshEnablePaletteOnAllDisplays[] =
     "ash-enable-palette-on-all-displays";
 
-// Enables persistent window bounds in multi-displays scenario.
-// TODO(warx): Remove this once the feature is launched. crbug.com/805046.
-const char kAshEnablePersistentWindowBounds[] =
-    "ash-enable-persistent-window-bounds";
-
 // Enables the sidebar.
 const char kAshSidebarEnabled[] = "enable-ash-sidebar";
 const char kAshSidebarDisabled[] = "disable-ash-sidebar";
diff --git a/ash/public/cpp/ash_switches.h b/ash/public/cpp/ash_switches.h
index 0f3493b..f9fc18f 100644
--- a/ash/public/cpp/ash_switches.h
+++ b/ash/public/cpp/ash_switches.h
@@ -24,12 +24,12 @@
 ASH_PUBLIC_EXPORT extern const char kAshDisableTabletSplitView[];
 ASH_PUBLIC_EXPORT extern const char kAshDisableTrilinearFiltering[];
 ASH_PUBLIC_EXPORT extern const char kAshDisableTouchExplorationMode[];
+ASH_PUBLIC_EXPORT extern const char kAshEnableCursorMotionBlur[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableV1AppBackButton[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableMagnifierKeyScroller[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableNewOverviewUi[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableNightLight[];
 ASH_PUBLIC_EXPORT extern const char kAshEnablePaletteOnAllDisplays[];
-ASH_PUBLIC_EXPORT extern const char kAshEnablePersistentWindowBounds[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableScaleSettingsTray[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableTabletMode[];
 ASH_PUBLIC_EXPORT extern const char kAshEnableWaylandServer[];
diff --git a/ash/shell.cc b/ash/shell.cc
index 14f1138..7c37a35 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -1142,6 +1142,8 @@
     cursor_manager_->SetCursor(ui::CursorType::kPointer);
   }
 
+  UpdateCursorCompositingEnabled();
+
   peripheral_battery_notifier_ = std::make_unique<PeripheralBatteryNotifier>();
   power_event_observer_.reset(new PowerEventObserver());
   user_activity_notifier_.reset(
diff --git a/ash/system/power/power_button_controller.cc b/ash/system/power/power_button_controller.cc
index 9b11e70b..9fc4ca4 100644
--- a/ash/system/power/power_button_controller.cc
+++ b/ash/system/power/power_button_controller.cc
@@ -30,10 +30,13 @@
 namespace ash {
 namespace {
 
-// Time that power button should be pressed before starting to show the power
-// button menu animation.
-constexpr base::TimeDelta kStartPowerButtonMenuAnimationTimeout =
+// Amount of time power button must be held to start the power menu animation
+// for convertible/slate/detachable devices. This differs depending on whether
+// the screen is on or off when the power button is initially pressed.
+constexpr base::TimeDelta kShowMenuWhenScreenOnTimeout =
     base::TimeDelta::FromMilliseconds(500);
+constexpr base::TimeDelta kShowMenuWhenScreenOffTimeout =
+    base::TimeDelta::FromMilliseconds(2000);
 
 // Time that power button should be pressed before starting to shutdown.
 constexpr base::TimeDelta kStartShutdownTimeout =
@@ -136,6 +139,7 @@
   }
 
   if (down) {
+    show_menu_animation_done_ = false;
     if (turn_screen_off_for_tap_) {
       force_off_on_button_up_ = true;
 
@@ -160,9 +164,17 @@
     screen_off_when_power_button_down_ = !display_controller_->IsScreenOn();
     display_controller_->SetBacklightsForcedOff(false);
 
-    power_button_menu_timer_.Start(
-        FROM_HERE, kStartPowerButtonMenuAnimationTimeout, this,
-        &PowerButtonController::OnPowerButtonMenuTimeout);
+    if (!turn_screen_off_for_tap_) {
+      StartPowerMenuAnimation();
+    } else {
+      base::TimeDelta timeout = screen_off_when_power_button_down_
+                                    ? kShowMenuWhenScreenOffTimeout
+                                    : kShowMenuWhenScreenOnTimeout;
+
+      power_button_menu_timer_.Start(
+          FROM_HERE, timeout, this,
+          &PowerButtonController::StartPowerMenuAnimation);
+    }
 
     shutdown_timer_.Start(FROM_HERE, kStartShutdownTimeout, this,
                           &PowerButtonController::OnShutdownTimeout);
@@ -183,6 +195,13 @@
       display_controller_->SetBacklightsForcedOff(true);
       LockScreenIfRequired();
     }
+
+    // Cancel the menu animation if it's still ongoing when the button is
+    // released on a clamshell device.
+    if (!turn_screen_off_for_tap_ && !show_menu_animation_done_) {
+      static_cast<PowerButtonMenuScreenView*>(menu_widget_->GetContentsView())
+          ->ScheduleShowHideAnimation(false);
+    }
   }
 }
 
@@ -305,7 +324,8 @@
 
 void PowerButtonController::OnScreenStateChanged(
     BacklightsForcedOffSetter::ScreenState screen_state) {
-  DismissMenu();
+  if (screen_state != BacklightsForcedOffSetter::ScreenState::ON)
+    DismissMenu();
 }
 
 void PowerButtonController::OnTabletModeStarted() {
@@ -332,10 +352,12 @@
   DismissMenu();
 }
 
-void PowerButtonController::OnPowerButtonMenuTimeout() {
+void PowerButtonController::StartPowerMenuAnimation() {
   if (!menu_widget_)
     menu_widget_ = CreateMenuWidget();
-  menu_widget_->SetContentsView(new PowerButtonMenuScreenView());
+  menu_widget_->SetContentsView(new PowerButtonMenuScreenView(
+      base::BindRepeating(&PowerButtonController::SetShowMenuAnimationDone,
+                          base::Unretained(this))));
   menu_widget_->Show();
 
   // Hide cursor, but let it reappear if the mouse moves.
@@ -383,4 +405,8 @@
   }
 }
 
+void PowerButtonController::SetShowMenuAnimationDone() {
+  show_menu_animation_done_ = true;
+}
+
 }  // namespace ash
diff --git a/ash/system/power/power_button_controller.h b/ash/system/power/power_button_controller.h
index 8a684a4..63c365bd 100644
--- a/ash/system/power/power_button_controller.h
+++ b/ash/system/power/power_button_controller.h
@@ -135,8 +135,9 @@
   // button menu.
   void StopTimersAndDismissMenu();
 
-  // Called by |power_button_menu_timer_| to start showing power button menu.
-  void OnPowerButtonMenuTimeout();
+  // Starts the power menu animation. Called when a clamshell device's power
+  // button is pressed or when |power_button_menu_timer_| fires.
+  void StartPowerMenuAnimation();
 
   // Called by |shutdown_timer_| to turn the screen off and request shutdown.
   void OnShutdownTimeout();
@@ -153,6 +154,9 @@
   // set and locking is possible.
   void LockScreenIfRequired();
 
+  // Sets |show_menu_animation_done_| to true.
+  void SetShowMenuAnimationDone();
+
   // Are the power or lock buttons currently held?
   bool power_button_down_ = false;
   bool lock_button_down_ = false;
@@ -165,6 +169,9 @@
   // external display is connected).
   bool internal_display_off_and_external_display_on_ = false;
 
+  // True after the animation that shows the power menu has finished.
+  bool show_menu_animation_done_ = false;
+
   // Saves the button type for this power button.
   ButtonType button_type_ = ButtonType::NORMAL;
 
@@ -213,8 +220,9 @@
   // Runs OnShutdownTimeout() to start shutdown.
   base::OneShotTimer shutdown_timer_;
 
-  // Started when the power button is pressed and stopped when it's released.
-  // Runs OnPowerButtonMenuTimeout() to show the power button menu.
+  // Started when the power button of convertible/slate/detachable devices is
+  // pressed and stopped when it's released. Runs StartPowerMenuAnimation() to
+  // show the power button menu.
   base::OneShotTimer power_button_menu_timer_;
 
   // The fullscreen widget of power button menu.
diff --git a/ash/system/power/power_button_controller_test_api.cc b/ash/system/power/power_button_controller_test_api.cc
index 0ebad831..e50c320 100644
--- a/ash/system/power/power_button_controller_test_api.cc
+++ b/ash/system/power/power_button_controller_test_api.cc
@@ -102,4 +102,9 @@
   controller_->turn_screen_off_for_tap_ = turn_screen_off_for_tap;
 }
 
+void PowerButtonControllerTestApi::SetShowMenuAnimationDone(
+    bool show_menu_animation_done) {
+  controller_->show_menu_animation_done_ = show_menu_animation_done;
+}
+
 }  // namespace ash
diff --git a/ash/system/power/power_button_controller_test_api.h b/ash/system/power/power_button_controller_test_api.h
index ed9680a..8c07d08 100644
--- a/ash/system/power/power_button_controller_test_api.h
+++ b/ash/system/power/power_button_controller_test_api.h
@@ -72,6 +72,8 @@
 
   void SetTurnScreenOffForTap(bool turn_screen_off_for_tap);
 
+  void SetShowMenuAnimationDone(bool show_menu_animation_done);
+
  private:
   PowerButtonController* controller_;  // Not owned.
 
diff --git a/ash/system/power/power_button_controller_unittest.cc b/ash/system/power/power_button_controller_unittest.cc
index 4d3b6e4f..db46e90 100644
--- a/ash/system/power/power_button_controller_unittest.cc
+++ b/ash/system/power/power_button_controller_unittest.cc
@@ -97,7 +97,6 @@
   void TappingPowerButtonWhenScreenIsIdleOff() {
     SendBrightnessChange(0, kUserCause);
     PressPowerButton();
-    EXPECT_TRUE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
     EXPECT_FALSE(power_manager_client_->backlights_forced_off());
     SendBrightnessChange(kNonZeroBrightness, kUserCause);
     ReleasePowerButton();
@@ -168,12 +167,34 @@
   EXPECT_FALSE(turn_screen_off_for_tap_);
   EXPECT_FALSE(power_manager_client_->backlights_forced_off());
   PressPowerButton();
+  power_button_test_api_->SetShowMenuAnimationDone(false);
+  // Start the showing power menu animation immediately as pressing the
+  // clamshell power button.
+  EXPECT_FALSE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
   ReleasePowerButton();
   EXPECT_FALSE(power_manager_client_->backlights_forced_off());
+  // Start the dimissing power menu animation immediately as releasing the
+  // clamsehll power button if showing animation hasn't finished.
+  EXPECT_FALSE(power_button_test_api_->IsMenuOpened());
 
   AdvanceClockToAvoidIgnoring();
   // Should turn screen on if screen is off.
   TappingPowerButtonWhenScreenIsIdleOff();
+
+  AdvanceClockToAvoidIgnoring();
+  // Should not start the dismissing menu animation if showing menu animation
+  // has finished.
+  PressPowerButton();
+  // Start the showing power menu animation immediately as pressing the
+  // clamshell power button.
+  EXPECT_FALSE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
+  power_button_test_api_->SetShowMenuAnimationDone(true);
+  ReleasePowerButton();
+  EXPECT_FALSE(power_manager_client_->backlights_forced_off());
+  // Power button menu should keep opened if showing animation has finished.
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
 }
 
 // Tests that tapping power button of a device that has tablet mode switch.
@@ -182,16 +203,31 @@
   // shown.
   EXPECT_TRUE(turn_screen_off_for_tap_);
   PressPowerButton();
+  // Showing power menu animation hasn't started as power menu timer is running.
   EXPECT_TRUE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
   EXPECT_FALSE(power_manager_client_->backlights_forced_off());
+  EXPECT_FALSE(power_button_test_api_->IsMenuOpened());
   ReleasePowerButton();
   EXPECT_FALSE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
   EXPECT_TRUE(power_manager_client_->backlights_forced_off());
+  EXPECT_FALSE(power_button_test_api_->IsMenuOpened());
 
   // Should turn screen on if screen is off.
   AdvanceClockToAvoidIgnoring();
   TappingPowerButtonWhenScreenIsIdleOff();
 
+  // Showing power menu animation should start until power menu timer is
+  // timeout.
+  PressPowerButton();
+  power_button_test_api_->SetShowMenuAnimationDone(false);
+  EXPECT_TRUE(power_button_test_api_->TriggerPowerButtonMenuTimeout());
+  EXPECT_FALSE(power_button_test_api_->PowerButtonMenuTimerIsRunning());
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
+  ReleasePowerButton();
+  // Showing animation will continue until show the power button menu even
+  // release the power button.
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
+
   // Should not turn screen off if clamshell-like power button behavior is
   // requested.
   ForceClamshellPowerButton();
@@ -200,8 +236,15 @@
   EXPECT_FALSE(turn_screen_off_for_tap_);
   EXPECT_FALSE(power_manager_client_->backlights_forced_off());
   PressPowerButton();
+  power_button_test_api_->SetShowMenuAnimationDone(false);
+  // Forced clamshell power button device should start showing menu animation
+  // immediately as pressing the power button.
+  EXPECT_TRUE(power_button_test_api_->IsMenuOpened());
   ReleasePowerButton();
   EXPECT_FALSE(power_manager_client_->backlights_forced_off());
+  // Forced clamshell power button device should start dismissing menu animation
+  // immediately as releasing the power button.
+  EXPECT_FALSE(power_button_test_api_->IsMenuOpened());
 }
 
 // Tests that release power button after menu is opened but before trigger
diff --git a/ash/system/power/power_button_menu_screen_view.cc b/ash/system/power/power_button_menu_screen_view.cc
index d4e3198..72bc31a 100644
--- a/ash/system/power/power_button_menu_screen_view.cc
+++ b/ash/system/power/power_button_menu_screen_view.cc
@@ -30,7 +30,8 @@
     : public views::View,
       public ui::ImplicitAnimationObserver {
  public:
-  PowerButtonMenuBackgroundView() {
+  PowerButtonMenuBackgroundView(base::RepeatingClosure show_animation_done)
+      : show_animation_done_(show_animation_done) {
     SetPaintToLayer(ui::LAYER_SOLID_COLOR);
     layer()->SetColor(kShieldColor);
   }
@@ -38,15 +39,20 @@
   ~PowerButtonMenuBackgroundView() override = default;
 
   void OnImplicitAnimationsCompleted() override {
+    PowerButtonController* power_button_controller =
+        Shell::Get()->power_button_controller();
     if (layer()->opacity() == 0.f) {
       SetVisible(false);
-      Shell::Get()->power_button_controller()->DismissMenu();
+      power_button_controller->DismissMenu();
     }
+
+    if (layer()->opacity() == kPowerButtonMenuOpacity)
+      show_animation_done_.Run();
   }
 
   void ScheduleShowHideAnimation(bool show) {
-    layer()->GetAnimator()->StopAnimating();
-    layer()->SetOpacity(show ? 0.f : kPowerButtonMenuOpacity);
+    layer()->GetAnimator()->AbortAllAnimations();
+    layer()->SetOpacity(show ? 0.f : layer()->opacity());
 
     ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
     animation.AddObserver(this);
@@ -59,11 +65,16 @@
   }
 
  private:
+  // A callback for when the animation that shows the power menu has finished.
+  base::RepeatingClosure show_animation_done_;
+
   DISALLOW_COPY_AND_ASSIGN(PowerButtonMenuBackgroundView);
 };
 
-PowerButtonMenuScreenView::PowerButtonMenuScreenView() {
-  power_button_screen_background_shield_ = new PowerButtonMenuBackgroundView();
+PowerButtonMenuScreenView::PowerButtonMenuScreenView(
+    base::RepeatingClosure show_animation_done) {
+  power_button_screen_background_shield_ =
+      new PowerButtonMenuBackgroundView(show_animation_done);
   AddChildView(power_button_screen_background_shield_);
 
   power_button_menu_view_ = new PowerButtonMenuView();
diff --git a/ash/system/power/power_button_menu_screen_view.h b/ash/system/power/power_button_menu_screen_view.h
index d906cb6a..0118b78 100644
--- a/ash/system/power/power_button_menu_screen_view.h
+++ b/ash/system/power/power_button_menu_screen_view.h
@@ -19,7 +19,10 @@
 class ASH_EXPORT PowerButtonMenuScreenView : public views::View,
                                              public display::DisplayObserver {
  public:
-  PowerButtonMenuScreenView();
+  // |show_animation_done| is a callback for when the animation that shows the
+  // power menu has finished.
+  explicit PowerButtonMenuScreenView(
+      base::RepeatingClosure show_animation_done);
   ~PowerButtonMenuScreenView() override;
 
   PowerButtonMenuView* power_button_menu_view() const {
diff --git a/ash/system/power/power_button_menu_view.cc b/ash/system/power/power_button_menu_view.cc
index 8627d73..d7caccc 100644
--- a/ash/system/power/power_button_menu_view.cc
+++ b/ash/system/power/power_button_menu_view.cc
@@ -38,12 +38,12 @@
 PowerButtonMenuView::~PowerButtonMenuView() = default;
 
 void PowerButtonMenuView::ScheduleShowHideAnimation(bool show) {
-  // Stop any previous animation.
-  layer()->GetAnimator()->StopAnimating();
+  // Cancel any previous animation.
+  layer()->GetAnimator()->AbortAllAnimations();
 
   // Set initial state.
   SetVisible(true);
-  layer()->SetOpacity(show ? 0.f : 1.0f);
+  layer()->SetOpacity(show ? 0.f : layer()->opacity());
 
   ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
   animation.AddObserver(this);
diff --git a/ash/system/tray_accessibility.h b/ash/system/tray_accessibility.h
index 99e010e..410c41da 100644
--- a/ash/system/tray_accessibility.h
+++ b/ash/system/tray_accessibility.h
@@ -29,6 +29,7 @@
 namespace ash {
 class HoverHighlightView;
 class SystemTrayItem;
+class TrayAccessibilityTest;
 
 namespace tray {
 
@@ -41,6 +42,7 @@
   void OnAccessibilityStatusChanged();
 
  private:
+  friend class ::ash::TrayAccessibilityTest;
   friend class chromeos::TrayAccessibilityTest;
 
   // TrayDetailsView:
@@ -104,6 +106,7 @@
   ~TrayAccessibility() override;
 
  private:
+  friend class TrayAccessibilityTest;
   friend class chromeos::TrayAccessibilityTest;
 
   void SetTrayIconVisible(bool visible);
diff --git a/ash/system/tray_accessibility_unittest.cc b/ash/system/tray_accessibility_unittest.cc
index 5c28402..c1a41301 100644
--- a/ash/system/tray_accessibility_unittest.cc
+++ b/ash/system/tray_accessibility_unittest.cc
@@ -34,7 +34,101 @@
       ->SetBoolean(prefs::kAccessibilityLargeCursorEnabled, enabled);
 }
 
-using TrayAccessibilityTest = AshTestBase;
+}  // namespace
+
+class TrayAccessibilityTest : public AshTestBase {
+ public:
+  TrayAccessibilityTest() = default;
+  ~TrayAccessibilityTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    tray_item_ = SystemTrayTestApi(Shell::Get()->GetPrimarySystemTray())
+                     .tray_accessibility();
+  }
+
+  // These functions are members so TrayAccessibility can friend the test.
+  bool CreateDetailedMenu() {
+    tray_item_->ShowDetailedView(0);
+    return tray_item_->detailed_menu_ != nullptr;
+  }
+
+  void CloseDetailMenu() {
+    ASSERT_TRUE(tray_item_->detailed_menu_);
+    tray_item_->OnDetailedViewDestroyed();
+    ASSERT_FALSE(tray_item_->detailed_menu_);
+  }
+
+  bool IsSpokenFeedbackMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->spoken_feedback_view_;
+  }
+
+  bool IsSelectToSpeakShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->select_to_speak_view_;
+  }
+
+  bool IsHighContrastMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->high_contrast_view_;
+  }
+
+  bool IsScreenMagnifierMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->screen_magnifier_view_;
+  }
+
+  bool IsLargeCursorMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->large_cursor_view_;
+  }
+
+  bool IsAutoclickMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->autoclick_view_;
+  }
+
+  bool IsVirtualKeyboardMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->virtual_keyboard_view_;
+  }
+
+  bool IsMonoAudioMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->mono_audio_view_;
+  }
+
+  bool IsCaretHighlightMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->caret_highlight_view_;
+  }
+
+  bool IsHighlightMouseCursorMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->highlight_mouse_cursor_view_;
+  }
+
+  bool IsHighlightKeyboardFocusMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->highlight_keyboard_focus_view_;
+  }
+
+  bool IsStickyKeysMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->sticky_keys_view_;
+  }
+
+  bool IsTapDraggingMenuShownOnDetailMenu() const {
+    return tray_item_->detailed_menu_->tap_dragging_view_;
+  }
+
+  // In material design we show the help button but theme it as disabled if
+  // it is not possible to load the help page.
+  bool IsHelpAvailableOnDetailMenu() {
+    return tray_item_->detailed_menu_->help_view_->state() ==
+           views::Button::STATE_NORMAL;
+  }
+
+  // In material design we show the settings button but theme it as disabled if
+  // it is not possible to load the settings page.
+  bool IsSettingsAvailableOnDetailMenu() {
+    return tray_item_->detailed_menu_->settings_view_->state() ==
+           views::Button::STATE_NORMAL;
+  }
+
+ private:
+  TrayAccessibility* tray_item_;
+};
 
 // Tests that the icon becomes visible when the tray menu toggles a feature.
 TEST_F(TrayAccessibilityTest, VisibilityFromMenu) {
@@ -134,5 +228,68 @@
   EXPECT_EQ(kChromeVoxEnabled, (*notifications.begin())->message());
 }
 
-}  // namespace
+TEST_F(TrayAccessibilityTest, CheckMenuVisibilityOnDetailMenu) {
+  // Except help & settings, others should be kept the same
+  // in LOGIN | NOT LOGIN | LOCKED. https://crbug.com/632107.
+  EXPECT_TRUE(CreateDetailedMenu());
+  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsSelectToSpeakShownOnDetailMenu());
+  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHelpAvailableOnDetailMenu());
+  EXPECT_TRUE(IsSettingsAvailableOnDetailMenu());
+  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
+  CloseDetailMenu();
+
+  // Simulate screen lock.
+  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
+  EXPECT_TRUE(CreateDetailedMenu());
+  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsSelectToSpeakShownOnDetailMenu());
+  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
+  EXPECT_FALSE(IsHelpAvailableOnDetailMenu());
+  EXPECT_FALSE(IsSettingsAvailableOnDetailMenu());
+  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
+  CloseDetailMenu();
+  UnblockUserSession();
+
+  // Simulate adding multiprofile user.
+  BlockUserSession(BLOCKED_BY_USER_ADDING_SCREEN);
+  EXPECT_TRUE(CreateDetailedMenu());
+  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsSelectToSpeakShownOnDetailMenu());
+  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
+  EXPECT_FALSE(IsHelpAvailableOnDetailMenu());
+  EXPECT_FALSE(IsSettingsAvailableOnDetailMenu());
+  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
+  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
+  CloseDetailMenu();
+  UnblockUserSession();
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc
index cb26375..8bdd4aa 100644
--- a/ash/wm/overview/window_selector.cc
+++ b/ash/wm/overview/window_selector.cc
@@ -18,6 +18,7 @@
 #include "ash/screen_util.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/overview/overview_window_drag_controller.h"
@@ -37,6 +38,8 @@
 #include "base/metrics/user_metrics.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/vector_icons/vector_icons.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/display/screen.h"
@@ -45,6 +48,7 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/skia_util.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -194,6 +198,8 @@
   textfield->SetTextColor(IsNewOverviewUi() ? kTextFilterTextColor
                                             : kOldTextFilterTextColor);
   textfield->SetFontList(font_list);
+  textfield->SetAccessibleName(l10n_util::GetStringUTF16(
+      IDS_ASH_WINDOW_SELECTOR_INPUT_FILTER_ACCESSIBLE_NAME));
 
   views::ImageView* image_view = new views::ImageView();
   image_view->SetImage(image);
diff --git a/ash/wm/overview/window_selector_item.cc b/ash/wm/overview/window_selector_item.cc
index 61b5ff0..b43ddd6 100644
--- a/ash/wm/overview/window_selector_item.cc
+++ b/ash/wm/overview/window_selector_item.cc
@@ -1304,6 +1304,19 @@
     widget_window->parent()->StackChildAtTop(widget_window);
     widget_window->parent()->StackChildBelow(window, widget_window);
   }
+
+  // If split view mode is actvie and there is already a snapped window, stack
+  // this item's window below the snapped window. Note: this should be temporary
+  // for M66, see https://crbug.com/809298 for details.
+  if (Shell::Get()->IsSplitViewModeActive()) {
+    aura::Window* snapped_window =
+        Shell::Get()->split_view_controller()->GetDefaultSnappedWindow();
+    if (widget_window && widget_window->parent() == window->parent() &&
+        widget_window->parent() == snapped_window->parent()) {
+      widget_window->parent()->StackChildBelow(widget_window, snapped_window);
+      widget_window->parent()->StackChildBelow(window, widget_window);
+    }
+  }
 }
 
 void WindowSelectorItem::EndDrag() {
diff --git a/ash/wm/workspace/multi_window_resize_controller.cc b/ash/wm/workspace/multi_window_resize_controller.cc
index 48131d7..487ccc9 100644
--- a/ash/wm/workspace/multi_window_resize_controller.cc
+++ b/ash/wm/workspace/multi_window_resize_controller.cc
@@ -226,8 +226,8 @@
   }
 
   windows_ = windows;
-  windows_.window1->AddObserver(this);
-  windows_.window2->AddObserver(this);
+  StartObserving(windows_.window1);
+  StartObserving(windows_.window2);
   show_location_in_parent_ =
       ConvertPointToTarget(window, window->parent(), point_in_window);
   show_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
@@ -240,11 +240,11 @@
     return;  // Ignore hides while actively resizing.
 
   if (windows_.window1) {
-    windows_.window1->RemoveObserver(this);
+    StopObserving(windows_.window1);
     windows_.window1 = nullptr;
   }
   if (windows_.window2) {
-    windows_.window2->RemoveObserver(this);
+    StopObserving(windows_.window2);
     windows_.window2 = nullptr;
   }
 
@@ -254,7 +254,7 @@
     return;
 
   for (size_t i = 0; i < windows_.other_windows.size(); ++i)
-    windows_.other_windows[i]->RemoveObserver(this);
+    StopObserving(windows_.other_windows[i]);
   mouse_watcher_.reset();
   resize_widget_.reset();
   windows_ = ResizeWindows();
@@ -270,6 +270,16 @@
   Hide();
 }
 
+void MultiWindowResizeController::OnPostWindowStateTypeChange(
+    wm::WindowState* window_state,
+    mojom::WindowStateType old_type) {
+  if (window_state->IsMaximized() || window_state->IsFullscreen() ||
+      window_state->IsMinimized()) {
+    window_resizer_.reset();
+    Hide();
+  }
+}
+
 MultiWindowResizeController::ResizeWindows
 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
     aura::Window* window) const {
@@ -416,6 +426,16 @@
   }
 }
 
+void MultiWindowResizeController::StartObserving(aura::Window* window) {
+  window->AddObserver(this);
+  wm::GetWindowState(window)->AddObserver(this);
+}
+
+void MultiWindowResizeController::StopObserving(aura::Window* window) {
+  window->RemoveObserver(this);
+  wm::GetWindowState(window)->RemoveObserver(this);
+}
+
 void MultiWindowResizeController::ShowIfValidMouseLocation() {
   if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
       DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
@@ -467,7 +487,7 @@
   FindWindowsTouching(windows_.window2, windows_.direction,
                       &windows_.other_windows);
   for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
-    windows_.other_windows[i]->AddObserver(this);
+    StartObserving(windows_.other_windows[i]);
     windows.push_back(windows_.other_windows[i]);
   }
   int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
@@ -510,7 +530,7 @@
     // the |other_windows|. If we start another resize we'll recalculate the
     // |other_windows| and invoke AddObserver() as necessary.
     for (size_t i = 0; i < windows_.other_windows.size(); ++i)
-      windows_.other_windows[i]->RemoveObserver(this);
+      StopObserving(windows_.other_windows[i]);
     windows_.other_windows.clear();
 
     CreateMouseWatcher();
diff --git a/ash/wm/workspace/multi_window_resize_controller.h b/ash/wm/workspace/multi_window_resize_controller.h
index 1b98294..43c47ea 100644
--- a/ash/wm/workspace/multi_window_resize_controller.h
+++ b/ash/wm/workspace/multi_window_resize_controller.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "ash/wm/window_state_observer.h"
 #include "base/macros.h"
 #include "base/timer/timer.h"
 #include "ui/aura/window_observer.h"
@@ -34,7 +35,8 @@
 // MultiWindowResizeController is driven by WorkspaceEventFilter.
 class ASH_EXPORT MultiWindowResizeController
     : public views::MouseWatcherListener,
-      public aura::WindowObserver {
+      public aura::WindowObserver,
+      public wm::WindowStateObserver {
  public:
   MultiWindowResizeController();
   ~MultiWindowResizeController() override;
@@ -46,12 +48,16 @@
   // Hides the resize widget.
   void Hide();
 
-  // MouseWatcherListenre overrides:
+  // MouseWatcherListener:
   void MouseMovedOutOfHost() override;
 
-  // WindowObserver overrides:
+  // WindowObserver:
   void OnWindowDestroying(aura::Window* window) override;
 
+  // wm::WindowStateObserver:
+  void OnPostWindowStateTypeChange(wm::WindowState* window_state,
+                                   mojom::WindowStateType old_type) override;
+
  private:
   friend class MultiWindowResizeControllerTest;
 
@@ -112,6 +118,10 @@
                            Direction direction,
                            std::vector<aura::Window*>* others) const;
 
+  // Starts/Stops observing |window|.
+  void StartObserving(aura::Window* window);
+  void StopObserving(aura::Window* window);
+
   // Shows the resizer if the mouse is still at a valid location. This is called
   // from the |show_timer_|.
   void ShowIfValidMouseLocation();
diff --git a/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
index a1ca3b09..f94350b 100644
--- a/ash/wm/workspace/multi_window_resize_controller_unittest.cc
+++ b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -9,6 +9,7 @@
 #include "ash/shell.h"
 #include "ash/shell_test_api.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_state_delegate.h"
 #include "ash/wm/window_util.h"
@@ -17,8 +18,10 @@
 #include "ash/wm/workspace_controller.h"
 #include "ash/wm/workspace_controller_test_api.h"
 #include "base/stl_util.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
+#include "ui/base/class_property.h"
 #include "ui/base/hit_test.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/views/widget/widget.h"
@@ -351,6 +354,57 @@
   EXPECT_FALSE(IsShowing());
 }
 
+// Tests that if the resized window is maximized/fullscreen/minimized, the
+// resizer widget should be dismissed.
+TEST_F(MultiWindowResizeControllerTest, WindowStateChange) {
+  aura::test::TestWindowDelegate delegate1;
+  std::unique_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+      &delegate1, -1, gfx::Rect(0, 0, 100, 100)));
+  delegate1.set_window_component(HTRIGHT);
+  aura::test::TestWindowDelegate delegate2;
+  std::unique_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+      &delegate2, -2, gfx::Rect(100, 0, 100, 100)));
+  delegate2.set_window_component(HTLEFT);
+
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  gfx::Point w1_center_in_screen = w1->GetBoundsInScreen().CenterPoint();
+  generator.MoveMouseTo(w1_center_in_screen);
+  ShowNow();
+  EXPECT_TRUE(IsShowing());
+
+  // Maxmize one window should dismiss the resizer.
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+  EXPECT_FALSE(IsShowing());
+
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+  generator.MoveMouseTo(w1_center_in_screen);
+  ShowNow();
+  EXPECT_TRUE(IsShowing());
+
+  // Entering Fullscreen should dismiss the resizer.
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+  EXPECT_FALSE(IsShowing());
+
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+  generator.MoveMouseTo(w1_center_in_screen);
+  ShowNow();
+  EXPECT_TRUE(IsShowing());
+
+  // Minimize one window should dimiss the resizer.
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+  EXPECT_FALSE(IsShowing());
+
+  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+  generator.MoveMouseTo(w1_center_in_screen);
+  ShowNow();
+  EXPECT_TRUE(IsShowing());
+
+  // When entering tablet mode, the windows will be maximized, thus the resizer
+  // widget should be dismissed.
+  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_FALSE(IsShowing());
+}
+
 namespace {
 
 class TestWindowStateDelegate : public wm::WindowStateDelegate {
diff --git a/base/atomicops.h b/base/atomicops.h
index 3428fe8..4d8510e8 100644
--- a/base/atomicops.h
+++ b/base/atomicops.h
@@ -145,8 +145,8 @@
 }  // namespace base
 
 #if defined(OS_WIN)
-// TODO(jfb): The MSVC header includes windows.h, which other files end up
-//            relying on. Fix this as part of crbug.com/559247.
+// TODO(jfb): Try to use base/atomicops_internals_portable.h everywhere.
+// https://crbug.com/559247.
 #  include "base/atomicops_internals_x86_msvc.h"
 #else
 #  include "base/atomicops_internals_portable.h"
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index 7150815..52adb693 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -154,7 +154,7 @@
 #if defined(OS_LINUX) || defined(OS_ANDROID)
   // Resident Set Size is a Linux/Android specific memory concept. Do not
   // attempt to extend this to other platforms.
-  BASE_EXPORT size_t GetRSS() const;
+  BASE_EXPORT size_t GetResidentSetSize() const;
 #endif
 
 #if defined(OS_MACOSX)
diff --git a/base/process/process_metrics_linux.cc b/base/process/process_metrics_linux.cc
index e388fe30..27c1496 100644
--- a/base/process/process_metrics_linux.cc
+++ b/base/process/process_metrics_linux.cc
@@ -211,7 +211,7 @@
   return WrapUnique(new ProcessMetrics(process));
 }
 
-size_t ProcessMetrics::GetRSS() const {
+size_t ProcessMetrics::GetResidentSetSize() const {
   return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
       getpagesize();
 }
diff --git a/base/win/win_includes_unittest.cc b/base/win/win_includes_unittest.cc
index 73b7b55..20c6cbc 100644
--- a/base/win/win_includes_unittest.cc
+++ b/base/win/win_includes_unittest.cc
@@ -5,6 +5,7 @@
 // This file ensures that these header files don't include Windows.h and can
 // compile without including Windows.h. This helps to improve compile times.
 
+#include "base/atomicops.h"
 #include "base/files/file_util.h"
 #include "base/files/platform_file.h"
 #include "base/process/process_handle.h"
diff --git a/cc/input/scrollbar_animation_controller.cc b/cc/input/scrollbar_animation_controller.cc
index 0ee16d5..55f97f09 100644
--- a/cc/input/scrollbar_animation_controller.cc
+++ b/cc/input/scrollbar_animation_controller.cc
@@ -54,6 +54,7 @@
       show_scrollbars_on_scroll_gesture_(false),
       need_thinning_animation_(false),
       is_mouse_down_(false),
+      tickmarks_showing_(false),
       weak_factory_(this) {}
 
 ScrollbarAnimationController::ScrollbarAnimationController(
@@ -76,6 +77,7 @@
       show_scrollbars_on_scroll_gesture_(true),
       need_thinning_animation_(true),
       is_mouse_down_(false),
+      tickmarks_showing_(false),
       weak_factory_(this) {
   vertical_controller_ = SingleScrollbarAnimationControllerThinning::Create(
       scroll_element_id, ScrollbarOrientation::VERTICAL, client,
@@ -194,11 +196,15 @@
   if (need_thinning_animation_ && MouseIsNearAnyScrollbar())
     return;
 
-  if (has_scrolled)
+  if (has_scrolled && !tickmarks_showing_)
     PostDelayedAnimation(FADE_OUT);
 }
 
 void ScrollbarAnimationController::DidScrollUpdate() {
+  UpdateScrollbarState();
+}
+
+void ScrollbarAnimationController::UpdateScrollbarState() {
   if (need_thinning_animation_ && Captured())
     return;
 
@@ -209,10 +215,14 @@
   // As an optimization, we avoid spamming fade delay tasks during active fast
   // scrolls.  But if we're not within one, we need to post every scroll update.
   if (!currently_scrolling_) {
-    // We don't fade out scrollbar if they need thinning animation and mouse is
-    // near.
-    if (!need_thinning_animation_ || !MouseIsNearAnyScrollbar())
+    // We don't fade out scrollbar if they need thinning animation (Aura
+    // Overlay) and mouse is near or tickmarks show.
+    if (need_thinning_animation_) {
+      if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_)
+        PostDelayedAnimation(FADE_OUT);
+    } else {
       PostDelayedAnimation(FADE_OUT);
+    }
   } else {
     show_in_fast_scroll_ = true;
   }
@@ -225,11 +235,22 @@
 
 void ScrollbarAnimationController::WillUpdateScroll() {
   if (show_scrollbars_on_scroll_gesture_)
-    DidScrollUpdate();
+    UpdateScrollbarState();
 }
 
 void ScrollbarAnimationController::DidRequestShowFromMainThread() {
-  DidScrollUpdate();
+  UpdateScrollbarState();
+}
+
+void ScrollbarAnimationController::UpdateTickmarksVisibility(bool show) {
+  if (!need_thinning_animation_)
+    return;
+
+  if (tickmarks_showing_ == show)
+    return;
+
+  tickmarks_showing_ = show;
+  UpdateScrollbarState();
 }
 
 void ScrollbarAnimationController::DidMouseDown() {
@@ -267,7 +288,7 @@
   vertical_controller_->DidMouseUp();
   horizontal_controller_->DidMouseUp();
 
-  if (!MouseIsNearAnyScrollbar() && !ScrollbarsHidden())
+  if (!MouseIsNearAnyScrollbar() && !ScrollbarsHidden() && !tickmarks_showing_)
     PostDelayedAnimation(FADE_OUT);
 }
 
@@ -281,7 +302,7 @@
   delayed_scrollbar_animation_.Cancel();
   need_trigger_scrollbar_fade_in_ = false;
 
-  if (ScrollbarsHidden() || Captured())
+  if (ScrollbarsHidden() || Captured() || tickmarks_showing_)
     return;
 
   PostDelayedAnimation(FADE_OUT);
@@ -297,7 +318,7 @@
   vertical_controller_->DidMouseMove(device_viewport_point);
   horizontal_controller_->DidMouseMove(device_viewport_point);
 
-  if (Captured()) {
+  if (Captured() || tickmarks_showing_) {
     DCHECK(!ScrollbarsHidden());
     return;
   }
diff --git a/cc/input/scrollbar_animation_controller.h b/cc/input/scrollbar_animation_controller.h
index 64bf2b6..9473b41 100644
--- a/cc/input/scrollbar_animation_controller.h
+++ b/cc/input/scrollbar_animation_controller.h
@@ -84,6 +84,8 @@
   // ScrollableArea::showOverlayScrollbars).
   void DidRequestShowFromMainThread();
 
+  void UpdateTickmarksVisibility(bool show);
+
   // These methods are public for testing.
   bool MouseIsOverScrollbarThumb(ScrollbarOrientation orientation) const;
   bool MouseIsNearScrollbarThumb(ScrollbarOrientation orientation) const;
@@ -114,6 +116,10 @@
   SingleScrollbarAnimationControllerThinning& GetScrollbarAnimationController(
       ScrollbarOrientation) const;
 
+  // Any scrollbar state update would show scrollbar hen post the delay fade out
+  // if needed.
+  void UpdateScrollbarState();
+
   // Returns how far through the animation we are as a progress value from
   // 0 to 1.
   float AnimationProgressAtTime(base::TimeTicks now);
@@ -156,6 +162,8 @@
 
   bool is_mouse_down_;
 
+  bool tickmarks_showing_;
+
   std::unique_ptr<SingleScrollbarAnimationControllerThinning>
       vertical_controller_;
   std::unique_ptr<SingleScrollbarAnimationControllerThinning>
diff --git a/cc/input/scrollbar_animation_controller_unittest.cc b/cc/input/scrollbar_animation_controller_unittest.cc
index b142096e..9749fc7 100644
--- a/cc/input/scrollbar_animation_controller_unittest.cc
+++ b/cc/input/scrollbar_animation_controller_unittest.cc
@@ -1318,6 +1318,54 @@
               client_.start_fade().IsCancelled());
 }
 
+// Ensure Aura Overlay Scrollbars shows and did not fade out when tickmarks show
+// and fade out when tickmarks hide.
+TEST_F(ScrollbarAnimationControllerAuraOverlayTest, TickmakrsShowHide) {
+  base::TimeTicks time;
+  time += base::TimeDelta::FromSeconds(1);
+
+  // Overlay Scrollbar hidden at beginnging.
+  EXPECT_TRUE(scrollbar_controller_->ScrollbarsHidden());
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Scrollbars show when tickmarks show.
+  scrollbar_controller_->UpdateTickmarksVisibility(true);
+  EXPECT_FALSE(scrollbar_controller_->ScrollbarsHidden());
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Scroll update, no delay fade animation.
+  scrollbar_controller_->DidScrollUpdate();
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Scroll update with phase, no delay fade animation.
+  scrollbar_controller_->DidScrollBegin();
+  scrollbar_controller_->DidScrollUpdate();
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+  scrollbar_controller_->DidScrollEnd();
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Move mouse, no delay fade animation.
+  scrollbar_controller_->DidMouseMove(NearVerticalScrollbarBegin(0, 0));
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Mouse leave, no delay fade animation.
+  scrollbar_controller_->DidMouseLeave();
+  EXPECT_TRUE(client_.start_fade().is_null() ||
+              client_.start_fade().IsCancelled());
+
+  // Scrollbars fade out animation has enqueued when tickmarks hide.
+  scrollbar_controller_->UpdateTickmarksVisibility(false);
+  EXPECT_FALSE(client_.start_fade().is_null());
+  EXPECT_FALSE(client_.start_fade().IsCancelled());
+  EXPECT_EQ(kFadeDelay, client_.delay());
+}
+
 class ScrollbarAnimationControllerAndroidTest
     : public testing::Test,
       public ScrollbarAnimationControllerClient {
diff --git a/cc/layers/painted_overlay_scrollbar_layer_impl.cc b/cc/layers/painted_overlay_scrollbar_layer_impl.cc
index 79724d1..4f5a61e 100644
--- a/cc/layers/painted_overlay_scrollbar_layer_impl.cc
+++ b/cc/layers/painted_overlay_scrollbar_layer_impl.cc
@@ -232,4 +232,8 @@
   return "cc::PaintedOverlayScrollbarLayerImpl";
 }
 
+bool PaintedOverlayScrollbarLayerImpl::HasFindInPageTickmarks() const {
+  return track_ui_resource_id_ != 0;
+}
+
 }  // namespace cc
diff --git a/cc/layers/painted_overlay_scrollbar_layer_impl.h b/cc/layers/painted_overlay_scrollbar_layer_impl.h
index 1152992ad..ca436bd 100644
--- a/cc/layers/painted_overlay_scrollbar_layer_impl.h
+++ b/cc/layers/painted_overlay_scrollbar_layer_impl.h
@@ -51,6 +51,8 @@
     track_ui_resource_id_ = uid;
   }
 
+  bool HasFindInPageTickmarks() const override;
+
  protected:
   PaintedOverlayScrollbarLayerImpl(LayerTreeImpl* tree_impl,
                                    int id,
diff --git a/cc/layers/scrollbar_layer_impl_base.cc b/cc/layers/scrollbar_layer_impl_base.cc
index 1758c6f..6a0f9eb6 100644
--- a/cc/layers/scrollbar_layer_impl_base.cc
+++ b/cc/layers/scrollbar_layer_impl_base.cc
@@ -270,4 +270,8 @@
   return layer_tree_impl()->settings().scrollbar_animator;
 }
 
+bool ScrollbarLayerImplBase::HasFindInPageTickmarks() const {
+  return false;
+}
+
 }  // namespace cc
diff --git a/cc/layers/scrollbar_layer_impl_base.h b/cc/layers/scrollbar_layer_impl_base.h
index df9b3d1..8c38e0e 100644
--- a/cc/layers/scrollbar_layer_impl_base.h
+++ b/cc/layers/scrollbar_layer_impl_base.h
@@ -64,6 +64,10 @@
 
   virtual LayerTreeSettings::ScrollbarAnimator GetScrollbarAnimator() const;
 
+  // Only PaintedOverlayScrollbar(Aura Overlay Scrollbar) need to know
+  // tickmarks's state.
+  virtual bool HasFindInPageTickmarks() const;
+
  protected:
   ScrollbarLayerImplBase(LayerTreeImpl* tree_impl,
                          int id,
diff --git a/cc/resources/video_resource_updater.cc b/cc/resources/video_resource_updater.cc
index 87026c7..0e0378a 100644
--- a/cc/resources/video_resource_updater.cc
+++ b/cc/resources/video_resource_updater.cc
@@ -338,6 +338,7 @@
   bool texture_needs_rgb_conversion =
       !software_compositor &&
       output_resource_format == viz::ResourceFormat::RGBA_8888;
+
   size_t output_plane_count = media::VideoFrame::NumPlanes(input_frame_format);
 
   // TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB
@@ -348,6 +349,11 @@
     output_resource_format = viz::RGBA_8888;
     output_plane_count = 1;
     bits_per_channel = 8;
+
+    // The YUV to RGB conversion will be performed when we convert
+    // from single-channel textures to an RGBA texture via
+    // ConvertVideoFrameToRGBPixels below.
+    output_color_space = output_color_space.GetAsFullRangeRGB();
   }
 
   // Drop recycled resources that are the wrong format.
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
index 4139dfa..8f5409f 100644
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -816,7 +816,6 @@
 
   did_draw_ = true;
   active_tree_needs_first_draw_ = false;
-  did_draw_in_last_frame_ = true;
   last_frame_number_draw_performed_ = current_frame_number_;
 
   if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW)
@@ -863,6 +862,10 @@
 void SchedulerStateMachine::WillDraw() {
   DCHECK(!did_draw_);
   WillDrawInternal();
+  // Set this to true to proactively request a new BeginFrame. We can't set this
+  // in WillDrawInternal because AbortDraw calls WillDrawInternal but shouldn't
+  // request another frame.
+  did_draw_in_last_frame_ = true;
 }
 
 void SchedulerStateMachine::DidDraw(DrawResult draw_result) {
@@ -910,11 +913,6 @@
   did_invalidate_layer_tree_frame_sink_ = true;
   last_frame_number_invalidate_layer_tree_frame_sink_performed_ =
       current_frame_number_;
-
-  // The synchronous compositor makes no guarantees about a draw coming in after
-  // an invalidate so clear any flags that would cause the compositor's pipeline
-  // to stall.
-  active_tree_needs_first_draw_ = false;  // blocks commit if true
 }
 
 void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency(
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc
index 9069538..a4e1fddb 100644
--- a/cc/scheduler/scheduler_unittest.cc
+++ b/cc/scheduler/scheduler_unittest.cc
@@ -2555,7 +2555,10 @@
   task_runner().RunPendingTasks();  // Run posted deadline.
 
   // Sync tree should be forced to activate.
-  EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
+  // Pausing the begin frame source aborts the draw. Then
+  // ProactiveBeginFrameWanted is no longer true, so the scheduler stops
+  // listening for begin frames.
+  EXPECT_ACTIONS("ScheduledActionActivateSyncTree", "RemoveObserver(this)");
 }
 
 // Tests to ensure frame sources can be successfully changed while drawing.
@@ -2977,7 +2980,10 @@
   task_runner().RunPendingTasks();  // Run posted deadline.
   // Should not try to schedule a draw. (ScheduledActionDrawIfPossible should
   // not appear.)
-  EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame");
+  // When the frame is aborted, the scheduler does not ask for a proactive begin
+  // frame, so stop listening for begin frames.
+  EXPECT_ACTIONS("AddObserver(this)", "WillBeginImplFrame",
+                 "RemoveObserver(this)");
   EXPECT_EQ(0, client_->num_draws());
 
   scheduler_->SetNeedsRedraw();
@@ -3063,49 +3069,6 @@
   client_->Reset();
 }
 
-TEST_F(SchedulerTest, SynchronousCompositorDoubleCommitWithoutDraw) {
-  scheduler_settings_.using_synchronous_renderer_compositor = true;
-  SetUpScheduler(EXTERNAL_BFS);
-
-  scheduler_->SetNeedsBeginMainFrame();
-  EXPECT_ACTIONS("AddObserver(this)");
-  client_->Reset();
-
-  // Next vsync.
-  AdvanceFrame();
-  EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame");
-  EXPECT_FALSE(client_->IsInsideBeginImplFrame());
-  client_->Reset();
-
-  scheduler_->NotifyBeginMainFrameStarted(now_src()->NowTicks());
-  EXPECT_NO_ACTION();
-
-  scheduler_->NotifyReadyToCommit();
-  EXPECT_ACTIONS("ScheduledActionCommit");
-  client_->Reset();
-
-  scheduler_->NotifyReadyToActivate();
-  EXPECT_ACTIONS("ScheduledActionActivateSyncTree");
-  client_->Reset();
-
-  // Ask for another commit.
-  scheduler_->SetNeedsBeginMainFrame();
-
-  AdvanceFrame();
-  EXPECT_ACTIONS("WillBeginImplFrame", "ScheduledActionSendBeginMainFrame",
-                 "ScheduledActionInvalidateLayerTreeFrameSink");
-  EXPECT_FALSE(client_->IsInsideBeginImplFrame());
-  client_->Reset();
-
-  scheduler_->NotifyBeginMainFrameStarted(now_src()->NowTicks());
-  EXPECT_NO_ACTION();
-
-  // Allow new commit even though previous commit hasn't been drawn.
-  scheduler_->NotifyReadyToCommit();
-  EXPECT_ACTIONS("ScheduledActionCommit");
-  client_->Reset();
-}
-
 class SchedulerClientSetNeedsPrepareTilesOnDraw : public FakeSchedulerClient {
  public:
   SchedulerClientSetNeedsPrepareTilesOnDraw() : FakeSchedulerClient() {}
diff --git a/cc/test/test_in_process_context_provider.cc b/cc/test/test_in_process_context_provider.cc
index 77d3bca..145c765 100644
--- a/cc/test/test_in_process_context_provider.cc
+++ b/cc/test/test_in_process_context_provider.cc
@@ -72,18 +72,6 @@
   cache_controller_.reset(new viz::ContextCacheController(
       context_->GetImplementation(), base::ThreadTaskRunnerHandle::Get()));
 
-  capabilities_.texture_rectangle = true;
-  capabilities_.sync_query = true;
-  capabilities_.texture_norm16 = true;
-  switch (viz::PlatformColor::Format()) {
-    case viz::PlatformColor::SOURCE_FORMAT_RGBA8:
-      capabilities_.texture_format_bgra8888 = false;
-      break;
-    case viz::PlatformColor::SOURCE_FORMAT_BGRA8:
-      capabilities_.texture_format_bgra8888 = true;
-      break;
-  }
-
   raster_implementation_ =
       std::make_unique<gpu::raster::RasterImplementationGLES>(
           context_->GetImplementation(), context_->GetImplementation(),
@@ -146,7 +134,7 @@
 
 const gpu::Capabilities& TestInProcessContextProvider::ContextCapabilities()
     const {
-  return capabilities_;
+  return context_->GetCapabilities();
 }
 
 const gpu::GpuFeatureInfo& TestInProcessContextProvider::GetGpuFeatureInfo()
diff --git a/cc/test/test_in_process_context_provider.h b/cc/test/test_in_process_context_provider.h
index a34c42c..cb684bf 100644
--- a/cc/test/test_in_process_context_provider.h
+++ b/cc/test/test_in_process_context_provider.h
@@ -60,9 +60,6 @@
   const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override;
   void AddObserver(viz::ContextLostObserver* obs) override {}
   void RemoveObserver(viz::ContextLostObserver* obs) override {}
-  void SetSupportTextureNorm16(bool support) {
-    capabilities_texture_norm16_ = support;
-  }
 
  protected:
   friend class base::RefCountedThreadSafe<TestInProcessContextProvider>;
@@ -76,8 +73,6 @@
   std::unique_ptr<skia_bindings::GrContextForGLES2Interface> gr_context_;
   std::unique_ptr<viz::ContextCacheController> cache_controller_;
   base::Lock context_lock_;
-  bool capabilities_texture_norm16_ = false;
-  gpu::Capabilities capabilities_;
   gpu::GpuFeatureInfo gpu_feature_info_;
 };
 
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index b5b2da7..55f8727 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -505,9 +505,33 @@
   target_tree->has_ever_been_drawn_ = false;
 
   // Note: this needs to happen after SetPropertyTrees.
+  target_tree->HandleTickmarksVisibilityChange();
   target_tree->HandleScrollbarShowRequestsFromMain();
 }
 
+void LayerTreeImpl::HandleTickmarksVisibilityChange() {
+  if (!host_impl_->ViewportMainScrollLayer())
+    return;
+
+  ScrollbarAnimationController* controller =
+      host_impl_->ScrollbarAnimationControllerForElementId(
+          OuterViewportScrollLayer()->element_id());
+
+  if (!controller)
+    return;
+
+  for (ScrollbarLayerImplBase* scrollbar : controller->Scrollbars()) {
+    if (scrollbar->orientation() != VERTICAL)
+      continue;
+
+    // Android Overlay Scrollbar don't have FindInPage Tickmarks.
+    if (scrollbar->GetScrollbarAnimator() != LayerTreeSettings::AURA_OVERLAY)
+      DCHECK(!scrollbar->HasFindInPageTickmarks());
+
+    controller->UpdateTickmarksVisibility(scrollbar->HasFindInPageTickmarks());
+  }
+}
+
 void LayerTreeImpl::HandleScrollbarShowRequestsFromMain() {
   LayerTreeHostCommon::CallFunctionForEveryLayer(this, [this](
                                                            LayerImpl* layer) {
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index 5dda067..801e631 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -551,6 +551,8 @@
   void ClearLayerList();
 
   void BuildLayerListForTesting();
+
+  void HandleTickmarksVisibilityChange();
   void HandleScrollbarShowRequestsFromMain();
 
   void InvalidateRegionForImages(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
index 67d95eb..8917449 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java
@@ -228,11 +228,13 @@
             // Click event happens before the up event. mClicked is set to mute the up event.
             mClicked = true;
             PortraitViewport viewportParams = getViewportParameters();
-            int stackIndexAt = viewportParams.getStackIndexAt(x, y);
-            if (stackIndexAt == getTabStackIndex()) {
+            final int stackIndexDeltaAt = viewportParams.getStackIndexDeltaAt(x, y);
+            if (stackIndexDeltaAt == 0) {
                 mStacks.get(getTabStackIndex()).click(time(), x, y);
             } else {
-                flingStacks(getTabStackIndex() == 0);
+                final int newStackIndex = getTabStackIndex() + stackIndexDeltaAt;
+                if (newStackIndex < 0 || newStackIndex >= mStacks.size()) return;
+                flingStacks(newStackIndex);
             }
             requestStackUpdate();
         }
@@ -274,12 +276,19 @@
 
         private void onUpOrCancel(long time) {
             int currentIndex = getTabStackIndex();
-            int nextIndex = 1 - currentIndex;
             if (!mClicked
-                    && Math.abs(currentIndex + mRenderedScrollOffset) > THRESHOLD_TO_SWITCH_STACK
-                    && mStacks.get(nextIndex).isDisplayable()) {
-                setActiveStackState(nextIndex == 1);
+                    && Math.abs(currentIndex + mRenderedScrollOffset) > THRESHOLD_TO_SWITCH_STACK) {
+                int nextIndex;
+                if (currentIndex + mRenderedScrollOffset < 0) {
+                    nextIndex = currentIndex + 1;
+                } else {
+                    nextIndex = currentIndex - 1;
+                }
+                if (mStacks.get(nextIndex).isDisplayable()) {
+                    setActiveStackState(nextIndex);
+                }
             }
+
             mClicked = false;
             finishScrollStacks();
             mStacks.get(getTabStackIndex()).onUpOrCancel(time);
@@ -444,7 +453,7 @@
         startMarginAnimation(true);
 
         // Animate the stack to leave incognito mode.
-        if (!mStacks.get(1).isDisplayable()) uiPreemptivelySelectTabModel(false);
+        if (!mStacks.get(1).isDisplayable()) onTabModelSwitched(false);
     }
 
     @Override
@@ -455,7 +464,7 @@
         // trigger the overlap animation.
         startMarginAnimation(true);
         // Animate the stack to leave incognito mode.
-        if (!mStacks.get(1).isDisplayable()) uiPreemptivelySelectTabModel(false);
+        if (!mStacks.get(1).isDisplayable()) onTabModelSwitched(false);
     }
 
     @Override
@@ -549,7 +558,7 @@
             startMarginAnimation(false);
         }
 
-        uiPreemptivelySelectTabModel(newIsIncognito);
+        onTabModelSwitched(newIsIncognito);
     }
 
     @Override
@@ -562,7 +571,7 @@
 
     @Override
     public void onTabModelSwitched(boolean toIncognitoTabModel) {
-        flingStacks(toIncognitoTabModel);
+        flingStacks(toIncognitoTabModel ? INCOGNITO_STACK_INDEX : NORMAL_STACK_INDEX);
         mFlingFromModelChange = true;
     }
 
@@ -640,7 +649,7 @@
 
         // Make sure we show/hide both stacks depending on which tab we're closing.
         startMarginAnimation(true, incognitoVisible);
-        if (!incognitoVisible) uiPreemptivelySelectTabModel(false);
+        if (!incognitoVisible) onTabModelSwitched(false);
     }
 
     /**
@@ -676,10 +685,6 @@
         doneShowing();
     }
 
-    private void uiPreemptivelySelectTabModel(boolean incognito) {
-        onTabModelSwitched(incognito);
-    }
-
     /**
      * Starts the animation for the opposite stack to slide in or out when entering
      * or leaving stack view.  The animation should be super fast to match more or less
@@ -744,7 +749,7 @@
         }
         startMarginAnimation(true);
         startYOffsetAnimation(true);
-        flingStacks(getTabStackIndex() == 1);
+        flingStacks(getTabStackIndex());
 
         if (!animate) onUpdateAnimation(time, true);
 
@@ -817,7 +822,12 @@
      * @return     The input mode to select.
      */
     private @SwipeMode int computeInputMode(long time, float x, float y, float dx, float dy) {
-        if (!mStacks.get(1).isDisplayable()) return SWIPE_MODE_SEND_TO_STACK;
+        if (mStacks.size() == 0) return SWIPE_MODE_NONE;
+
+        if (mStacks.size() == 1 || (mStacks.size() == 2 && !mStacks.get(1).isDisplayable())) {
+            return SWIPE_MODE_SEND_TO_STACK;
+        }
+
         int currentIndex = getTabStackIndex();
 
         // When a drag starts, lock the drag into being either horizontal or vertical until the
@@ -881,21 +891,58 @@
             return margin;
         }
 
-        int getStackIndexAt(float x, float y) {
-            if (LocalizationUtils.isLayoutRtl()) {
-                // On RTL portrait mode, stack1 (incognito) is on the left.
-                float separation = getStack0Left();
-                return x < separation ? 1 : 0;
-            } else {
-                float separation = getStack0Left() + getWidth();
-                return x < separation ? 0 : 1;
+        /**
+         * Returns an offset that can be added to the index of the current stack to get the index of
+         * the stack at the specified on-screen location.
+         * @param x The x coordinate of the specified on-screen location.
+         * @param y The x coordinate of the specified on-screen location.
+         * @return  The offset to be added to the index of the current stack.
+         */
+        int getStackIndexDeltaAt(float x, float y) {
+            int delta = 0;
+            if (x < getCurrentStackLeft()) {
+                delta = -1;
+            } else if (x > getCurrentStackLeft() + getWidth()) {
+                delta = 1;
             }
+
+            // Tabs are counted from left to right in LTR mode, but from right to left in RTL mode.
+            if (LocalizationUtils.isLayoutRtl()) delta *= -1;
+
+            return delta;
         }
 
+        /**
+         * @return The current x coordinate for the left edge of the first stack (right edge if in
+         * RTL mode).
+         */
         float getStack0Left() {
-            return LocalizationUtils.isLayoutRtl()
-                    ? getInnerMargin() - getClampedRenderedScrollOffset() * getFullScrollDistance()
-                    : getClampedRenderedScrollOffset() * getFullScrollDistance();
+            float stack0LeftLtr = getClampedRenderedScrollOffset() * getFullScrollDistance();
+            if (mStacks.size() > 2) {
+                // If we have one or two stacks, we only show a margin on the right side of the left
+                // stack and on the left side of the right stack. But if we have three or more
+                // stacks, we put a margin on both sides
+                stack0LeftLtr += getInnerMargin() / 2;
+            }
+
+            if (LocalizationUtils.isLayoutRtl()) return getInnerMargin() - stack0LeftLtr;
+
+            return stack0LeftLtr;
+        }
+
+        /**
+         * @return The current x coordinate for the left edge of the current stack (actually the
+         * right edge if in RTL mode).
+         */
+        float getCurrentStackLeft() {
+            float offset = getClampedRenderedScrollOffset() + getTabStackIndex();
+            if (mStacks.size() > 2) {
+                return offset * getFullScrollDistance() + getInnerMargin() / 2;
+            }
+
+            // Note: getInnerMargin() is zero if there's only one stack.
+            boolean isRightStack = (getTabStackIndex() == 1) ^ LocalizationUtils.isLayoutRtl();
+            return offset * getFullScrollDistance() + (isRightStack ? getInnerMargin() : 0);
         }
 
         float getWidth() {
@@ -941,9 +988,10 @@
         }
 
         @Override
-        int getStackIndexAt(float x, float y) {
-            float separation = getStack0Top() + getHeight();
-            return y < separation ? 0 : 1;
+        int getStackIndexDeltaAt(float x, float y) {
+            if (y < getCurrentStackTop()) return -1;
+            if (y > getCurrentStackTop() + getHeight()) return 1;
+            return 0;
         }
 
         @Override
@@ -957,6 +1005,20 @@
                     + getTopHeightOffset();
         }
 
+        /**
+         * @return The current y coordinate for the top edge of the current stack.
+         */
+        float getCurrentStackTop() {
+            float offset = getClampedRenderedScrollOffset() + getTabStackIndex();
+            if (mStacks.size() > 2) {
+                return offset * getFullScrollDistance() + getInnerMargin() / 2
+                        + getTopHeightOffset();
+            }
+
+            return offset * getFullScrollDistance()
+                    + ((getTabStackIndex() == 1) ? getInnerMargin() : 0) + getTopHeightOffset();
+        }
+
         @Override
         float getWidth() {
             return super.getHeight();
@@ -992,18 +1054,30 @@
         }
     }
 
+    /**
+     * Scrolls the tab stacks by amount delta (clamped so that it's not possible to scroll past the
+     * last stack in either direciton). Positive delta corresponds to increasing the x coordinate
+     * in portrait mode (in both LTR and RTL modes), or increasing the y coordinate in landscape
+     * mode.
+     * @param delta The amount to scroll by.
+     */
     private void scrollStacks(float delta) {
         cancelAnimation(this, Property.STACK_SNAP);
         float fullDistance = getFullScrollDistance();
         mScrollIndexOffset += MathUtils.flipSignIf(delta / fullDistance,
                 getOrientation() == Orientation.PORTRAIT && LocalizationUtils.isLayoutRtl());
-        mRenderedScrollOffset = MathUtils.clamp(mScrollIndexOffset, 0, -1);
+        mRenderedScrollOffset = MathUtils.clamp(mScrollIndexOffset, 0, -(mStacks.size() - 1));
         requestStackUpdate();
     }
 
-    private void flingStacks(boolean toIncognito) {
+    /**
+     * Scrolls over to the tab stack at the specified index, and records that it's now the current
+     * tab stack.
+     * @param index The index of the newly-selected tab stack.
+     */
+    private void flingStacks(int index) {
         // velocityX is measured in pixel per second.
-        setActiveStackState(toIncognito);
+        setActiveStackState(index);
         finishScrollStacks();
         requestStackUpdate();
     }
@@ -1148,13 +1222,10 @@
     /**
      * Sets the active tab stack.
      *
-     * @param isIncognito True if the model to select is incognito.
-     * @return Whether the tab stack index passed in differed from the currently selected stack.
+     * @param stackIndex Index of the tab stack to be made active.
      */
-    public boolean setActiveStackState(boolean isIncognito) {
-        if (isIncognito == mTabModelSelector.isIncognitoSelected()) return false;
-        mTemporarySelectedStack = isIncognito ? INCOGNITO_STACK_INDEX : NORMAL_STACK_INDEX;
-        return true;
+    public void setActiveStackState(int stackIndex) {
+        mTemporarySelectedStack = stackIndex;
     }
 
     private void resetScrollData() {
@@ -1162,9 +1233,16 @@
         mRenderedScrollOffset = mScrollIndexOffset;
     }
 
+    /**
+     * @return The distance between two neighboring tab stacks.
+     */
     private float getFullScrollDistance() {
         float distance = getOrientation() == Orientation.PORTRAIT ? getWidth()
                                                                   : getHeightMinusBrowserControls();
+        if (mStacks.size() > 2) {
+            return distance - getViewportParameters().getInnerMargin();
+        }
+
         return distance - 2 * getViewportParameters().getInnerMargin();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index cdb0118..5b618b5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -322,10 +322,16 @@
                 break;
             case FROM_CHROME_UI:
             case FROM_LONGPRESS_FOREGROUND:
-            case FROM_LONGPRESS_BACKGROUND:
             case FROM_LAUNCHER_SHORTCUT:
                 transition = PageTransition.AUTO_TOPLEVEL;
                 break;
+            case FROM_LONGPRESS_BACKGROUND:
+                // On low end devices tabs are backgrounded in a frozen state, so we set the
+                // transition type to RELOAD to avoid handling intents when the tab is foregrounded.
+                // (https://crbug.com/758027)
+                transition = SysUtils.isLowEndDevice() ? PageTransition.RELOAD
+                                                       : PageTransition.AUTO_TOPLEVEL;
+                break;
             default:
                 assert false;
                 break;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
index be08b58..28cce39 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/PopupTest.java
@@ -26,8 +26,10 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content.browser.test.util.DOMUtils;
 import org.chromium.content.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 
@@ -46,6 +48,9 @@
 
     private static final String POPUP_HTML_PATH = "/chrome/test/data/android/popup_test.html";
 
+    private static final String METADATA_FOR_ABUSIVE_ENFORCEMENT =
+            "{\"matches\":[{\"threat_type\":\"13\",\"sf_absv\":\"\"}]}";
+
     private String mPopupHtmlUrl;
     private EmbeddedTestServer mTestServer;
 
@@ -55,6 +60,10 @@
 
     @Before
     public void setUp() throws Exception {
+        // Create a new temporary instance to ensure the Class is loaded. Otherwise we will get a
+        // ClassNotFoundException when trying to instantiate during startup.
+        SafeBrowsingApiBridge.setSafeBrowsingHandlerType(
+                new MockSafeBrowsingApiHandler().getClass());
         mActivityTestRule.startMainActivityOnBlankPage();
 
         ThreadUtils.runOnUiThread(() -> Assert.assertTrue(getNumInfobarsShowing() == 0));
@@ -66,6 +75,7 @@
     @After
     public void tearDown() throws Exception {
         mTestServer.stopAndDestroyServer();
+        MockSafeBrowsingApiHandler.clearMockResponses();
     }
 
     @Test
@@ -79,6 +89,38 @@
     @Test
     @MediumTest
     @Feature({"Popup"})
+    public void testSafeGestureTabNotBlocked() throws Exception {
+        final TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
+
+        String url = mTestServer.getURL("/chrome/test/data/android/popup_on_click.html");
+
+        mActivityTestRule.loadUrl(url);
+        CriteriaHelper.pollUiThread(Criteria.equals(0, () -> getNumInfobarsShowing()));
+        DOMUtils.clickNode(
+                mActivityTestRule.getActivity().getActivityTab().getContentViewCore(), "link");
+        CriteriaHelper.pollUiThread(Criteria.equals(0, () -> getNumInfobarsShowing()));
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Popup"})
+    public void testAbusiveGesturePopupBlocked() throws Exception {
+        final TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
+
+        String url = mTestServer.getURL("/chrome/test/data/android/popup_on_click.html");
+        MockSafeBrowsingApiHandler.addMockResponse(url, METADATA_FOR_ABUSIVE_ENFORCEMENT);
+
+        mActivityTestRule.loadUrl(url);
+        CriteriaHelper.pollUiThread(Criteria.equals(0, () -> getNumInfobarsShowing()));
+        DOMUtils.clickNode(
+                mActivityTestRule.getActivity().getActivityTab().getContentViewCore(), "link");
+        CriteriaHelper.pollUiThread(Criteria.equals(1, () -> getNumInfobarsShowing()));
+        Assert.assertEquals(1, selector.getTotalTabCount());
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Popup"})
     @FlakyTest(message = "crbug.com/771103")
     public void testPopupWindowsAppearWhenAllowed() throws Exception {
         final TabModelSelector selector = mActivityTestRule.getActivity().getTabModelSelector();
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 9d09a9d0..448e077 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-67.0.3366.0_rc-r1.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-67.0.3366.3_rc-r1.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 00d201d..7a197fed 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -34,8 +34,11 @@
 
     # Alternative squarer content_paste icon optimised for display at 18x18dip.
     # Currently only used in the Page Info bubble.
-    "page_info_content_paste.icon",
+    "back_arrow_touch.icon",
     "bookmarkbar_touch_overflow.icon",
+    "browser_tools_error_touch.icon",
+    "browser_tools_touch.icon",
+    "browser_tools_update_touch.icon",
     "content_paste.icon",
     "cookie.icon",
     "crashed_tab.icon",
@@ -51,6 +54,7 @@
     "folder.icon",
     "folder_supervised.1x.icon",
     "folder_supervised.icon",
+    "forward_arrow_touch.icon",
     "globe.icon",
     "google_g_logo.icon",
     "google_pay_logo_with_vertical_separator.icon",
@@ -65,16 +69,20 @@
     "my_location.icon",
     "navigate_home.1x.icon",
     "navigate_home.icon",
+    "navigate_home_touch.icon",
     "navigate_stop.1x.icon",
     "navigate_stop.icon",
+    "navigate_stop_touch.icon",
     "new_tab_button_incognito.icon",
     "new_tab_button_plus.icon",
     "overflow_chevron.1x.icon",
     "overflow_chevron.icon",
+    "page_info_content_paste.icon",
     "paintbrush.icon",
     "photo.icon",
     "photo_camera.icon",
     "profile_switcher_outline.icon",
+    "reload_touch.icon",
     "remove.icon",
     "remove_box.icon",
     "sad_tab.icon",
@@ -93,10 +101,10 @@
     "tab.icon",
     "tab_audio.1x.icon",
     "tab_audio.icon",
-    "tab_audio_rounded.icon",
     "tab_audio_muting.1x.icon",
     "tab_audio_muting.icon",
     "tab_audio_muting_rounded.icon",
+    "tab_audio_rounded.icon",
     "tab_bluetooth_connected.icon",
     "tab_close_button_touch.icon",
     "tab_close_button_touch_incognito.icon",
diff --git a/chrome/app/vector_icons/back_arrow_touch.icon b/chrome/app/vector_icons/back_arrow_touch.icon
new file mode 100644
index 0000000..974a7c83
--- /dev/null
+++ b/chrome/app/vector_icons/back_arrow_touch.icon
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12.91f, 18.59f,
+R_LINE_TO, 0.09f, -0.09f,
+LINE_TO, 7.49f, 13,
+H_LINE_TO, 19,
+R_ARC_TO, 1, 1, 0, 0, 0, 0, -2,
+H_LINE_TO, 7.49f,
+LINE_TO, 13, 5.49f,
+R_LINE_TO, -0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 0, -1.32f, -1.32f,
+R_H_LINE_TO, 0,
+LINE_TO, 11.51f, 4,
+LINE_TO, 4, 11.51f,
+R_LINE_TO, 0.09f, 0.09f,
+R_ARC_TO, 1, 1, 0, 0, 0, 0, 0.82f,
+LINE_TO, 4, 12.5f,
+LINE_TO, 11.51f, 20,
+R_LINE_TO, 0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 0, 1.32f, -1.32f,
+R_V_LINE_TO, 0,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/browser_tools_error_touch.icon b/chrome/app/vector_icons/browser_tools_error_touch.icon
new file mode 100644
index 0000000..2ae9ce0c
--- /dev/null
+++ b/chrome/app/vector_icons/browser_tools_error_touch.icon
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12, 22,
+R_CUBIC_TO, 5.52f, 0, 10, -4.48f, 10, -10,
+CUBIC_TO_SHORTHAND, 17.52f, 2, 12, 2,
+CUBIC_TO_SHORTHAND, 2, 6.48f, 2, 12,
+R_CUBIC_TO, 0, 5.52f, 4.48f, 10, 10, 10,
+CLOSE,
+R_MOVE_TO, 1, -9,
+R_H_LINE_TO, -2,
+V_LINE_TO, 7,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 6,
+CLOSE,
+R_MOVE_TO, 0, 4,
+R_H_LINE_TO, -2,
+R_V_LINE_TO, -2,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 2,
+CLOSE,
+MOVE_TO, 0, 0,
+R_H_LINE_TO, 24,
+R_V_LINE_TO, 24,
+H_LINE_TO, 0,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/browser_tools_touch.icon b/chrome/app/vector_icons/browser_tools_touch.icon
new file mode 100644
index 0000000..8e92a38
--- /dev/null
+++ b/chrome/app/vector_icons/browser_tools_touch.icon
@@ -0,0 +1,18 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12, 8,
+R_ARC_TO, 2, 2, 0, 1, 0, 0, -4,
+R_ARC_TO, 2, 2, 0, 0, 0, 0, 4,
+CLOSE,
+R_MOVE_TO, 0, 2,
+R_ARC_TO, 2, 2, 0, 1, 0, 0, 4,
+R_ARC_TO, 2, 2, 0, 0, 0, 0, -4,
+CLOSE,
+R_MOVE_TO, 0, 6,
+R_ARC_TO, 2, 2, 0, 1, 0, 0, 4,
+R_ARC_TO, 2, 2, 0, 0, 0, 0, -4,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/browser_tools_update_touch.icon b/chrome/app/vector_icons/browser_tools_update_touch.icon
new file mode 100644
index 0000000..b3a8b9c
--- /dev/null
+++ b/chrome/app/vector_icons/browser_tools_update_touch.icon
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12, 22,
+CUBIC_TO, 6.48f, 22, 2, 17.52f, 2, 12,
+CUBIC_TO_SHORTHAND, 6.48f, 2, 12, 2,
+R_CUBIC_TO, 5.52f, 0, 10, 4.48f, 10, 10,
+R_CUBIC_TO, 0, 5.52f, -4.48f, 10, -10, 10,
+CLOSE,
+R_MOVE_TO, -2, -5,
+R_H_LINE_TO, 4,
+R_V_LINE_TO, -4,
+R_H_LINE_TO, 3.5f,
+LINE_TO, 12, 6.5f,
+LINE_TO, 6.5f, 13,
+H_LINE_TO, 10,
+R_V_LINE_TO, 4,
+CLOSE,
+MOVE_TO, 0, 0,
+R_H_LINE_TO, 24,
+R_V_LINE_TO, 24,
+H_LINE_TO, 0,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/forward_arrow_touch.icon b/chrome/app/vector_icons/forward_arrow_touch.icon
new file mode 100644
index 0000000..b7bb8ce
--- /dev/null
+++ b/chrome/app/vector_icons/forward_arrow_touch.icon
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 11.09f, 18.59f,
+LINE_TO, 11, 18.51f,
+LINE_TO, 16.51f, 13,
+H_LINE_TO, 5,
+R_ARC_TO, 1, 1, 0, 0, 1, 0, -2,
+R_H_LINE_TO, 11.51f,
+LINE_TO, 11, 5.49f,
+R_LINE_TO, 0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, 1.32f, -1.32f,
+R_H_LINE_TO, 0,
+LINE_TO, 12.49f, 4,
+LINE_TO, 20, 11.51f,
+R_LINE_TO, -0.09f, 0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, 0, 0.82f,
+R_LINE_TO, 0.09f, 0.09f,
+LINE_TO, 12.49f, 20,
+R_LINE_TO, -0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, -1.32f, -1.32f,
+R_V_LINE_TO, 0,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/navigate_home_touch.icon b/chrome/app/vector_icons/navigate_home_touch.icon
new file mode 100644
index 0000000..d266a1e
--- /dev/null
+++ b/chrome/app/vector_icons/navigate_home_touch.icon
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12.87f, 3.54f,
+CUBIC_TO, 12.67f, 3.33f, 12.33f, 3, 12, 3,
+R_CUBIC_TO, -0.33f, 0, -0.67f, 0.33f, -0.87f, 0.54f,
+R_LINE_TO, -7.89f, 7.84f,
+ARC_TO, 0.98f, 0.98f, 0, 0, 0, 3, 12,
+R_ARC_TO, 1, 1, 0, 0, 0, 1, 1,
+R_CUBIC_TO, 0.24f, 0, 0.45f, -0.09f, 0.62f, -0.24f,
+R_LINE_TO, 0.38f, -0.35f,
+V_LINE_TO, 19,
+R_ARC_TO, 2, 2, 0, 0, 0, 2, 2,
+R_H_LINE_TO, 10,
+R_ARC_TO, 2, 2, 0, 0, 0, 2, -2,
+R_V_LINE_TO, -6.59f,
+R_LINE_TO, 0.38f, 0.35f,
+ARC_TO, 0.98f, 0.98f, 0, 0, 0, 20, 13,
+R_ARC_TO, 1, 1, 0, 0, 0, 1, -1,
+R_ARC_TO, 0.98f, 0.98f, 0, 0, 0, -0.24f, -0.62f,
+R_CUBIC_TO, 0, 0, -5.91f, -5.88f, -7.89f, -7.84f,
+CLOSE,
+MOVE_TO, 7, 19,
+R_V_LINE_TO, -8.85f,
+LINE_TO, 12, 5,
+R_LINE_TO, 5, 5.15f,
+V_LINE_TO, 19,
+H_LINE_TO, 7,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/navigate_stop_touch.icon b/chrome/app/vector_icons/navigate_stop_touch.icon
new file mode 100644
index 0000000..1ef5b4f
--- /dev/null
+++ b/chrome/app/vector_icons/navigate_stop_touch.icon
@@ -0,0 +1,32 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 17.51f, 5,
+R_LINE_TO, 0.09f, 0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, 1.32f, 1.32f,
+R_LINE_TO, 0.09f, 0.09f,
+LINE_TO, 14.49f, 11,
+R_LINE_TO, -0.99f, 1,
+R_LINE_TO, 0.99f, 1,
+LINE_TO, 19, 17.51f,
+R_LINE_TO, -0.09f, 0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, -1.32f, 1.32f,
+R_LINE_TO, -0.09f, 0.09f,
+LINE_TO, 12, 13.5f,
+LINE_TO, 6.49f, 19,
+R_LINE_TO, -0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, -1.32f, -1.32f,
+LINE_TO, 5, 17.51f,
+LINE_TO, 9.51f, 13,
+R_LINE_TO, 0.99f, -1,
+R_LINE_TO, -0.99f, -1,
+LINE_TO, 5, 6.49f,
+R_LINE_TO, 0.09f, -0.09f,
+R_ARC_TO, 1, 1, 0, 0, 1, 1.32f, -1.32f,
+LINE_TO, 6.5f, 5,
+LINE_TO, 12, 10.51f,
+LINE_TO, 17.51f, 5,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/app/vector_icons/reload_touch.icon b/chrome/app/vector_icons/reload_touch.icon
new file mode 100644
index 0000000..0e3d14b
--- /dev/null
+++ b/chrome/app/vector_icons/reload_touch.icon
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 17.68f, 6.32f,
+LINE_TO, 20, 4,
+R_V_LINE_TO, 7,
+R_H_LINE_TO, -7,
+R_LINE_TO, 3.27f, -3.27f,
+ARC_TO, 6.01f, 6.01f, 0, 0, 0, 12.04f, 6,
+R_CUBIC_TO, -3.33f, 0, -6.03f, 2.69f, -6.03f, 6,
+R_CUBIC_TO, 0, 3.31f, 2.7f, 6, 6.03f, 6,
+R_CUBIC_TO, 2.62f, 0, 4.71f, -1.62f, 5.47f, -3.89f,
+R_LINE_TO, 0.11f, -0.01f,
+R_ARC_TO, 1, 1, 0, 0, 1, 0.89f, -0.6f,
+R_CUBIC_TO, 0.44f, 0, 1, 0.32f, 1, 1,
+R_ARC_TO, 1, 1, 0, 0, 1, -0.09f, 0.41f,
+R_LINE_TO, 0.05f, 0,
+CUBIC_TO, 18.26f, 17.84f, 15.41f, 20, 12.04f, 20,
+CUBIC_TO, 7.6f, 20, 4, 16.42f, 4, 12,
+R_CUBIC_TO, 0, -4.42f, 3.6f, -8, 8.04f, -8,
+R_CUBIC_TO, 2.2f, 0, 4.2f, 0.88f, 5.65f, 2.32f,
+CLOSE,
+END
\ No newline at end of file
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 848151f..40b6df11 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -45,6 +45,7 @@
   "+media/capture",
   "+media/midi",  # For midi switches
   "+media/mojo",  # For mojo media services.
+  "+media/renderers",
   "+ppapi/c",  # For various types.
   "+ppapi/host",
   "+ppapi/proxy",
@@ -74,6 +75,7 @@
   "+services/ui/common",
   "+services/ui/public",
   "+services/ui/service.h",
+  "+services/viz/privileged",
   "+skia/ext",
   "+third_party/boringssl/src/include",
   "+third_party/crashpad",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9207a97..6023d43b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -695,6 +695,12 @@
      switches::kEnableUseZoomForDSF, "false"},
 };
 
+const FeatureEntry::Choice kSiteIsolationTrialOptOutChoices[] = {
+    {flag_descriptions::kSiteIsolationTrialOptOutChoiceDefault, "", ""},
+    {flag_descriptions::kSiteIsolationTrialOptOutChoiceOptOut,
+     switches::kDisableSiteIsolationTrials, ""},
+};
+
 const FeatureEntry::Choice kTLS13VariantChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kTLS13VariantDisabled, switches::kTLS13Variant,
@@ -1466,6 +1472,10 @@
      SINGLE_VALUE_TYPE_AND_VALUE(switches::kAllowNaClSocketAPI, "*")},
 #endif  // ENABLE_PLUGINS
 #if defined(OS_CHROMEOS)
+    {"ash-enable-cursor-motion-blur",
+     flag_descriptions::kEnableCursorMotionBlurName,
+     flag_descriptions::kEnableCursorMotionBlurDescription, kOsAll,
+     SINGLE_VALUE_TYPE(ash::switches::kAshEnableCursorMotionBlur)},
     {"ash-enable-docked-magnifier",
      flag_descriptions::kEnableDockedMagnifierName,
      flag_descriptions::kEnableDockedMagnifierDescription, kOsCrOS,
@@ -2025,9 +2035,13 @@
     {"committed-interstitials", flag_descriptions::kCommittedInterstitialsName,
      flag_descriptions::kCommittedInterstitialsDescription, kOsAll,
      SINGLE_VALUE_TYPE(switches::kCommittedInterstitials)},
-    {"enable-site-per-process", flag_descriptions::kSitePerProcessName,
-     flag_descriptions::kSitePerProcessDescription, kOsAll,
+    {"enable-site-per-process", flag_descriptions::kStrictSiteIsolationName,
+     flag_descriptions::kStrictSiteIsolationDescription, kOsAll,
      SINGLE_VALUE_TYPE(switches::kSitePerProcess)},
+    {"site-isolation-trial-opt-out",
+     flag_descriptions::kSiteIsolationTrialOptOutName,
+     flag_descriptions::kSiteIsolationTrialOptOutDescription, kOsAll,
+     MULTI_VALUE_TYPE(kSiteIsolationTrialOptOutChoices)},
     {"enable-top-document-isolation",
      flag_descriptions::kTopDocumentIsolationName,
      flag_descriptions::kTopDocumentIsolationDescription, kOsAll,
@@ -3590,7 +3604,7 @@
     {"ash-enable-persistent-window-bounds",
      flag_descriptions::kAshEnablePersistentWindowBoundsName,
      flag_descriptions::kAshEnablePersistentWindowBoundsDescription, kOsCrOS,
-     SINGLE_VALUE_TYPE(ash::switches::kAshEnablePersistentWindowBounds)},
+     FEATURE_VALUE_TYPE(ash::features::kPersistentWindowBounds)},
 #endif  // OS_CHROMEOS
 
     {"clipboard-content-setting",
diff --git a/chrome/browser/android/vr/vr_gl_thread.cc b/chrome/browser/android/vr/vr_gl_thread.cc
index d2e7bfb..76f27f2d 100644
--- a/chrome/browser/android/vr/vr_gl_thread.cc
+++ b/chrome/browser/android/vr/vr_gl_thread.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/vr/model/assets.h"
 #include "chrome/browser/vr/model/omnibox_suggestions.h"
 #include "chrome/browser/vr/model/toolbar_state.h"
+#include "chrome/browser/vr/sounds_manager_audio_delegate.h"
 #include "chrome/browser/vr/ui.h"
 #include "chrome/common/chrome_features.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -66,8 +67,12 @@
       !keyboard_delegate_ ? nullptr : keyboard_delegate_.get();
   if (!keyboard_delegate)
     ui_initial_state_.needs_keyboard_update = true;
+
+  audio_delegate_ = std::make_unique<SoundsManagerAudioDelegate>();
+
   auto ui = std::make_unique<Ui>(this, this, keyboard_delegate,
-                                 text_input_delegate_.get(), ui_initial_state_);
+                                 text_input_delegate_.get(),
+                                 audio_delegate_.get(), ui_initial_state_);
   if (keyboard_enabled) {
     text_input_delegate_->SetRequestFocusCallback(
         base::BindRepeating(&Ui::RequestFocus, base::Unretained(ui.get())));
@@ -91,6 +96,7 @@
 }
 
 void VrGLThread::CleanUp() {
+  audio_delegate_.reset();
   vr_shell_gl_.reset();
 }
 
diff --git a/chrome/browser/android/vr/vr_gl_thread.h b/chrome/browser/android/vr/vr_gl_thread.h
index 3c39270a..24315371 100644
--- a/chrome/browser/android/vr/vr_gl_thread.h
+++ b/chrome/browser/android/vr/vr_gl_thread.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/vr/browser_ui_interface.h"
 #include "chrome/browser/vr/content_input_delegate.h"
 #include "chrome/browser/vr/model/omnibox_suggestions.h"
+#include "chrome/browser/vr/model/sound_id.h"
 #include "chrome/browser/vr/text_input_delegate.h"
 #include "chrome/browser/vr/ui.h"
 #include "chrome/browser/vr/ui_browser_interface.h"
@@ -27,6 +28,7 @@
 
 namespace vr {
 
+class AudioDelegate;
 class VrInputConnection;
 class VrShell;
 class VrShellGl;
@@ -131,6 +133,7 @@
   std::unique_ptr<VrShellGl> vr_shell_gl_;
   std::unique_ptr<GvrKeyboardDelegate> keyboard_delegate_;
   std::unique_ptr<TextInputDelegate> text_input_delegate_;
+  std::unique_ptr<AudioDelegate> audio_delegate_;
 
   base::WeakPtr<VrShell> weak_vr_shell_;
   base::WeakPtr<BrowserUiInterface> weak_browser_ui_;
diff --git a/chrome/browser/chromeos/customization/customization_document_unittest.cc b/chrome/browser/chromeos/customization/customization_document_unittest.cc
index 85eef2e93..8907032 100644
--- a/chrome/browser/chromeos/customization/customization_document_unittest.cc
+++ b/chrome/browser/chromeos/customization/customization_document_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/extensions/external_provider_impl.h"
 #include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -202,7 +203,7 @@
     }
 
     TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
-    ServicesCustomizationDocument::RegisterPrefs(local_state_.registry());
+    RegisterLocalState(local_state_.registry());
   }
 
   void TearDown() override {
@@ -265,7 +266,11 @@
         factory.CreateSyncable(registry.get()));
     RegisterUserProfilePrefs(registry.get());
     profile_builder.SetPrefService(std::move(prefs));
-    return profile_builder.Build();
+    std::unique_ptr<TestingProfile> profile = profile_builder.Build();
+    // Make sure we have a Profile Manager.
+    TestingBrowserProcess::GetGlobal()->SetProfileManager(
+        new ProfileManagerWithoutInit(profile->GetPath()));
+    return profile;
   }
 
   network::TestURLLoaderFactory factory_;
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.cc b/chrome/browser/chromeos/smb_client/smb_file_system.cc
index 9500103..de04230c 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.cc
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.cc
@@ -288,9 +288,9 @@
     const base::FilePath& entry_path,
     bool recursive,
     const storage::AsyncFileUtil::StatusCallback& callback) {
-  GetSmbProviderClient()->DeleteEntry(
-      GetMountId(), entry_path, recursive,
-      base::BindOnce(&SmbFileSystem::HandleStatusCallback,
+  GetSmbProviderClient()->GetDeleteList(
+      GetMountId(), entry_path,
+      base::BindOnce(&SmbFileSystem::HandleGetDeleteListCallback,
                      weak_ptr_factory_.GetWeakPtr(), callback));
   return CreateAbortCallback();
 }
@@ -441,6 +441,43 @@
   callback.Run(TranslateError(error), entry_list, false /* has_more */);
 }
 
+void SmbFileSystem::HandleGetDeleteListCallback(
+    storage::AsyncFileUtil::StatusCallback callback,
+    smbprovider::ErrorType list_error,
+    const smbprovider::DeleteListProto& delete_list) {
+  if (delete_list.entries_size() == 0) {
+    // There are no entries to delete.
+    DCHECK_NE(smbprovider::ERROR_OK, list_error);
+    callback.Run(TranslateError(list_error));
+    return;
+  }
+
+  for (int i = 0; i < delete_list.entries_size(); ++i) {
+    const base::FilePath entry_path(delete_list.entries(i));
+    bool is_last_entry = (i == delete_list.entries_size() - 1);
+
+    GetSmbProviderClient()->DeleteEntry(
+        GetMountId(), entry_path, false /* recursive */,
+        base::BindOnce(&SmbFileSystem::HandleDeleteEntryCallback,
+                       weak_ptr_factory_.GetWeakPtr(), callback, list_error,
+                       is_last_entry));
+  }
+}
+
+void SmbFileSystem::HandleDeleteEntryCallback(
+    storage::AsyncFileUtil::StatusCallback callback,
+    smbprovider::ErrorType list_error,
+    bool is_last_entry,
+    smbprovider::ErrorType delete_error) const {
+  if (is_last_entry) {
+    // Only run the callback once.
+    if (list_error != smbprovider::ERROR_OK) {
+      delete_error = list_error;
+    }
+    callback.Run(TranslateError(delete_error));
+  }
+}
+
 void SmbFileSystem::HandleRequestGetMetadataEntryCallback(
     ProvidedFileSystemInterface::MetadataFieldMask fields,
     const ProvidedFileSystemInterface::GetMetadataCallback& callback,
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.h b/chrome/browser/chromeos/smb_client/smb_file_system.h
index d855c88..f17f486 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.h
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.h
@@ -213,6 +213,17 @@
                                      smbprovider::ErrorType error,
                                      const base::ScopedFD& fd) const;
 
+  void HandleGetDeleteListCallback(
+      storage::AsyncFileUtil::StatusCallback callback,
+      smbprovider::ErrorType list_error,
+      const smbprovider::DeleteListProto& delete_list);
+
+  void HandleDeleteEntryCallback(
+      storage::AsyncFileUtil::StatusCallback callback,
+      smbprovider::ErrorType list_error,
+      bool is_last_entry,
+      smbprovider::ErrorType delete_error) const;
+
   int32_t GetMountId() const;
 
   SmbProviderClient* GetSmbProviderClient() const;
diff --git a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
index 50e2219..175f4b2 100644
--- a/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
+++ b/chrome/browser/chromeos/system/tray_accessibility_browsertest.cc
@@ -344,54 +344,6 @@
     return tray()->detailed_menu_->tap_dragging_enabled_;
   }
 
-  bool IsSpokenFeedbackMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->spoken_feedback_view_;
-  }
-
-  bool IsHighContrastMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->high_contrast_view_;
-  }
-
-  bool IsScreenMagnifierMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->screen_magnifier_view_;
-  }
-
-  bool IsLargeCursorMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->large_cursor_view_;
-  }
-
-  bool IsAutoclickMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->autoclick_view_;
-  }
-
-  bool IsVirtualKeyboardMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->virtual_keyboard_view_;
-  }
-
-  bool IsMonoAudioMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->mono_audio_view_;
-  }
-
-  bool IsCaretHighlightMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->caret_highlight_view_;
-  }
-
-  bool IsHighlightMouseCursorMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->highlight_mouse_cursor_view_;
-  }
-
-  bool IsHighlightKeyboardFocusMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->highlight_keyboard_focus_view_;
-  }
-
-  bool IsStickyKeysMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->sticky_keys_view_;
-  }
-
-  bool IsTapDraggingMenuShownOnDetailMenu() const {
-    return tray()->detailed_menu_->tap_dragging_view_;
-  }
-
   // In material design we show the help button but theme it as disabled if
   // it is not possible to load the help page.
   static bool IsHelpAvailableOnDetailMenu() {
@@ -994,6 +946,7 @@
   EXPECT_TRUE(CanCreateMenuItem());
 }
 
+// TODO: Move to ash_unittests.
 IN_PROC_BROWSER_TEST_P(TrayAccessibilityTest, KeepMenuVisibilityOnLockScreen) {
   // Enables high contrast mode.
   EnableHighContrast(true);
@@ -1158,6 +1111,7 @@
   EXPECT_FALSE(AccessibilityManager::Get()->IsSelectToSpeakEnabled());
 }
 
+// TODO: Move to ash_unittests.
 IN_PROC_BROWSER_TEST_P(TrayAccessibilityTest, CheckMarksOnDetailMenu) {
   SetLoginStatus(ash::LoginStatus::NOT_LOGGED_IN);
 
@@ -1668,82 +1622,11 @@
   CloseDetailMenu();
 }
 
-// Flaky failures of IsHelpAvailableOnDetailMenu(). Possible race condition
-// with session state. https://crbug.com/819987
-IN_PROC_BROWSER_TEST_P(TrayAccessibilityTest,
-                       DISABLED_CheckMenuVisibilityOnDetailMenu) {
-  // Except help & settings, others should be kept the same
-  // in LOGIN | NOT LOGIN | LOCKED. https://crbug.com/632107.
-  EXPECT_TRUE(CreateDetailedMenu());
-  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
-  EXPECT_FALSE(IsSelectToSpeakEnabledOnDetailMenu());
-  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHelpAvailableOnDetailMenu());
-  EXPECT_TRUE(IsSettingsAvailableOnDetailMenu());
-  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
-  CloseDetailMenu();
-
-  // Simulate screen lock.
-  session_manager::SessionManager::Get()->SetSessionState(
-      session_manager::SessionState::LOCKED);
-  // Flush to ensure the session state reaches ash and updates login status.
-  SessionControllerClient::FlushForTesting();
-  EXPECT_TRUE(CreateDetailedMenu());
-  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
-  EXPECT_FALSE(IsSelectToSpeakEnabledOnDetailMenu());
-  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
-  EXPECT_FALSE(IsHelpAvailableOnDetailMenu());
-  EXPECT_FALSE(IsSettingsAvailableOnDetailMenu());
-  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
-  CloseDetailMenu();
-
-  // Simulate adding multiprofile user.
-  session_manager::SessionManager::Get()->SetSessionState(
-      session_manager::SessionState::LOGIN_SECONDARY);
-  // Flush to ensure the session state reaches ash and updates login status.
-  SessionControllerClient::FlushForTesting();
-  EXPECT_TRUE(CreateDetailedMenu());
-  EXPECT_TRUE(IsSpokenFeedbackMenuShownOnDetailMenu());
-  EXPECT_FALSE(IsSelectToSpeakEnabledOnDetailMenu());
-  EXPECT_TRUE(IsHighContrastMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsScreenMagnifierMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsAutoclickMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsVirtualKeyboardMenuShownOnDetailMenu());
-  EXPECT_FALSE(IsHelpAvailableOnDetailMenu());
-  EXPECT_FALSE(IsSettingsAvailableOnDetailMenu());
-  EXPECT_TRUE(IsLargeCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsMonoAudioMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsCaretHighlightMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightMouseCursorMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
-  EXPECT_TRUE(IsTapDraggingMenuShownOnDetailMenu());
-  CloseDetailMenu();
-}
-
 // Verify that the accessiblity system detailed menu remains open when an item
 // is selected or deselected.
+// TODO: Move to ash_unittests.
 IN_PROC_BROWSER_TEST_P(TrayAccessibilityTest, DetailMenuRemainsOpen) {
   EXPECT_TRUE(CreateDetailedMenu());
-  ASSERT_TRUE(IsAutoclickMenuShownOnDetailMenu());
 
   ClickAutoclickOnDetailMenu();
   EXPECT_TRUE(IsAutoclickEnabledOnDetailMenu());
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index ef80069..6d87646 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -29,54 +29,50 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
-#include "net/test/url_request/url_request_mock_data_job.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "net/url_request/url_request_filter.h"
-#include "net/url_request/url_request_interceptor.h"
-#include "net/url_request/url_request_job.h"
-#include "net/url_request/url_request_test_job.h"
 
 namespace {
 
 // An interceptor that records count of fetches and client hint headers for
 // requests to https://foo.com/non-existing-image.jpg.
-class ThirdPartyRequestInterceptor : public net::URLRequestInterceptor {
+class ThirdPartyURLLoaderInterceptor {
  public:
-  ThirdPartyRequestInterceptor()
-      : request_count_seen_(0u), client_hints_count_seen_(0u) {}
+  explicit ThirdPartyURLLoaderInterceptor(const GURL intercepted_url)
+      : intercepted_url_(intercepted_url),
+        interceptor_(base::BindRepeating(
+            &ThirdPartyURLLoaderInterceptor::InterceptURLRequest,
+            base::Unretained(this))) {}
 
-  ~ThirdPartyRequestInterceptor() override = default;
-
-  // net::URLRequestInterceptor implementation
-  net::URLRequestJob* MaybeInterceptRequest(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const override {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-
-    net::HttpRequestHeaders headers = request->extra_request_headers();
-
-    request_count_seen_++;
-    if (headers.HasHeader("dpr")) {
-      client_hints_count_seen_++;
-    }
-    if (headers.HasHeader("device-memory")) {
-      client_hints_count_seen_++;
-    }
-    return new net::URLRequestMockDataJob(request, network_delegate, "contents",
-                                          1, false);
-  }
+  ~ThirdPartyURLLoaderInterceptor() = default;
 
   size_t request_count_seen() const { return request_count_seen_; }
 
   size_t client_hints_count_seen() const { return client_hints_count_seen_; }
 
  private:
-  mutable size_t request_count_seen_;
+  bool InterceptURLRequest(
+      content::URLLoaderInterceptor::RequestParams* params) {
+    if (params->url_request.url != intercepted_url_)
+      return false;
 
-  mutable size_t client_hints_count_seen_;
+    request_count_seen_++;
+    if (params->url_request.headers.HasHeader("dpr")) {
+      client_hints_count_seen_++;
+    }
+    if (params->url_request.headers.HasHeader("device-memory")) {
+      client_hints_count_seen_++;
+    }
+    return false;
+  }
 
-  DISALLOW_COPY_AND_ASSIGN(ThirdPartyRequestInterceptor);
+  GURL intercepted_url_;
+
+  size_t request_count_seen_ = 0u;
+
+  size_t client_hints_count_seen_ = 0u;
+
+  content::URLLoaderInterceptor interceptor_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThirdPartyURLLoaderInterceptor);
 };
 
 }  // namespace
@@ -151,17 +147,13 @@
         content::BrowserThread::IO, FROM_HERE,
         base::BindOnce(&chrome_browser_net::SetUrlRequestMocksEnabled, true));
 
-    request_interceptor_ = new ThirdPartyRequestInterceptor();
-    std::unique_ptr<net::URLRequestInterceptor> owned_interceptor(
-        request_interceptor_);
-    content::BrowserThread::PostTask(
-        content::BrowserThread::IO, FROM_HERE,
-        base::BindOnce(&InstallMockInterceptors,
-                       GURL("https://foo.com/non-existing-image.jpg"),
-                       std::move(owned_interceptor)));
+    request_interceptor_ = std::make_unique<ThirdPartyURLLoaderInterceptor>(
+        GURL("https://foo.com/non-existing-image.jpg"));
     base::RunLoop().RunUntilIdle();
   }
 
+  void TearDownOnMainThread() override { request_interceptor_.reset(); }
+
   void SetUpCommandLine(base::CommandLine* cmd) override {
     cmd->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
   }
@@ -241,16 +233,6 @@
   }
 
  private:
-  static void InstallMockInterceptors(
-      const GURL& url,
-      std::unique_ptr<net::URLRequestInterceptor> request_interceptor) {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-    chrome_browser_net::SetUrlRequestMocksEnabled(true);
-
-    net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
-        url, std::move(request_interceptor));
-  }
-
   // Called by |https_server_|.
   void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
     bool is_main_frame_navigation =
@@ -297,8 +279,7 @@
 
   size_t count_client_hints_headers_seen_;
 
-  // Not owned. May be null.
-  ThirdPartyRequestInterceptor* request_interceptor_;
+  std::unique_ptr<ThirdPartyURLLoaderInterceptor> request_interceptor_;
 
   DISALLOW_COPY_AND_ASSIGN(ClientHintsBrowserTest);
 };
diff --git a/chrome/browser/devtools/BUILD.gn b/chrome/browser/devtools/BUILD.gn
index dd7f4e8e..a1264b2e 100644
--- a/chrome/browser/devtools/BUILD.gn
+++ b/chrome/browser/devtools/BUILD.gn
@@ -117,6 +117,7 @@
     "//base",
     "//content/public/browser",
     "//net",
+    "//services/viz/privileged/interfaces/compositing",
     "//third_party/WebKit/public:features",
     "//ui/events:dom_keycode_converter",
   ]
diff --git a/chrome/browser/devtools/devtools_eye_dropper.cc b/chrome/browser/devtools/devtools_eye_dropper.cc
index 742b752..7b1c93b 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.cc
+++ b/chrome/browser/devtools/devtools_eye_dropper.cc
@@ -6,12 +6,15 @@
 
 #include "base/bind.h"
 #include "build/build_config.h"
+#include "cc/paint/skia_paint_canvas.h"
+#include "components/viz/common/features.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/cursor_info.h"
 #include "content/public/common/screen_info.h"
+#include "media/base/limits.h"
 #include "third_party/WebKit/public/platform/WebInputEvent.h"
 #include "third_party/WebKit/public/platform/WebMouseEvent.h"
 #include "third_party/skia/include/core/SkCanvas.h"
@@ -27,6 +30,9 @@
       last_cursor_x_(-1),
       last_cursor_y_(-1),
       host_(nullptr),
+      video_consumer_binding_(this),
+      enable_viz_(
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor)),
       weak_factory_(this) {
   mouse_event_callback_ =
       base::Bind(&DevToolsEyeDropper::HandleMouseEvent, base::Unretained(this));
@@ -44,6 +50,29 @@
 void DevToolsEyeDropper::AttachToHost(content::RenderWidgetHost* host) {
   host_ = host;
   host_->AddMouseEventCallback(mouse_event_callback_);
+
+  // TODO(crbug.com/813929): Use video capturer even if viz is not enabled.
+  if (!enable_viz_)
+    return;
+
+  // Capturing a full-page screenshot can be costly so we shouldn't do it too
+  // often. We can capture at a lower frame rate without hurting the user
+  // experience.
+  constexpr static int kMaxFrameRate = 15;
+
+  // Create and configure the video capturer.
+  video_capturer_ = host_->GetView()->CreateVideoCapturer();
+  video_capturer_->SetResolutionConstraints(
+      gfx::Size(1, 1),
+      gfx::Size(media::limits::kMaxDimension, media::limits::kMaxDimension),
+      false);
+  video_capturer_->SetMinCapturePeriod(base::TimeDelta::FromSeconds(1) /
+                                       kMaxFrameRate);
+
+  // Start video capture.
+  viz::mojom::FrameSinkVideoConsumerPtr consumer;
+  video_consumer_binding_.Bind(mojo::MakeRequest(&consumer));
+  video_capturer_->Start(std::move(consumer));
 }
 
 void DevToolsEyeDropper::DetachFromHost() {
@@ -53,6 +82,8 @@
   content::CursorInfo cursor_info;
   cursor_info.type = blink::WebCursorInfo::kTypePointer;
   host_->SetCursor(cursor_info);
+  video_consumer_binding_.Close();
+  video_capturer_.reset();
   host_ = nullptr;
 }
 
@@ -85,7 +116,7 @@
 }
 
 void DevToolsEyeDropper::UpdateFrame() {
-  if (!host_ || !host_->GetView())
+  if (enable_viz_ || !host_ || !host_->GetView())
     return;
 
   // TODO(miu): This is the wrong size. It's the size of the view on-screen, and
@@ -106,6 +137,7 @@
 }
 
 void DevToolsEyeDropper::FrameUpdated(const SkBitmap& bitmap) {
+  DCHECK(!enable_viz_);
   if (bitmap.drawsNothing())
     return;
   frame_ = bitmap;
@@ -271,3 +303,42 @@
                                    kHotspotOffset * device_scale_factor);
   host_->SetCursor(cursor_info);
 }
+
+void DevToolsEyeDropper::OnFrameCaptured(
+    mojo::ScopedSharedBufferHandle buffer,
+    uint32_t buffer_size,
+    ::media::mojom::VideoFrameInfoPtr info,
+    const gfx::Rect& update_rect,
+    const gfx::Rect& content_rect,
+    viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {
+  if (!buffer.is_valid()) {
+    callbacks->Done();
+    return;
+  }
+  mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
+  if (!mapping) {
+    DLOG(ERROR) << "Shared memory mapping failed.";
+    return;
+  }
+  scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
+      info->pixel_format, info->coded_size, info->visible_rect,
+      info->visible_rect.size(), static_cast<uint8_t*>(mapping.get()),
+      buffer_size, info->timestamp);
+  if (!frame)
+    return;
+  frame->AddDestructionObserver(base::BindOnce(
+      [](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping)));
+
+  SkBitmap skbitmap;
+  skbitmap.allocN32Pixels(info->visible_rect.width(),
+                          info->visible_rect.height());
+  cc::SkiaPaintCanvas canvas(skbitmap);
+  video_renderer_.Copy(frame, &canvas, media::Context3D());
+
+  frame_ = skbitmap;
+  UpdateCursor();
+}
+
+void DevToolsEyeDropper::OnTargetLost(const viz::FrameSinkId& frame_sink_id) {}
+
+void DevToolsEyeDropper::OnStopped() {}
diff --git a/chrome/browser/devtools/devtools_eye_dropper.h b/chrome/browser/devtools/devtools_eye_dropper.h
index 4956dce6..cf1ce5b0 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.h
+++ b/chrome/browser/devtools/devtools_eye_dropper.h
@@ -9,13 +9,17 @@
 #include "base/macros.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "media/renderers/paint_canvas_video_renderer.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace blink {
 class WebMouseEvent;
 }
 
-class DevToolsEyeDropper : public content::WebContentsObserver {
+class DevToolsEyeDropper : public content::WebContentsObserver,
+                           public viz::mojom::FrameSinkVideoConsumer {
  public:
   typedef base::Callback<void(int, int, int, int)> EyeDropperCallback;
 
@@ -40,12 +44,27 @@
   bool HandleMouseEvent(const blink::WebMouseEvent& event);
   void UpdateCursor();
 
+  // viz::mojom::FrameSinkVideoConsumer implementation.
+  void OnFrameCaptured(
+      mojo::ScopedSharedBufferHandle buffer,
+      uint32_t buffer_size,
+      ::media::mojom::VideoFrameInfoPtr info,
+      const gfx::Rect& update_rect,
+      const gfx::Rect& content_rect,
+      viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) override;
+  void OnTargetLost(const viz::FrameSinkId& frame_sink_id) override;
+  void OnStopped() override;
+
   EyeDropperCallback callback_;
   SkBitmap frame_;
   int last_cursor_x_;
   int last_cursor_y_;
   content::RenderWidgetHost::MouseEventCallback mouse_event_callback_;
   content::RenderWidgetHost* host_;
+  viz::mojom::FrameSinkVideoCapturerPtr video_capturer_;
+  mojo::Binding<viz::mojom::FrameSinkVideoConsumer> video_consumer_binding_;
+  const bool enable_viz_;
+  media::PaintCanvasVideoRenderer video_renderer_;
   base::WeakPtrFactory<DevToolsEyeDropper> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DevToolsEyeDropper);
diff --git a/chrome/browser/download/download_service_factory.cc b/chrome/browser/download/download_service_factory.cc
index 4de785d9b..9776dc5 100644
--- a/chrome/browser/download/download_service_factory.cc
+++ b/chrome/browser/download/download_service_factory.cc
@@ -29,7 +29,6 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
-#include "net/url_request/url_request_context_getter.h"
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/download/service/download_task_scheduler.h"
@@ -79,10 +78,6 @@
   // Build in memory download service for incognito profile.
   if (context->IsOffTheRecord() &&
       base::FeatureList::IsEnabled(download::kDownloadServiceIncognito)) {
-    scoped_refptr<net::URLRequestContextGetter> url_request_context =
-        content::BrowserContext::GetDefaultStoragePartition(
-            Profile::FromBrowserContext(context))
-            ->GetURLRequestContext();
     content::BrowserContext::BlobContextGetter blob_context_getter =
         content::BrowserContext::GetBlobStorageContext(context);
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner =
@@ -90,8 +85,8 @@
             content::BrowserThread::IO);
 
     return download::BuildInMemoryDownloadService(
-        context, std::move(clients), base::FilePath(), url_request_context,
-        blob_context_getter, io_task_runner);
+        context, std::move(clients), base::FilePath(), blob_context_getter,
+        io_task_runner);
   } else {
     // Build download service for normal profile.
     base::FilePath storage_dir;
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 2fbe7a4..fdcee0a 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -853,6 +853,7 @@
     "//components/sync_sessions",
     "//components/translate/core/browser",
     "//components/undo",
+    "//components/unzip_service/public/cpp",
     "//components/update_client",
     "//components/url_matcher",
     "//components/user_prefs",
diff --git a/chrome/browser/extensions/DEPS b/chrome/browser/extensions/DEPS
index 5e0ad1d..73dab839 100644
--- a/chrome/browser/extensions/DEPS
+++ b/chrome/browser/extensions/DEPS
@@ -37,6 +37,9 @@
     # TODO(mash): Remove. http://crbug.com/678705
     "+ash/shell.h",
   ],
+  "zipfile_installer_unittest.cc": [
+    "+services/data_decoder",
+  ],
   "test_extension_system.cc": [
     "+services/data_decoder",
   ],
diff --git a/chrome/browser/extensions/zipfile_installer_unittest.cc b/chrome/browser/extensions/zipfile_installer_unittest.cc
index b347b3f7..43994cc 100644
--- a/chrome/browser/extensions/zipfile_installer_unittest.cc
+++ b/chrome/browser/extensions/zipfile_installer_unittest.cc
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <vector>
+
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
@@ -19,12 +22,16 @@
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/unzip_service/unzip_service.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
+#include "services/data_decoder/data_decoder_service.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_CHROMEOS)
@@ -81,12 +88,27 @@
   base::Closure quit_closure;
 };
 
+struct UnzipFileFilterTestCase {
+  const base::FilePath::CharType* input;
+  const bool should_unzip;
+};
+
 }  // namespace
 
 class ZipFileInstallerTest : public testing::Test {
  public:
   ZipFileInstallerTest()
-      : browser_threads_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
+      : browser_threads_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+    service_manager::TestConnectorFactory::NameToServiceMap services;
+    services.insert(std::make_pair("data_decoder",
+                                   data_decoder::DataDecoderService::Create()));
+    services.insert(
+        std::make_pair("unzip_service", unzip::UnzipService::CreateService()));
+    test_connector_factory_ =
+        service_manager::TestConnectorFactory::CreateForServices(
+            std::move(services));
+    connector_ = test_connector_factory_->CreateConnector();
+  }
 
   void SetUp() override {
     extensions::LoadErrorReporter::Init(/*enable_noisy_errors=*/false);
@@ -121,8 +143,8 @@
                         .AppendASCII("zipfile_installer")
                         .AppendASCII(zip_name);
     ASSERT_TRUE(base::PathExists(original_path)) << original_path.value();
-
     zipfile_installer_ = ZipFileInstaller::Create(
+        connector_.get(),
         MakeRegisterInExtensionServiceCallback(extension_service_));
 
     base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -131,6 +153,17 @@
     observer_.WaitForInstall(expect_error);
   }
 
+  void RunZipFileFilterTest(
+      const std::vector<UnzipFileFilterTestCase>& cases,
+      base::RepeatingCallback<bool(const base::FilePath&)>& filter) {
+    for (size_t i = 0; i < cases.size(); ++i) {
+      base::FilePath input(cases[i].input);
+      bool observed = filter.Run(input);
+      EXPECT_EQ(cases[i].should_unzip, observed)
+          << "i: " << i << ", input: " << input.value();
+    }
+  }
+
  protected:
   scoped_refptr<ZipFileInstaller> zipfile_installer_;
 
@@ -148,6 +181,11 @@
   // ChromeOS needs a user manager to instantiate an extension service.
   chromeos::ScopedTestUserManager test_user_manager_;
 #endif
+
+ private:
+  std::unique_ptr<service_manager::TestConnectorFactory>
+      test_connector_factory_;
+  std::unique_ptr<service_manager::Connector> connector_;
 };
 
 TEST_F(ZipFileInstallerTest, GoodZip) {
@@ -165,4 +203,49 @@
   EXPECT_EQ(observer_.last_extension_installed, kIdForPublicKey);
 }
 
+TEST_F(ZipFileInstallerTest, NonTheme_FileExtractionFilter) {
+  const std::vector<UnzipFileFilterTestCase> cases = {
+      {FILE_PATH_LITERAL("foo"), true},
+      {FILE_PATH_LITERAL("foo.nexe"), true},
+      {FILE_PATH_LITERAL("foo.dll"), true},
+      {FILE_PATH_LITERAL("foo.jpg.exe"), false},
+      {FILE_PATH_LITERAL("foo.exe"), false},
+      {FILE_PATH_LITERAL("foo.EXE"), false},
+      {FILE_PATH_LITERAL("file_without_extension"), true},
+  };
+  base::RepeatingCallback<bool(const base::FilePath&)> filter =
+      base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, false);
+  RunZipFileFilterTest(cases, filter);
+}
+
+TEST_F(ZipFileInstallerTest, Theme_FileExtractionFilter) {
+  const std::vector<UnzipFileFilterTestCase> cases = {
+      {FILE_PATH_LITERAL("image.jpg"), true},
+      {FILE_PATH_LITERAL("IMAGE.JPEG"), true},
+      {FILE_PATH_LITERAL("test/image.bmp"), true},
+      {FILE_PATH_LITERAL("test/IMAGE.gif"), true},
+      {FILE_PATH_LITERAL("test/image.WEBP"), true},
+      {FILE_PATH_LITERAL("test/dir/file.image.png"), true},
+      {FILE_PATH_LITERAL("manifest.json"), true},
+      {FILE_PATH_LITERAL("other.html"), false},
+      {FILE_PATH_LITERAL("file_without_extension"), true},
+  };
+  base::RepeatingCallback<bool(const base::FilePath&)> filter =
+      base::BindRepeating(&ZipFileInstaller::ShouldExtractFile, true);
+  RunZipFileFilterTest(cases, filter);
+}
+
+TEST_F(ZipFileInstallerTest, ManifestExtractionFilter) {
+  const std::vector<UnzipFileFilterTestCase> cases = {
+      {FILE_PATH_LITERAL("manifest.json"), true},
+      {FILE_PATH_LITERAL("MANIFEST.JSON"), true},
+      {FILE_PATH_LITERAL("test/manifest.json"), false},
+      {FILE_PATH_LITERAL("manifest.json/test"), false},
+      {FILE_PATH_LITERAL("other.file"), false},
+  };
+  base::RepeatingCallback<bool(const base::FilePath&)> filter =
+      base::BindRepeating(&ZipFileInstaller::IsManifestFile);
+  RunZipFileFilterTest(cases, filter);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 81e265c4..b5a72027 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -347,6 +347,10 @@
 const char kEnableClientLoFiDescription[] =
     "Enable showing low fidelity images on some pages on slow networks.";
 
+const char kEnableCursorMotionBlurName[] = "Enable Cursor Motion Blur";
+const char kEnableCursorMotionBlurDescription[] =
+    "Enable motion blur effect for the cursor.";
+
 const char kEnableNoScriptPreviewsName[] = "NoScript previews";
 
 const char kEnableNoScriptPreviewsDescription[] =
@@ -1400,11 +1404,25 @@
 const char kSingleClickAutofillDescription[] =
     "Make autofill suggestions on initial mouse click on a form element.";
 
-const char kSitePerProcessName[] = "Strict site isolation";
-const char kSitePerProcessDescription[] =
-    "Experimental security mode that ensures each renderer process "
-    "contains pages from at most one site. In this mode, out-of-process "
-    "iframes will be used whenever an iframe is cross-site.";
+const char kStrictSiteIsolationName[] = "Strict site isolation";
+const char kStrictSiteIsolationDescription[] =
+    "Security mode that enables site isolation for all sites. When enabled, "
+    "each renderer process will contain pages from at most one site, using "
+    "out-of-process iframes when needed. When enabled, this flag forces the "
+    "strictest site isolation mode (SitePerProcess). When disabled, the site "
+    "isolation mode will be determined by enterprise policy or field trial.";
+
+const char kSiteIsolationTrialOptOutName[] = "Site isolation trial opt-out";
+const char kSiteIsolationTrialOptOutDescription[] =
+    "Opts out of field trials that enable site isolation modes "
+    "(SitePerProcess, IsolateOrigins, etc). Intended for diagnosing bugs that "
+    "may be due to out-of-process iframes. Opt-out has no effect if site "
+    "isolation is force-enabled via #enable-site-per-process or enterprise "
+    "policy. Caution: this disables important mitigations for the Spectre CPU "
+    "vulnerability affecting most computers.";
+extern const char kSiteIsolationTrialOptOutChoiceDefault[] = "Default";
+extern const char kSiteIsolationTrialOptOutChoiceOptOut[] =
+    "Opt-out (not recommended)";
 
 const char kSiteSettings[] = "Site settings with All sites and Site details";
 const char kSiteSettingsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 0d746b6..0e2e9fa 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -244,6 +244,9 @@
 extern const char kEnableClientLoFiName[];
 extern const char kEnableClientLoFiDescription[];
 
+extern const char kEnableCursorMotionBlurName[];
+extern const char kEnableCursorMotionBlurDescription[];
+
 extern const char kEnableNoScriptPreviewsName[];
 extern const char kEnableNoScriptPreviewsDescription[];
 
@@ -857,8 +860,13 @@
 extern const char kSingleClickAutofillName[];
 extern const char kSingleClickAutofillDescription[];
 
-extern const char kSitePerProcessName[];
-extern const char kSitePerProcessDescription[];
+extern const char kStrictSiteIsolationName[];
+extern const char kStrictSiteIsolationDescription[];
+
+extern const char kSiteIsolationTrialOptOutName[];
+extern const char kSiteIsolationTrialOptOutDescription[];
+extern const char kSiteIsolationTrialOptOutChoiceDefault[];
+extern const char kSiteIsolationTrialOptOutChoiceOptOut[];
 
 extern const char kSiteSettings[];
 extern const char kSiteSettingsDescription[];
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.cc b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.cc
index 1cc2961..5b7be3d3 100644
--- a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.cc
+++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.cc
@@ -53,11 +53,12 @@
       base::BindRepeating(&DialMediaSinkServiceImpl::OnAppInfoParseCompleted,
                           base::Unretained(this)));
 
+  MediaSinkServiceBase::StartTimer();
+
   dial_registry_ =
       test_dial_registry_ ? test_dial_registry_ : DialRegistry::GetInstance();
   dial_registry_->RegisterObserver(this);
   dial_registry_->OnListenerAdded();
-  MediaSinkServiceBase::StartTimer();
 }
 
 void DialMediaSinkServiceImpl::OnUserGesture() {
@@ -127,6 +128,9 @@
   current_devices_ = devices;
 
   description_service_->GetDeviceDescriptions(devices);
+
+  // Makes sure the timer fires even if there is no device.
+  MediaSinkServiceBase::RestartTimer();
 }
 
 void DialMediaSinkServiceImpl::OnDialError(DialRegistry::DialErrorCode type) {
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.h b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.h
index a5b6da6..56e87ad0 100644
--- a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.h
+++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl.h
@@ -84,7 +84,10 @@
   friend class DialMediaSinkServiceImplTest;
   friend class MockDialMediaSinkServiceImpl;
   FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest, TestStart);
-  FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest, TestTimer);
+  FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
+                           TestOnDeviceDescriptionRestartsTimer);
+  FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
+                           TestOnDialDeviceEventRestartsTimer);
   FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
                            TestOnDeviceDescriptionAvailable);
   FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceImplTest,
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl_unittest.cc b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl_unittest.cc
index 521463a..37332f9 100644
--- a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl_unittest.cc
+++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_impl_unittest.cc
@@ -166,7 +166,7 @@
   }
 }
 
-TEST_F(DialMediaSinkServiceImplTest, TestTimer) {
+TEST_F(DialMediaSinkServiceImplTest, TestOnDeviceDescriptionRestartsTimer) {
   DialDeviceData device_data("first", GURL("http://127.0.0.1/dd.xml"),
                              base::Time::Now());
   ParsedDialDeviceDescription device_description;
@@ -196,6 +196,32 @@
   EXPECT_TRUE(mock_timer_->IsRunning());
 }
 
+TEST_F(DialMediaSinkServiceImplTest, TestOnDialDeviceEventRestartsTimer) {
+  MediaSinkInternal dial_sink = CreateDialSink(1);
+  media_sink_service_->current_sinks_.insert_or_assign(dial_sink.sink().id(),
+                                                       dial_sink);
+
+  EXPECT_CALL(mock_sink_discovered_cb_, Run(_));
+  media_sink_service_->OnDiscoveryComplete();
+
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  EXPECT_CALL(*mock_description_service_,
+              GetDeviceDescriptions(std::vector<DialDeviceData>()));
+  media_sink_service_->OnDialDeviceEvent(std::vector<DialDeviceData>());
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  EXPECT_CALL(mock_sink_discovered_cb_, Run(std::vector<MediaSinkInternal>()));
+  mock_timer_->Fire();
+
+  EXPECT_CALL(*mock_description_service_,
+              GetDeviceDescriptions(std::vector<DialDeviceData>()));
+  media_sink_service_->OnDialDeviceEvent(std::vector<DialDeviceData>());
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  EXPECT_CALL(mock_sink_discovered_cb_, Run(_)).Times(0);
+  mock_timer_->Fire();
+}
+
 TEST_F(DialMediaSinkServiceImplTest, OnDialSinkAddedCallback) {
   DialDeviceData device_data1("first", GURL("http://127.0.0.1/dd.xml"),
                               base::Time::Now());
diff --git a/chrome/browser/metrics/thread_watcher_report_hang.cc b/chrome/browser/metrics/thread_watcher_report_hang.cc
index 5385f87..0e45e1e 100644
--- a/chrome/browser/metrics/thread_watcher_report_hang.cc
+++ b/chrome/browser/metrics/thread_watcher_report_hang.cc
@@ -50,12 +50,6 @@
   ReportThreadHang();
 }
 
-NOINLINE void ThreadUnresponsive_PROCESS_LAUNCHER() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  ReportThreadHang();
-}
-
 NOINLINE void ThreadUnresponsive_IO() {
   volatile int inhibit_comdat = __LINE__;
   ALLOW_UNUSED_LOCAL(inhibit_comdat);
@@ -67,8 +61,6 @@
   switch (thread_id) {
     case content::BrowserThread::UI:
       return ThreadUnresponsive_UI();
-    case content::BrowserThread::PROCESS_LAUNCHER:
-      return ThreadUnresponsive_PROCESS_LAUNCHER();
     case content::BrowserThread::IO:
       return ThreadUnresponsive_IO();
     case content::BrowserThread::ID_COUNT:
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 0610049..bef2eac7 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -55,7 +55,6 @@
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
 #include "content/public/common/context_menu_params.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
@@ -888,42 +887,6 @@
   EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), GetActiveWebContents()->GetTitle());
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, MultipleDomains) {
-  for (const auto& url :
-       {embedded_test_server()->GetURL("a.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("b.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("c.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("d.com", "/pdf/test.pdf")}) {
-    ASSERT_TRUE(LoadPdf(url));
-  }
-  EXPECT_EQ(1, CountPDFProcesses());
-}
-
-class PDFIsolatedExtensionTest : public PDFExtensionTest {
- public:
-  PDFIsolatedExtensionTest() {}
-  ~PDFIsolatedExtensionTest() override {}
-
-  void SetUp() override {
-    features_.InitAndEnableFeature(features::kPdfIsolation);
-    PDFExtensionTest::SetUp();
-  }
-
- private:
-  base::test::ScopedFeatureList features_;
-};
-
-IN_PROC_BROWSER_TEST_F(PDFIsolatedExtensionTest, MultipleDomains) {
-  for (const auto& url :
-       {embedded_test_server()->GetURL("a.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("b.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("c.com", "/pdf/test.pdf"),
-        embedded_test_server()->GetURL("d.com", "/pdf/test.pdf")}) {
-    ASSERT_TRUE(LoadPdf(url));
-  }
-  EXPECT_EQ(4, CountPDFProcesses());
-}
-
 class PDFExtensionLinkClickTest : public PDFExtensionTest {
  public:
   PDFExtensionLinkClickTest() : guest_contents_(nullptr) {}
diff --git a/chrome/browser/policy/site_isolation_policy_browsertest.cc b/chrome/browser/policy/site_isolation_policy_browsertest.cc
index 911056d..d5d4b47 100644
--- a/chrome/browser/policy/site_isolation_policy_browsertest.cc
+++ b/chrome/browser/policy/site_isolation_policy_browsertest.cc
@@ -140,6 +140,19 @@
   DISALLOW_COPY_AND_ASSIGN(WebDriverSitePerProcessPolicyBrowserTest);
 };
 
+// Ensure that --disable-site-isolation-trials does not override policies.
+class NoOverrideSitePerProcessPolicyBrowserTest
+    : public SitePerProcessPolicyBrowserTest {
+ protected:
+  NoOverrideSitePerProcessPolicyBrowserTest() {}
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(switches::kDisableSiteIsolationTrials);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoOverrideSitePerProcessPolicyBrowserTest);
+};
+
 IN_PROC_BROWSER_TEST_F(SitePerProcessPolicyBrowserTest, Simple) {
   Expectations expectations[] = {
       {"https://foo.com/noodles.html", true},
@@ -170,3 +183,11 @@
   };
   CheckExpectations(expectations, arraysize(expectations));
 }
+
+IN_PROC_BROWSER_TEST_F(NoOverrideSitePerProcessPolicyBrowserTest, Simple) {
+  Expectations expectations[] = {
+      {"https://foo.com/noodles.html", true},
+      {"http://example.org/pumpkins.html", true},
+  };
+  CheckExpectations(expectations, arraysize(expectations));
+}
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 8fbcb35..f8306073 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -191,6 +191,11 @@
     print_job_->StartPdfToEmfConversion(print_data, page_size, content_area,
                                         print_text_with_gdi);
   }
+  // Indicate that the PDF is fully rendered and we no longer need the renderer
+  // and web contents, so the print job does not need to be cancelled if they
+  // die. This is needed on Windows because the PrintedDocument will not be
+  // considered complete until PDF conversion finishes.
+  document->SetConvertingPdf();
 #else
   std::unique_ptr<PdfMetafileSkia> metafile =
       std::make_unique<PdfMetafileSkia>();
@@ -518,6 +523,12 @@
   if (!print_job_.get() || !print_job_->is_job_pending())
     return false;
 
+  // Is the document already complete?
+  if (print_job_->document() && print_job_->document()->IsComplete()) {
+    printing_succeeded_ = true;
+    return true;
+  }
+
   // We can't print if there is no renderer.
   if (!web_contents() ||
       !web_contents()->GetRenderViewHost() ||
@@ -525,12 +536,6 @@
     return false;
   }
 
-  // Is the document already complete?
-  if (print_job_->document() && print_job_->document()->IsComplete()) {
-    printing_succeeded_ = true;
-    return true;
-  }
-
   // WebContents is either dying or a second consecutive request to print
   // happened before the first had time to finish. We need to render all the
   // pages in an hurry if a print_job_ is still pending. No need to wait for it
diff --git a/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc b/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
index bae652f..f6c52d67 100644
--- a/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
+++ b/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
@@ -22,6 +22,7 @@
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -130,9 +131,8 @@
   // Ensures that the backgrounding / foregrounding gets a chance to run.
   void WaitForLauncherThread() {
     base::RunLoop run_loop;
-    content::BrowserThread::PostTaskAndReply(
-        content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE, base::DoNothing(),
-        run_loop.QuitWhenIdleClosure());
+    content::GetProcessLauncherTaskRunner()->PostTaskAndReply(
+        FROM_HERE, base::DoNothing(), run_loop.QuitWhenIdleClosure());
     run_loop.Run();
   }
 
diff --git a/chrome/browser/resource_coordinator/tab_manager.cc b/chrome/browser/resource_coordinator/tab_manager.cc
index a11970a..266b911 100644
--- a/chrome/browser/resource_coordinator/tab_manager.cc
+++ b/chrome/browser/resource_coordinator/tab_manager.cc
@@ -430,11 +430,23 @@
   if (index == -1)
     return nullptr;
 
-  VLOG(1) << "Discarding tab " << index << " id " << tab_id;
+  DVLOG(1) << "Discarding tab " << index << " id " << tab_id;
 
   return DiscardWebContentsAt(index, model, reason);
 }
 
+void TabManager::FreezeTabById(int32_t tab_id) {
+  TabStripModel* model = nullptr;
+  int index = FindTabStripModelById(tab_id, &model);
+
+  if (index == -1)
+    return;
+
+  DVLOG(1) << "Freezing tab " << index << " id " << tab_id;
+
+  FreezeWebContentsAt(index, model);
+}
+
 WebContents* TabManager::DiscardTabByExtension(content::WebContents* contents) {
   if (contents) {
     return DiscardTabById(IdFromWebContents(contents),
@@ -849,6 +861,17 @@
   return null_contents;
 }
 
+void TabManager::FreezeWebContentsAt(int index, TabStripModel* model) {
+  WebContents* content = model->GetWebContentsAt(index);
+
+  // Can't freeze tabs that are already discarded or frozen.
+  // TODO(fmeawad): Only freeze non-frozen tabs.
+  if (!content || GetWebContentsData(content)->IsDiscarded())
+    return;
+
+  content->FreezePage();
+}
+
 void TabManager::PauseBackgroundTabOpeningIfNeeded() {
   TRACE_EVENT_INSTANT0("navigation",
                        "TabManager::PauseBackgroundTabOpeningIfNeeded",
diff --git a/chrome/browser/resource_coordinator/tab_manager.h b/chrome/browser/resource_coordinator/tab_manager.h
index 5783651..892a73e 100644
--- a/chrome/browser/resource_coordinator/tab_manager.h
+++ b/chrome/browser/resource_coordinator/tab_manager.h
@@ -125,6 +125,11 @@
   // of the discarded tab.
   content::WebContents* DiscardTabById(int32_t tab_id, DiscardReason reason);
 
+  // Freezes a tab with the given unique ID. Unlike discarding, freezing does
+  // not kill or change the tab other than stopping its task queues. Revisiting
+  // the tab causes it to unfreeze.
+  void FreezeTabById(int32_t tab_id);
+
   // Method used by the extensions API to discard tabs. If |contents| is null,
   // discards the least important tab using DiscardTab(). Otherwise discards
   // the given contents. Returns the new web_contents or null if no tab
@@ -290,6 +295,7 @@
                            UrgentFastShutdownWithBeforeunloadHandler);
   FRIEND_TEST_ALL_PREFIXES(TabManagerTest, IsTabRestoredInForeground);
   FRIEND_TEST_ALL_PREFIXES(TabManagerTest, EnablePageAlmostIdleSignal);
+  FRIEND_TEST_ALL_PREFIXES(TabManagerTest, FreezeTab);
 
   // The time of the first purging after a renderer is backgrounded.
   // The initial value was chosen because most of users activate backgrounded
@@ -367,6 +373,10 @@
                                              TabStripModel* model,
                                              DiscardReason reason);
 
+  // Makes a request to the WebContents at the specified index to freeze its
+  // page.
+  void FreezeWebContentsAt(int index, TabStripModel* model);
+
   // Pause or resume background tab opening according to memory pressure change
   // if there are pending background tabs.
   void PauseBackgroundTabOpeningIfNeeded();
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index d09b8b5..fd7497c 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -12,7 +12,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
-#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
 #include "chrome/browser/resource_coordinator/tab_manager.h"
 #include "chrome/browser/resource_coordinator/tab_manager_web_contents_data.h"
 #include "chrome/browser/resource_coordinator/time.h"
@@ -36,6 +35,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
+#include "net/dns/mock_host_resolver.h"
 #include "url/gurl.h"
 
 using content::OpenURLParams;
@@ -58,6 +58,11 @@
                                     kBlinkPageLifecycleFeature);
   }
 
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    host_resolver()->AddRule("*", "127.0.0.1");
+  }
+
   void OpenTwoTabs(const GURL& first_url, const GURL& second_url) {
     // Open two tabs. Wait for both of them to load.
     content::WindowedNotificationObserver load1(
@@ -791,6 +796,76 @@
       "TabManager.Discarding.DiscardedTabCouldFastShutdown", false, 1);
 }
 
+IN_PROC_BROWSER_TEST_F(TabManagerTest, FreezeTab) {
+  const char kMainFrameFrozenStateJS[] =
+      "window.domAutomationController.send(mainFrameFreezeCount);";
+  const char kChildFrameFrozenStateJS[] =
+      "window.domAutomationController.send(childFrameFreezeCount);";
+
+  const int freezing_index = 1;  // The second tab.
+  // Setup the embedded_test_server to serve a cross-site frame.
+  content::SetupCrossSiteRedirector(embedded_test_server());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Opening two tabs, where the second tab is backgrounded.
+  GURL main_url(
+      embedded_test_server()->GetURL("a.com", "/iframe_cross_site.html"));
+  OpenTwoTabs(GURL(chrome::kChromeUIAboutURL), main_url);
+  content::WebContents* content =
+      browser()->tab_strip_model()->GetWebContentsAt(freezing_index);
+
+  // Grab the frames.
+  content::RenderFrameHost* main_frame = content->GetMainFrame();
+  ASSERT_EQ(3u, content->GetAllFrames().size());
+  // The page has 2 iframes, we will use the first one.
+  content::RenderFrameHost* child_frame = content->GetAllFrames()[1];
+  // Verify that the main frame and subframe are cross-site.
+  EXPECT_FALSE(content::SiteInstance::IsSameWebSite(
+      browser()->profile(), main_frame->GetLastCommittedURL(),
+      child_frame->GetLastCommittedURL()));
+  if (content::AreAllSitesIsolatedForTesting()) {
+    EXPECT_NE(main_frame->GetProcess()->GetID(),
+              child_frame->GetProcess()->GetID());
+  }
+
+  EXPECT_TRUE(content::ExecuteScript(
+      main_frame,
+      "if (window.location.pathname != '/iframe_cross_site.html')"
+      "  throw 'Incorrect frame';"
+      "mainFrameFreezeCount = 0;"
+      "window.onfreeze = function(){ mainFrameFreezeCount++; };"));
+
+  EXPECT_TRUE(content::ExecuteScript(
+      child_frame,
+      "if (window.location.pathname != '/title1.html') throw 'Incorrect frame';"
+      "childFrameFreezeCount = 0;"
+      "window.onfreeze = function(){ childFrameFreezeCount++; };"));
+
+  // freeze_count_result should be 0 for both frames, if it is undefined then we
+  // are in the wrong frame/tab.
+  int freeze_count_result;
+  EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+      main_frame, kMainFrameFrozenStateJS, &freeze_count_result));
+  EXPECT_EQ(0, freeze_count_result);
+  EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+      child_frame, kChildFrameFrozenStateJS, &freeze_count_result));
+  EXPECT_EQ(0, freeze_count_result);
+
+  // Freeze the tab. If it fails then we might be freezing a visible tab.
+  g_browser_process->GetTabManager()->FreezeWebContentsAt(
+      freezing_index, browser()->tab_strip_model());
+
+  // freeze_count_result should be exactly 1 for both frames. The valus is
+  // incremented in the onfreeze callback. If it is >1, then the callback was
+  // called more than once.
+  EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+      main_frame, kMainFrameFrozenStateJS, &freeze_count_result));
+  EXPECT_EQ(1, freeze_count_result);
+  EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+      child_frame, kChildFrameFrozenStateJS, &freeze_count_result));
+  EXPECT_EQ(1, freeze_count_result);
+}
+
 IN_PROC_BROWSER_TEST_F(TabManagerTest, TabManagerWasDiscarded) {
   const char kDiscardedStateJS[] =
       "window.domAutomationController.send("
diff --git a/chrome/browser/resources/chromeos/select_to_speak/options.css b/chrome/browser/resources/chromeos/select_to_speak/options.css
index 5fc5037d..c1c426c 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/options.css
+++ b/chrome/browser/resources/chromeos/select_to_speak/options.css
@@ -142,10 +142,45 @@
 }
 
 .highlight {
-    opacity: .3;
-    z-index: 10;
-    top: -40px;
-    position: relative;
     height: 20px;
+    opacity: .3;
+    position: relative;
+    top: -40px;
     width: 100%;
+    z-index: 10;
+}
+
+.slider {
+    -webkit-appearance: none;
+    padding: 15px 0 15px 0;
+    width: 200px;
+}
+
+.slider::-webkit-slider-runnable-track {
+    background: #bbb;
+    border: none;
+    border-radius: 1px;
+    height: 2px;
+}
+
+.slider::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    -webkit-transition: .200ms;
+    appearance: none;
+    background: rgb(51, 103, 214);
+    border-radius: 50%;
+    height: 10px;
+    margin-top: -3px;
+    transition: width .200ms, height .200ms, margin-top .200ms;
+    width: 10px;
+}
+
+.slider:active::-webkit-slider-thumb {
+    -webkit-appearance: none;
+    appearance: none;
+    background: rgb(51, 103, 214);
+    border-radius: 50%;
+    height: 20px;
+    margin-top: -10px;
+    width: 20px;
 }
diff --git a/chrome/browser/resources/chromeos/select_to_speak/options.html b/chrome/browser/resources/chromeos/select_to_speak/options.html
index 26b525f3..7945d08 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/options.html
+++ b/chrome/browser/resources/chromeos/select_to_speak/options.html
@@ -23,25 +23,21 @@
       </div>
 
       <div class="option">
-        <span class="i18n" msgid="options_rate_description"
-              id="rate_description"></span>
-        <select id="rate" aria-labelledby="rate_description">
+        <label class="i18n" msgid="options_rate_description"
+              id="rate_description"></label>
+        <div class="example">
           <!-- If these options are changed, make sure to update the
                SelectToSpeak.speechRateToEnum_ function and add new
                enums in tools/metrics/histograms/enums.xml -->
-          <option value="0.5" class="i18n" msgid="options_rate_slowest">
-          </option>
-          <option value="0.75" class="i18n" msgid="options_rate_slow">
-          </option>
-          <option value="1.0" class="i18n" msgid="options_rate_normal">
-          </option>
-          <option value="1.25" class="i18n" msgid="options_rate_fast">
-          </option>
-          <option value="1.5" class="i18n" msgid="options_rate_faster">
-          </option>
-          <option value="2.0" class="i18n" msgid="options_rate_fastest">
-          </option>
-        </select>
+          <input type="range" min="0.5" max="5.0" step=".25" id="rate"
+                 aria-labelledby="rate_description" class="slider">
+          <div style="line-height:0px; margin-top:-1.5em">
+            <span class="i18n" msgid="options_rate_slowest"
+                  style="float:left"></span>
+            <span class="i18n" msgid="options_rate_fastest"
+                  style="float:right"></span>
+          </div>
+        </div>
       </div>
 
       <div class="option">
@@ -65,11 +61,11 @@
 
       <h2 class="i18n" msgid="options_highlight"></h2>
       <div class="option">
-        <input id="wordHighlight" type="checkbox" class="checkbox pref"
-           name="wordHighlight" aria-labeledby="wordHighlightLabel">
         <label id="wordHighlightLabel" class="i18n"
                msgid="options_highlight_description">
         </label>
+        <input id="wordHighlight" type="checkbox" class="checkbox"
+           name="wordHighlight" aria-labeledby="wordHighlightLabel">
         <div class="sub-option hidden" id="highlightSubOption">
           <span class="i18n" msgid="options_highlight_color_description"
                 id="highlight_color_description"></span>
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index 37346798..a1250cc 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -202,7 +202,7 @@
     this.trackingMouse_ = true;
     this.didTrackMouse_ = true;
     this.mouseStart_ = {x: evt.screenX, y: evt.screenY};
-    this.cancelIfSpeaking_();
+    this.cancelIfSpeaking_(false /* don't clear the focus ring */);
 
     // Fire a hit test event on click to warm up the cache.
     this.desktop_.hitTest(evt.screenX, evt.screenY, EventType.MOUSE_PRESSED);
@@ -345,7 +345,7 @@
       if (this.isSelectionKeyDown_ && this.keysPressedTogether_.size == 2 &&
           this.keysPressedTogether_.has(evt.keyCode) &&
           this.keysPressedTogether_.has(SelectToSpeak.SEARCH_KEY_CODE)) {
-        this.cancelIfSpeaking_();
+        this.cancelIfSpeaking_(true /* clear the focus ring */);
         chrome.automation.getFocus(this.requestSpeakSelectedText_.bind(this));
       }
       this.isSelectionKeyDown_ = false;
@@ -367,7 +367,7 @@
         this.keysPressedTogether_.has(evt.keyCode) &&
         this.keysPressedTogether_.size == 1) {
       this.trackingMouse_ = false;
-      this.cancelIfSpeaking_();
+      this.cancelIfSpeaking_(true /* clear the focus ring */);
     }
 
     this.keysCurrentlyDown_.delete(evt.keyCode);
@@ -750,7 +750,7 @@
    * Prepares for speech. Call once before chrome.tts.speak is called.
    */
   prepareForSpeech_: function() {
-    this.cancelIfSpeaking_();
+    this.cancelIfSpeaking_(true /* clear the focus ring */);
     if (this.intervalRef_ !== undefined) {
       clearInterval(this.intervalRef_);
     }
@@ -798,10 +798,17 @@
    * record a cancel event if speech was in progress. We must cancel
    * before the callback (rather than in it) to avoid race conditions
    * where cancel is called twice.
+   * @param {boolean} clearFocusRing Whether to clear the focus ring
+   *    as well.
    */
-  cancelIfSpeaking_: function() {
+  cancelIfSpeaking_: function(clearFocusRing) {
     chrome.tts.isSpeaking(this.recordCancelIfSpeaking_.bind(this));
-    this.stopAll_();
+    if (clearFocusRing) {
+      this.stopAll_();
+    } else {
+      // Just stop speech
+      chrome.tts.stop();
+    }
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
index d9bd60f..99df1f7 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
@@ -21,7 +21,7 @@
       this.populateVoiceList_('voice');
     }.bind(this));
     this.syncSelectControlToPref_('voice', 'voice');
-    this.syncSelectControlToPref_('rate', 'rate');
+    this.syncRangeControlToPref_('rate', 'rate');
     this.syncSelectControlToPref_('pitch', 'pitch');
     this.syncCheckboxControlToPref_(
         'wordHighlight', 'wordHighlight', function(checked) {
@@ -146,10 +146,10 @@
    * pref, sync them both ways.
    * @param {string} selectId The id of the select element.
    * @param {string} pref The name of a chrome.storage pref.
-   * @param {?function(string): undefined=} onChange Optional change
+   * @param {?function(string): undefined=} opt_onChange Optional change
    *     listener to call when the setting has been changed.
    */
-  syncSelectControlToPref_: function(selectId, pref, onChange) {
+  syncSelectControlToPref_: function(selectId, pref, opt_onChange) {
     var element = document.getElementById(selectId);
 
     function updateFromPref() {
@@ -162,8 +162,8 @@
             break;
           }
         }
-        if (onChange) {
-          onChange(value);
+        if (opt_onChange) {
+          opt_onChange(value);
         }
       });
     }
@@ -181,6 +181,39 @@
   },
 
   /**
+   * Given the id of an HTML range element and the name of a chrome.storage
+   * pref, sync them both ways.
+   * @param {string} rangeId The id of the range element.
+   * @param {string} pref The name of a chrome.storage pref.
+   * @param {?function(string): undefined=} opt_onChange Optional change
+   *     listener to call when the setting has been changed.
+   */
+  syncRangeControlToPref_: function(rangeId, pref, opt_onChange) {
+    var element = document.getElementById(rangeId);
+
+    function updateFromPref() {
+      chrome.storage.sync.get(pref, function(items) {
+        var value = items[pref];
+        element.value = value;
+        if (opt_onChange) {
+          opt_onChange(value);
+        }
+      });
+    }
+
+    element.addEventListener('change', function() {
+      var newValue = element.value;
+      var setParams = {};
+      setParams[pref] = newValue;
+      chrome.storage.sync.set(setParams);
+    });
+
+    element.updateFunction = updateFromPref;
+    updateFromPref();
+    chrome.storage.onChanged.addListener(updateFromPref);
+  },
+
+  /**
    * Sets up the highlight listeners and preferences.
    */
   setUpHighlightListener_: function() {
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
index df00c43..f543b8474 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
@@ -138,18 +138,6 @@
       <message desc="Label for the slowest synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_SLOWEST">
         Slowest
       </message>
-      <message desc="Label for slow synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_SLOW">
-        Slow
-      </message>
-      <message desc="Label for a normal synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_NORMAL">
-        Normal
-      </message>
-      <message desc="Label for fast synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_FAST">
-        Fast
-      </message>
-      <message desc="Label for faster synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_FASTER">
-        Faster
-      </message>
       <message desc="Label for the fastest synthesized speech rate in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_RATE_FASTEST">
         Fastest
       </message>
diff --git a/chrome/browser/resources/discards/discards.html b/chrome/browser/resources/discards/discards.html
index 4d5828f..96ae713 100644
--- a/chrome/browser/resources/discards/discards.html
+++ b/chrome/browser/resources/discards/discards.html
@@ -71,6 +71,7 @@
           <div is="action-link" class="discard-urgent-link">
             [Urgent Discard]
           </div>
+          <div is="action-link" class="freeze-link">[Freeze]</div>
         </td>
       </tr>
     </template>
diff --git a/chrome/browser/resources/discards/discards.js b/chrome/browser/resources/discards/discards.js
index b09684e..2c2da84 100644
--- a/chrome/browser/resources/discards/discards.js
+++ b/chrome/browser/resources/discards/discards.js
@@ -229,6 +229,20 @@
     discardLink.addEventListener('click', discardListener);
     discardUrgentLink.addEventListener('click', discardListener);
 
+
+    // Set up the listeners for freeze links.
+    let lifecycleListener = function(e) {
+      // Get the info backing this row.
+      let info = infos[getRowIndex(e.target)];
+      // TODO(fmeawad): Disable the action, and let the update function
+      // re-enable it. Blocked on acquiring freeze status.
+      // Perform the action.
+      uiHandler.freezeById(info.id);
+    };
+
+    let freezeLink = row.querySelector('.freeze-link');
+    freezeLink.addEventListener('click', lifecycleListener);
+
     return row;
   }
 
diff --git a/chrome/browser/resources/settings/controls/settings_toggle_button.js b/chrome/browser/resources/settings/controls/settings_toggle_button.js
index b4e7c83..3cd57ae 100644
--- a/chrome/browser/resources/settings/controls/settings_toggle_button.js
+++ b/chrome/browser/resources/settings/controls/settings_toggle_button.js
@@ -26,7 +26,7 @@
   },
 
   listeners: {
-    'tap': 'onHostTap_',
+    'click': 'onHostTap_',
   },
 
   observers: [
@@ -68,7 +68,7 @@
   },
 
   /**
-   * Handles non cr-toggle button taps (cr-toggle handles its own tap events
+   * Handles non cr-toggle button clicks (cr-toggle handles its own click events
    * which don't bubble).
    * @param {!Event} e
    * @private
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html
index 3255e932..5d7c874 100644
--- a/chrome/browser/resources/settings/device_page/display.html
+++ b/chrome/browser/resources/settings/device_page/display.html
@@ -115,7 +115,7 @@
             </div>
           </div>
           <cr-toggle checked="[[unifiedDesktopMode_]]"
-              on-tap="onUnifiedDesktopTap_"
+              on-click="onUnifiedDesktopTap_"
               aria-labelledby="displayUnifiedDesktopCheckboxLabel">
           </cr-toggle>
         </div>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index d0bd4cf..38cb20e3 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -101,7 +101,7 @@
             on-settings-boolean-control-change="onMetricsReportingChange_"
             no-set-pref>
           <template is="dom-if" if="[[showRestart_]]" restamp>
-            <paper-button on-tap="onRestartTap_" id="restart"
+            <paper-button on-click="onRestartTap_" id="restart"
                 slot="more-actions">
               $i18n{restart}
             </paper-button>
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.js b/chrome/browser/resources/settings/settings_menu/settings_menu.js
index aec2a0c..81160f27 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.js
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.js
@@ -25,8 +25,8 @@
   },
 
   listeners: {
-    'topMenu.tap': 'onLinkTap_',
-    'subMenu.tap': 'onLinkTap_',
+    'topMenu.click': 'onLinkTap_',
+    'subMenu.click': 'onLinkTap_',
   },
 
   /** @param {!settings.Route} newRoute */
diff --git a/chrome/browser/resources/settings/system_page/system_page.html b/chrome/browser/resources/settings/system_page/system_page.html
index fb4ed50..7eb8d44 100644
--- a/chrome/browser/resources/settings/system_page/system_page.html
+++ b/chrome/browser/resources/settings/system_page/system_page.html
@@ -24,7 +24,7 @@
         label="$i18n{hardwareAccelerationLabel}">
       <template is="dom-if" if="[[shouldShowRestart_(
           prefs.hardware_acceleration_mode.enabled.value)]]">
-        <paper-button on-tap="onRestartTap_" slot="more-actions">
+        <paper-button on-click="onRestartTap_" slot="more-actions">
           $i18n{restart}
         </paper-button>
       </template>
diff --git a/chrome/browser/service_process/service_process_control.cc b/chrome/browser/service_process/service_process_control.cc
index e0d8e7a..00e3d77f 100644
--- a/chrome/browser/service_process/service_process_control.cc
+++ b/chrome/browser/service_process/service_process_control.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/upgrade_detector.h"
 #include "chrome/common/service_process_util.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "mojo/edk/embedder/named_platform_handle.h"
 #include "mojo/edk/embedder/named_platform_handle_utils.h"
@@ -338,8 +339,8 @@
 void ServiceProcessControl::Launcher::Run(const base::Closure& task) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   notify_task_ = task;
-  BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
-                          base::Bind(&Launcher::DoRun, this));
+  content::GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&Launcher::DoRun, this));
 }
 
 ServiceProcessControl::Launcher::~Launcher() {
diff --git a/chrome/browser/task_manager/sampling/shared_sampler_win_unittest.cc b/chrome/browser/task_manager/sampling/shared_sampler_win_unittest.cc
index b0e21c8..5814200 100644
--- a/chrome/browser/task_manager/sampling/shared_sampler_win_unittest.cc
+++ b/chrome/browser/task_manager/sampling/shared_sampler_win_unittest.cc
@@ -7,6 +7,7 @@
 #include <windows.h>
 
 #include <memory>
+#include <numeric>
 
 #include "base/callback.h"
 #include "base/macros.h"
@@ -133,10 +134,17 @@
 
   int64_t initial_value = physical_bytes();
 
-  // Allocate a large continuos block of memory.
+  // Allocate a large continuous block of memory.
   const int allocated_size = 4 * 1024 * 1024;
   std::vector<uint8_t> memory_block(allocated_size);
 
+  // It appears the allocation is not counted when allocated, but when actually
+  // accessed. vector's constructor does access the memory to initialize it,
+  // but if the memory then isn't read the compiler might optimize away the
+  // initialization. So read the memory to prevent that.
+  uint8_t sum = std::accumulate(memory_block.begin(), memory_block.end(), 0);
+  EXPECT_EQ(0, sum);
+
   StartRefresh(REFRESH_TYPE_PHYSICAL_MEMORY);
   WaitUntilRefreshDone();
 
diff --git a/chrome/browser/themes/theme_properties.cc b/chrome/browser/themes/theme_properties.cc
index d2b2e71..d3a51a7 100644
--- a/chrome/browser/themes/theme_properties.cc
+++ b/chrome/browser/themes/theme_properties.cc
@@ -174,38 +174,33 @@
     return base::nullopt;
 
   switch (id) {
-    // Properties stored in theme pack.
     case ThemeProperties::COLOR_FRAME:
-      // Active frame colors.
       return incognito ? gfx::kGoogleGrey900 : kDefaultTouchUiColorActiveFrame;
     case ThemeProperties::COLOR_FRAME_INACTIVE:
-      // Inactive frame colors.
       return incognito ? kDefaultTouchUiColorInactiveFrameIncognito
                        : kDefaultTouchUiColorInactiveFrame;
-
     case ThemeProperties::COLOR_TOOLBAR:
       return incognito ? kDefaultTouchUiColorInactiveFrameIncognito
                        : kDefaultTouchUiColorToolbar;
+
     case ThemeProperties::COLOR_TAB_TEXT:
     case ThemeProperties::COLOR_BOOKMARK_TEXT:
+    case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_ACTIVE:
+    case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON:
       return incognito ? gfx::kGoogleGrey100 : gfx::kGoogleGrey800;
+
     case ThemeProperties::COLOR_BACKGROUND_TAB_TEXT:
+    case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_INACTIVE:
+    case ThemeProperties::COLOR_TAB_ALERT_AUDIO:
       return incognito ? gfx::kGoogleGrey400 : gfx::kGoogleGrey700;
 
-    // Properties not stored in theme pack.
     case ThemeProperties::COLOR_BACKGROUND_TAB:
       return incognito ? kDefaultTouchUiColorTabBackgroundInactiveIncognito
                        : kDefaultTouchUiColorTabBackgroundInactive;
-    case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_ACTIVE:
-      return incognito ? gfx::kGoogleGrey100 : gfx::kGoogleGrey800;
-    case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_INACTIVE:
-      return incognito ? gfx::kGoogleGrey400 : gfx::kGoogleGrey700;
     case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_HOVER:
       return incognito ? gfx::kGoogleRedDark600 : gfx::kGoogleRed600;
     case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_PRESSED:
       return incognito ? gfx::kGoogleRedDark800 : gfx::kGoogleRed800;
-    case ThemeProperties::COLOR_TAB_ALERT_AUDIO:
-      return incognito ? gfx::kGoogleGrey400 : gfx::kGoogleGrey700;
     case ThemeProperties::COLOR_TAB_ALERT_RECORDING:
       return incognito ? gfx::kGoogleGrey400 : gfx::kGoogleRed600;
     case ThemeProperties::COLOR_TAB_ALERT_CAPTURING:
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 56b1659..95c7eeb2 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -424,8 +424,17 @@
   const int kNtpText = ThemeProperties::COLOR_NTP_TEXT;
   const int kLabelBackground =
       ThemeProperties::COLOR_SUPERVISED_USER_LABEL_BACKGROUND;
+  const bool is_touch =
+      ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
   switch (id) {
     case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON:
+      // Touch-optimized UI uses different colors than gfx::kChromeIconGrey.
+      // They are two specific colors in normal and incognito modes which we
+      // can't get one from the other by HSLShift().
+      // TODO: This will break custom themes. https://crbug.com/820495.
+      if (is_touch)
+        break;
+
       return color_utils::HSLShift(
           gfx::kChromeIconGrey,
           GetTint(ThemeProperties::TINT_BUTTONS, incognito));
@@ -433,7 +442,7 @@
       // The active color is overridden in GtkUi.
       return SkColorSetA(
           GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON, incognito),
-          0x33);
+          is_touch ? 0x6E : 0x33);
     case ThemeProperties::COLOR_LOCATION_BAR_BORDER:
       return SkColorSetA(SK_ColorBLACK, 0x4D);
     case ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR:
@@ -460,8 +469,8 @@
     case ThemeProperties::COLOR_BACKGROUND_TAB: {
       // Touch optimized color design uses different tab background colors.
       // TODO(malaykeshav) - This will break custom themes on touch optimized
-      // UI. Use tint shift instead.
-      if (ui::MaterialDesignController::IsTouchOptimizedUiEnabled())
+      // UI. Use tint shift instead. https://crbug.com/820495.
+      if (is_touch)
         break;
 
       // The tints here serve a different purpose than TINT_BACKGROUND_TAB.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b5352b83..4e0f92e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3231,6 +3231,7 @@
         "views/toolbar/toolbar_action_view_delegate_views.h",
         "views/toolbar/toolbar_button.cc",
         "views/toolbar/toolbar_button.h",
+        "views/toolbar/toolbar_ink_drop_util.h",
         "views/toolbar/toolbar_view.cc",
         "views/toolbar/toolbar_view.h",
         "views/touch_uma/touch_uma.h",
diff --git a/chrome/browser/ui/layout_constants.cc b/chrome/browser/ui/layout_constants.cc
index a1f3476..dd3109d 100644
--- a/chrome/browser/ui/layout_constants.cc
+++ b/chrome/browser/ui/layout_constants.cc
@@ -45,12 +45,14 @@
       return hybrid ? 3 : 1;
     case LOCATION_BAR_PADDING:
       return hybrid ? 3 : 1;
-    case LOCATION_BAR_HEIGHT:
-      return hybrid ? 32 : 28;
+    case LOCATION_BAR_HEIGHT: {
+      constexpr int kHeights[] = {28, 32, 36};
+      return kHeights[mode];
+    }
     case LOCATION_BAR_ICON_SIZE:
       return 16;
     case LOCATION_BAR_ICON_INTERIOR_PADDING:
-      return 4;
+      return touch_optimized_material ? 8 : 4;
     case TABSTRIP_NEW_TAB_BUTTON_SPACING: {
       // In non-touch optimized UI, we make the new tab button overlap with the
       // last tab in the tabstrip (i.e negative spacing). However, in
@@ -81,8 +83,10 @@
       return touch_optimized_material ? 245 : 193;
     case TOOLBAR_ELEMENT_PADDING:
       return hybrid ? 8 : 0;
-    case TOOLBAR_STANDARD_SPACING:
-      return hybrid ? 8 : 4;
+    case TOOLBAR_STANDARD_SPACING: {
+      constexpr int kSpacings[] = {4, 8, 12};
+      return kSpacings[mode];
+    }
   }
   NOTREACHED();
   return 0;
@@ -90,10 +94,14 @@
 
 gfx::Insets GetLayoutInsets(LayoutInset inset) {
   switch (inset) {
-    case TAB:
+    case TAB: {
       constexpr int kTabHorizontalInset[] = {16, 18, 24};
       return gfx::Insets(
           1, kTabHorizontalInset[ui::MaterialDesignController::GetMode()]);
+    }
+    case TOOLBAR_BUTTON:
+      return gfx::Insets(
+          ui::MaterialDesignController::IsTouchOptimizedUiEnabled() ? 12 : 6);
   }
   NOTREACHED();
   return gfx::Insets();
diff --git a/chrome/browser/ui/layout_constants.h b/chrome/browser/ui/layout_constants.h
index ef7338e..dda6cb5 100644
--- a/chrome/browser/ui/layout_constants.h
+++ b/chrome/browser/ui/layout_constants.h
@@ -112,6 +112,9 @@
 enum LayoutInset {
   // The padding inside the tab bounds that defines the tab contents region.
   TAB,
+
+  // The padding inside the border of a toolbar button (around the image).
+  TOOLBAR_BUTTON,
 };
 
 enum LayoutSize {
diff --git a/chrome/browser/ui/login/login_handler.cc b/chrome/browser/ui/login/login_handler.cc
index 73151db..3dac9e9 100644
--- a/chrome/browser/ui/login/login_handler.cc
+++ b/chrome/browser/ui/login/login_handler.cc
@@ -100,12 +100,25 @@
       password_manager_(NULL),
       web_contents_getter_(web_contents_getter),
       login_model_(NULL),
-      auth_required_callback_(auth_required_callback) {
+      auth_required_callback_(auth_required_callback),
+      has_shown_login_handler_(false),
+      release_soon_has_been_called_(false) {
   // This constructor is called on the I/O thread, so we cannot load the nib
   // here. BuildViewImpl() will be invoked on the UI thread later, so wait with
   // loading the nib until then.
   DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
 
+  // Note from erikchen@:
+  // The ownership semantics for this class are very confusing. The class is
+  // self-owned, and is only destroyed when LoginHandler::ReleaseSoon() is
+  // called. But this relies on the assumption that the LoginHandlerView is
+  // shown, which isn't always the case. So this class also tracks whether the
+  // LoginHandler has been shown, and if has never been shown but CancelAuth()
+  // is called, then LoginHandler::ReleaseSoon() will also be called.
+  // This relies on the assumption that if the LoginView is not shown, then
+  // CancelAuth() must be called.
+  // Ideally, the whole class should be refactored to have saner ownership
+  // semantics.
   AddRef();  // matched by LoginHandler::ReleaseSoon().
 
   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
@@ -133,12 +146,14 @@
   password_manager_ = password_manager;
   password_form_ = observed_form;
   LoginHandler::LoginModelData model_data(password_manager, observed_form);
+  has_shown_login_handler_ = true;
   BuildViewImpl(authority, explanation, &model_data);
 }
 
 void LoginHandler::BuildViewWithoutPasswordManager(
     const base::string16& authority,
     const base::string16& explanation) {
+  has_shown_login_handler_ = true;
   BuildViewImpl(authority, explanation, nullptr);
 }
 
@@ -298,6 +313,12 @@
 }
 
 void LoginHandler::ReleaseSoon() {
+  if (release_soon_has_been_called_) {
+    return;
+  } else {
+    release_soon_has_been_called_ = true;
+  }
+
   if (!TestAndSetAuthHandled()) {
     BrowserThread::PostTask(
         BrowserThread::IO, FROM_HERE,
@@ -378,6 +399,10 @@
   service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
                   content::Source<NavigationController>(controller),
                   content::Details<LoginNotificationDetails>(&details));
+
+  if (!has_shown_login_handler_) {
+    ReleaseSoon();
+  }
 }
 
 // Marks authentication as handled and returns the previous handled state.
@@ -517,12 +542,15 @@
                                    LoginHandler* handler) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   WebContents* parent_contents = handler->GetWebContentsForLogin();
-  if (!parent_contents)
+  if (!parent_contents) {
+    handler->CancelAuth();
     return;
+  }
   prerender::PrerenderContents* prerender_contents =
       prerender::PrerenderContents::FromWebContents(parent_contents);
   if (prerender_contents) {
     prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
+    handler->CancelAuth();
     return;
   }
 
diff --git a/chrome/browser/ui/login/login_handler.h b/chrome/browser/ui/login/login_handler.h
index 36fba38..458a1cb 100644
--- a/chrome/browser/ui/login/login_handler.h
+++ b/chrome/browser/ui/login/login_handler.h
@@ -256,6 +256,13 @@
 
   base::WeakPtr<LoginInterstitialDelegate> interstitial_delegate_;
 
+  // Default to |false|. Must be set to |true| anytime the login handler is
+  // shown.
+  bool has_shown_login_handler_;
+
+  // ReleaseSoon() should be called exactly once to destroy the object.
+  bool release_soon_has_been_called_;
+
 #if !defined(OS_ANDROID)
   std::unique_ptr<PopunderPreventer> popunder_preventer_;
 #endif
diff --git a/chrome/browser/ui/views/ime/ime_window_frame_view.cc b/chrome/browser/ui/views/ime/ime_window_frame_view.cc
index 5b8457fe..04ac79f3 100644
--- a/chrome/browser/ui/views/ime/ime_window_frame_view.cc
+++ b/chrome/browser/ui/views/ime/ime_window_frame_view.cc
@@ -8,10 +8,12 @@
 #include "chrome/grit/browser_resources.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/hit_test.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/path.h"
+#include "ui/strings/grit/ui_strings.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
@@ -57,6 +59,8 @@
                           rb.GetImageSkiaNamed(IDR_IME_WINDOW_CLOSE_C));
   close_button_->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
                                    views::ImageButton::ALIGN_MIDDLE);
+  close_button_->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
   AddChildView(close_button_);
 
   title_icon_ = new views::ImageView();
diff --git a/chrome/browser/ui/views/toolbar/app_menu_button.cc b/chrome/browser/ui/views/toolbar/app_menu_button.cc
index 8527fd9..24629c7 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu_button.cc
@@ -24,10 +24,12 @@
 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
 #include "chrome/browser/ui/views/toolbar/app_menu.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/chromium_strings.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/theme_provider.h"
 #include "ui/gfx/canvas.h"
@@ -41,10 +43,13 @@
 
 namespace {
 
-constexpr float kIconSize = 16;
-
 constexpr base::TimeDelta kDelayTime = base::TimeDelta::FromMilliseconds(1500);
 
+// Returns true if the touch-optimized UI is enabled.
+bool IsTouchOptimized() {
+  return ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
+}
+
 }  // namespace
 
 // static
@@ -61,6 +66,7 @@
                              false) {
   SetInkDropMode(InkDropMode::ON);
   SetFocusPainter(nullptr);
+  SetHorizontalAlignment(gfx::ALIGN_CENTER);
 
   if (base::FeatureList::IsEnabled(features::kAnimatedAppMenuIcon)) {
     toolbar_view_->browser()->tab_strip_model()->AddObserver(this);
@@ -68,6 +74,9 @@
     should_delay_animation_ = base::GetFieldTrialParamByFeatureAsBool(
         features::kAnimatedAppMenuIcon, "HasDelay", false);
   }
+
+  if (IsTouchOptimized())
+    set_ink_drop_visible_opacity(kTouchToolbarInkDropVisibleOpacity);
 }
 
 AppMenuButton::~AppMenuButton() {}
@@ -151,8 +160,10 @@
 }
 
 gfx::Size AppMenuButton::CalculatePreferredSize() const {
-  gfx::Rect rect(gfx::Size(kIconSize, kIconSize));
-  rect.Inset(gfx::Insets(-ToolbarButton::kInteriorPadding));
+  const int icon_size = IsTouchOptimized() ? 24 : 16;
+  gfx::Rect rect(gfx::Size(icon_size, icon_size));
+  rect.Inset(-GetLayoutInsets(TOOLBAR_BUTTON));
+
   return rect.size();
 }
 
@@ -224,18 +235,21 @@
     return;
   }
 
+  const bool is_touch = IsTouchOptimized();
   const gfx::VectorIcon* icon_id = nullptr;
   switch (type_) {
     case AppMenuIconController::IconType::NONE:
-      icon_id = &kBrowserToolsIcon;
+      icon_id = is_touch ? &kBrowserToolsTouchIcon : &kBrowserToolsIcon;
       DCHECK_EQ(AppMenuIconController::Severity::NONE, severity_);
       break;
     case AppMenuIconController::IconType::UPGRADE_NOTIFICATION:
-      icon_id = &kBrowserToolsUpdateIcon;
+      icon_id =
+          is_touch ? &kBrowserToolsUpdateTouchIcon : &kBrowserToolsUpdateIcon;
       break;
     case AppMenuIconController::IconType::GLOBAL_ERROR:
     case AppMenuIconController::IconType::INCOMPATIBILITY_WARNING:
-      icon_id = &kBrowserToolsErrorIcon;
+      icon_id =
+          is_touch ? &kBrowserToolsErrorTouchIcon : &kBrowserToolsErrorIcon;
       break;
   }
 
@@ -329,3 +343,25 @@
 int AppMenuButton::OnPerformDrop(const ui::DropTargetEvent& event) {
   return ui::DragDropTypes::DRAG_MOVE;
 }
+
+std::unique_ptr<views::InkDrop> AppMenuButton::CreateInkDrop() {
+  return CreateToolbarInkDrop<MenuButton>(this);
+}
+
+std::unique_ptr<views::InkDropRipple> AppMenuButton::CreateInkDropRipple()
+    const {
+  // FIXME: GetInkDropCenterBasedOnLastEvent() will always return the center
+  // of this view. https://crbug.com/819878.
+  return CreateToolbarInkDropRipple<MenuButton>(
+      this, GetInkDropCenterBasedOnLastEvent());
+}
+
+std::unique_ptr<views::InkDropHighlight> AppMenuButton::CreateInkDropHighlight()
+    const {
+  return CreateToolbarInkDropHighlight<MenuButton>(
+      this, GetMirroredRect(GetContentsBounds()).CenterPoint());
+}
+
+std::unique_ptr<views::InkDropMask> AppMenuButton::CreateInkDropMask() const {
+  return CreateToolbarInkDropMask<MenuButton>(this);
+}
diff --git a/chrome/browser/ui/views/toolbar/app_menu_button.h b/chrome/browser/ui/views/toolbar/app_menu_button.h
index 52a5ba01..1ca9749 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_button.h
+++ b/chrome/browser/ui/views/toolbar/app_menu_button.h
@@ -31,6 +31,8 @@
 // The app menu button lives in the top right of the main browser window. It
 // shows three dots and animates to a hamburger-ish icon when there's a need to
 // alert the user. Clicking displays the app menu.
+// TODO: Consider making ToolbarButton and AppMenuButton share a common base
+// class https://crbug.com/819854.
 class AppMenuButton : public views::MenuButton, public TabStripModelObserver {
  public:
   explicit AppMenuButton(ToolbarView* toolbar_view);
@@ -108,6 +110,11 @@
   int OnDragUpdated(const ui::DropTargetEvent& event) override;
   void OnDragExited() override;
   int OnPerformDrop(const ui::DropTargetEvent& event) override;
+  std::unique_ptr<views::InkDrop> CreateInkDrop() override;
+  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
+  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
+      const override;
+  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
 
   AppMenuIconController::Severity severity_ =
       AppMenuIconController::Severity::NONE;
diff --git a/chrome/browser/ui/views/toolbar/reload_button.cc b/chrome/browser/ui/views/toolbar/reload_button.cc
index 76c66d1..16e8a98 100644
--- a/chrome/browser/ui/views/toolbar/reload_button.cc
+++ b/chrome/browser/ui/views/toolbar/reload_button.cc
@@ -15,6 +15,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/base/theme_provider.h"
 #include "ui/base/window_open_disposition.h"
@@ -31,6 +32,18 @@
   IDS_RELOAD_MENU_EMPTY_AND_HARD_RELOAD_ITEM,
 };
 
+// Returns true if the touch-optimized UI is enabled.
+bool IsTouchOptimized() {
+  return ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
+}
+
+const gfx::VectorIcon& GetIconForMode(bool is_reload) {
+  if (IsTouchOptimized())
+    return is_reload ? kReloadTouchIcon : kNavigateStopTouchIcon;
+
+  return is_reload ? vector_icons::kReloadIcon : kNavigateStopIcon;
+}
+
 }  // namespace
 
 // ReloadButton ---------------------------------------------------------------
@@ -224,8 +237,7 @@
   const ui::ThemeProvider* tp = GetThemeProvider();
   // |tp| can be NULL in unit tests.
   if (tp) {
-    const gfx::VectorIcon& icon =
-        (mode == Mode::kReload) ? vector_icons::kReloadIcon : kNavigateStopIcon;
+    const gfx::VectorIcon& icon = GetIconForMode(mode == Mode::kReload);
     const SkColor normal_color =
         tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
     const SkColor disabled_color =
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index f72b9f0b..986882d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -12,18 +12,28 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/models/menu_model.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
-#include "ui/views/animation/ink_drop_highlight.h"
-#include "ui/views/animation/ink_drop_ripple.h"
 #include "ui/views/controls/button/label_button_border.h"
 #include "ui/views/controls/menu/menu_item_view.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/controls/menu/menu_runner.h"
 #include "ui/views/widget/widget.h"
 
+namespace {
+
+// Returns true if the touch-optimized UI is enabled.
+bool IsTouchOptimized() {
+  return ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
+}
+
+}  // namespace
+
 ToolbarButton::ToolbarButton(Profile* profile,
                              views::ButtonListener* listener,
                              std::unique_ptr<ui::MenuModel> model)
@@ -36,6 +46,9 @@
   SetInkDropMode(InkDropMode::ON);
   SetFocusPainter(nullptr);
   SetLeadingMargin(0);
+
+  if (IsTouchOptimized())
+    set_ink_drop_visible_opacity(kTouchToolbarInkDropVisibleOpacity);
 }
 
 ToolbarButton::~ToolbarButton() {}
@@ -45,8 +58,9 @@
 }
 
 void ToolbarButton::SetLeadingMargin(int margin) {
-  SetBorder(views::CreateEmptyBorder(gfx::Insets(kInteriorPadding) +
-                                     gfx::Insets(0, margin, 0, 0)));
+  const gfx::Insets insets =
+      GetLayoutInsets(TOOLBAR_BUTTON) + gfx::Insets(0, margin, 0, 0);
+  SetBorder(views::CreateEmptyBorder(insets));
 }
 
 void ToolbarButton::ClearPendingMenu() {
@@ -133,6 +147,26 @@
     node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kPress);
 }
 
+std::unique_ptr<views::InkDrop> ToolbarButton::CreateInkDrop() {
+  return CreateToolbarInkDrop<ImageButton>(this);
+}
+
+std::unique_ptr<views::InkDropRipple> ToolbarButton::CreateInkDropRipple()
+    const {
+  return CreateToolbarInkDropRipple<ImageButton>(
+      this, GetInkDropCenterBasedOnLastEvent());
+}
+
+std::unique_ptr<views::InkDropHighlight> ToolbarButton::CreateInkDropHighlight()
+    const {
+  return CreateToolbarInkDropHighlight<ImageButton>(
+      this, GetMirroredRect(GetContentsBounds()).CenterPoint());
+}
+
+std::unique_ptr<views::InkDropMask> ToolbarButton::CreateInkDropMask() const {
+  return CreateToolbarInkDropMask<ImageButton>(this);
+}
+
 void ToolbarButton::ShowContextMenuForView(View* source,
                                            const gfx::Point& point,
                                            ui::MenuSourceType source_type) {
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 00de1a0..db1f822542 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -30,12 +30,11 @@
 
 // This class provides basic drawing and mouse-over behavior for buttons
 // appearing in the toolbar.
+// TODO: Consider making ToolbarButton and AppMenuButton share a common base
+// class https://crbug.com/819854.
 class ToolbarButton : public views::ImageButton,
                       public views::ContextMenuController {
  public:
-  // Padding inside the border (around the image).
-  static constexpr int kInteriorPadding = 6;
-
   // The profile and listener pointers must outlive this class. The model can
   // be null if no menu is to be shown.
   ToolbarButton(Profile* profile,
@@ -64,6 +63,11 @@
   void OnMouseExited(const ui::MouseEvent& event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  std::unique_ptr<views::InkDrop> CreateInkDrop() override;
+  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
+  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
+      const override;
+  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
 
   // views::ContextMenuController:
   void ShowContextMenuForView(View* source,
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
new file mode 100644
index 0000000..c44b2d7
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h
@@ -0,0 +1,110 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
+#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
+
+#include <memory>
+
+#include "ui/base/material_design/material_design_controller.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
+#include "ui/views/animation/ink_drop_highlight.h"
+#include "ui/views/animation/ink_drop_impl.h"
+#include "ui/views/animation/ink_drop_mask.h"
+#include "ui/views/animation/ink_drop_ripple.h"
+
+namespace {
+
+// Dimentions specific to the ink drop on the Browser's toolbar buttons when
+// the Touch-optimized UI is being used.
+constexpr int kTouchInkDropCornerRadius = 18;
+constexpr gfx::Size kTouchInkDropHighlightSize{2 * kTouchInkDropCornerRadius,
+                                               2 * kTouchInkDropCornerRadius};
+constexpr gfx::Insets kTouchInkDropInsets{6};
+
+}  // namespace
+
+constexpr float kTouchToolbarInkDropVisibleOpacity = 0.06f;
+
+// The below utility functions are templated since we have two different types
+// of buttons on the toolbar (ToolbarButton and AppMenuButton) which don't share
+// the same base classes (ImageButton and MenuButton respectively), and these
+// functions need to call into the base classes' default implementations when
+// needed.
+// TODO: Consider making ToolbarButton and AppMenuButton share a common base
+// class https://crbug.com/819854.
+
+// Creates the appropriate ink drop for the calling button. When the touch-
+// optimized UI is not enabled, it uses the default implementation of the
+// calling button's base class (the template argument BaseInkDropHostView).
+// Otherwise, it uses an ink drop that shows a highlight on hover that is kept
+// and combined with the ripple when the ripple is shown.
+template <class BaseInkDropHostView>
+std::unique_ptr<views::InkDrop> CreateToolbarInkDrop(
+    BaseInkDropHostView* host_view) {
+  if (!ui::MaterialDesignController::IsTouchOptimizedUiEnabled())
+    return host_view->BaseInkDropHostView::CreateInkDrop();
+
+  auto ink_drop =
+      std::make_unique<views::InkDropImpl>(host_view, host_view->size());
+  ink_drop->SetAutoHighlightMode(
+      views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE);
+  ink_drop->SetShowHighlightOnHover(true);
+  return ink_drop;
+}
+
+// Creates the appropriate ink drop ripple for the calling button. When the
+// touch-optimized UI is not enabled, it uses the default implementation of the
+// calling button's base class (the template argument BaseInkDropHostView).
+// Otherwise, it uses a |FloodFillInkDropRipple|.
+template <class BaseInkDropHostView>
+std::unique_ptr<views::InkDropRipple> CreateToolbarInkDropRipple(
+    const BaseInkDropHostView* host_view,
+    const gfx::Point& center_point) {
+  if (!ui::MaterialDesignController::IsTouchOptimizedUiEnabled())
+    return host_view->BaseInkDropHostView::CreateInkDropRipple();
+
+  return std::make_unique<views::FloodFillInkDropRipple>(
+      host_view->size(), kTouchInkDropInsets, center_point,
+      host_view->GetInkDropBaseColor(), host_view->ink_drop_visible_opacity());
+}
+
+// Creates the appropriate ink drop highlight for the calling button. When the
+// touch-optimized UI is not enabled, it uses the default implementation of the
+// calling button's base class (the template argument BaseInkDropHostView).
+// Otherwise, it uses a kTouchInkDropHighlightSize circular highlight.
+template <class BaseInkDropHostView>
+std::unique_ptr<views::InkDropHighlight> CreateToolbarInkDropHighlight(
+    const BaseInkDropHostView* host_view,
+    const gfx::Point& center_point) {
+  if (!ui::MaterialDesignController::IsTouchOptimizedUiEnabled())
+    return host_view->BaseInkDropHostView::CreateInkDropHighlight();
+
+  auto highlight = std::make_unique<views::InkDropHighlight>(
+      kTouchInkDropHighlightSize, kTouchInkDropCornerRadius,
+      gfx::PointF(center_point), host_view->GetInkDropBaseColor());
+  highlight->set_visible_opacity(0.08f);
+  return highlight;
+}
+
+// Creates the appropriate ink drop mask for the calling button. When the
+// touch-optimized UI is not enabled, it uses the default implementation of the
+// calling button's base class (the template argument BaseInkDropHostView).
+// Otherwise, it uses a circular mask that has the same size as that of the
+// highlight, which is needed to make the flood
+// fill ripple fill a circle rather than a default square shape.
+template <class BaseInkDropHostView>
+std::unique_ptr<views::InkDropMask> CreateToolbarInkDropMask(
+    const BaseInkDropHostView* host_view) {
+  if (!ui::MaterialDesignController::IsTouchOptimizedUiEnabled())
+    return host_view->BaseInkDropHostView::CreateInkDropMask();
+
+  return std::make_unique<views::RoundRectInkDropMask>(
+      host_view->size(), kTouchInkDropInsets, kTouchInkDropCornerRadius);
+}
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_INK_DROP_UTIL_H_
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 125d618..2ac1e4a 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -89,8 +89,17 @@
 namespace {
 
 int GetToolbarHorizontalPadding() {
+  // In the touch-optimized UI, we don't use any horizontal paddings; the back
+  // button starts from the beginning of the view, and the app menu button ends
+  // at the end of the view.
   using Md = ui::MaterialDesignController;
-  return Md::GetMode() == Md::MATERIAL_HYBRID ? 8 : 4;
+  constexpr int kPaddings[] = {4, 8, 0};
+  return kPaddings[Md::GetMode()];
+}
+
+// Returns true if the touch-optimized UI is enabled.
+bool IsTouchOptimized() {
+  return ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
 }
 
 }  // namespace
@@ -334,13 +343,6 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// ToolbarView, Menu::Delegate overrides:
-
-bool ToolbarView::GetAcceleratorInfo(int id, ui::Accelerator* accel) {
-  return GetWidget()->GetAccelerator(id, accel);
-}
-
-////////////////////////////////////////////////////////////////////////////////
 // ToolbarView, views::MenuButtonListener implementation:
 
 void ToolbarView::OnMenuButtonClicked(views::MenuButton* source,
@@ -538,7 +540,6 @@
 
   const int location_height = location_bar_->GetPreferredSize().height();
   const int location_y = (height() - location_height) / 2;
-
   location_bar_->SetBounds(next_element_x, location_y,
                            location_bar_width, location_height);
 
@@ -567,6 +568,7 @@
   // we extend the back button to the left edge.
   if (maximized)
     app_menu_width += end_padding;
+
   app_menu_button_->SetBounds(next_element_x, toolbar_button_y, app_menu_width,
                               toolbar_button_height);
   app_menu_button_->SetTrailingMargin(maximized ? end_padding : 0);
@@ -698,8 +700,10 @@
     // and constant padding values.
     int content_height = std::max(back_->GetPreferredSize().height(),
                                   location_bar_->GetPreferredSize().height());
-    const int kExtraVerticalSpace = 9;
-    size.SetToMax(gfx::Size(0, content_height + kExtraVerticalSpace));
+    // In the touch-optimized UI, the toolbar buttons are big and occupy the
+    // entire view's height, we don't need to add any extra vertical space.
+    const int extra_vertical_space = IsTouchOptimized() ? 0 : 9;
+    size.SetToMax(gfx::Size(0, content_height + extra_vertical_space));
   }
   return size;
 }
@@ -712,26 +716,33 @@
   const SkColor disabled_color =
       tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE);
 
-  back_->SetImage(
-      views::Button::STATE_NORMAL,
-      gfx::CreateVectorIcon(vector_icons::kBackArrowIcon, normal_color));
-  back_->SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(vector_icons::kBackArrowIcon, disabled_color));
-  forward_->SetImage(
-      views::Button::STATE_NORMAL,
-      gfx::CreateVectorIcon(vector_icons::kForwardArrowIcon, normal_color));
-  forward_->SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(vector_icons::kForwardArrowIcon, disabled_color));
+  const bool is_touch = IsTouchOptimized();
+  const gfx::VectorIcon& back_image =
+      is_touch ? kBackArrowTouchIcon : vector_icons::kBackArrowIcon;
+  back_->SetImage(views::Button::STATE_NORMAL,
+                  gfx::CreateVectorIcon(back_image, normal_color));
+  back_->SetImage(views::Button::STATE_DISABLED,
+                  gfx::CreateVectorIcon(back_image, disabled_color));
+
+  const gfx::VectorIcon& forward_image =
+      is_touch ? kForwardArrowTouchIcon : vector_icons::kForwardArrowIcon;
+  forward_->SetImage(views::Button::STATE_NORMAL,
+                     gfx::CreateVectorIcon(forward_image, normal_color));
+  forward_->SetImage(views::Button::STATE_DISABLED,
+                     gfx::CreateVectorIcon(forward_image, disabled_color));
+
+  const gfx::VectorIcon& home_image =
+      is_touch ? kNavigateHomeTouchIcon : kNavigateHomeIcon;
   home_->SetImage(views::Button::STATE_NORMAL,
-                  gfx::CreateVectorIcon(kNavigateHomeIcon, normal_color));
+                  gfx::CreateVectorIcon(home_image, normal_color));
   app_menu_button_->UpdateIcon(false);
 
-  back_->set_ink_drop_base_color(normal_color);
-  forward_->set_ink_drop_base_color(normal_color);
-  home_->set_ink_drop_base_color(normal_color);
-  app_menu_button_->set_ink_drop_base_color(normal_color);
+  const SkColor ink_drop_color = color_utils::BlendTowardOppositeLuma(
+      tp->GetColor(ThemeProperties::COLOR_TOOLBAR), SK_AlphaOPAQUE);
+  back_->set_ink_drop_base_color(ink_drop_color);
+  forward_->set_ink_drop_base_color(ink_drop_color);
+  home_->set_ink_drop_base_color(ink_drop_color);
+  app_menu_button_->set_ink_drop_base_color(ink_drop_color);
 
   reload_->LoadImages();
 }
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index c0f8b33..e88c1c4 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -81,8 +81,6 @@
   // Returns true if the app menu is focused.
   bool IsAppMenuFocused();
 
-  virtual bool GetAcceleratorInfo(int id, ui::Accelerator* accel);
-
 #if defined(OS_CHROMEOS)
   void ShowIntentPickerBubble(
       const std::vector<IntentPickerBubbleView::AppInfo>& app_info,
diff --git a/chrome/browser/ui/webui/discards/discards.mojom b/chrome/browser/ui/webui/discards/discards.mojom
index c1be52bd..52932e9bb 100644
--- a/chrome/browser/ui/webui/discards/discards.mojom
+++ b/chrome/browser/ui/webui/discards/discards.mojom
@@ -53,6 +53,9 @@
   // Invokes a callback when the discard is complete.
   DiscardById(int32 tab_id, bool urgent) => ();
 
+  // Freezes a tab given its |tab_id|.
+  FreezeById(int32 tab_id);
+
   // Discards the least important tab. If |urgent| is specified the unload
   // handlers will not be run, and the tab will be unloaded with prejudice.
   // This can fail to discard a tab if no tabs are currently considered
diff --git a/chrome/browser/ui/webui/discards/discards_ui.cc b/chrome/browser/ui/webui/discards/discards_ui.cc
index 9b986af..5b52a26 100644
--- a/chrome/browser/ui/webui/discards/discards_ui.cc
+++ b/chrome/browser/ui/webui/discards/discards_ui.cc
@@ -94,6 +94,12 @@
     std::move(callback).Run();
   }
 
+  void FreezeById(int32_t tab_id) override {
+    resource_coordinator::TabManager* tab_manager =
+        g_browser_process->GetTabManager();
+    tab_manager->FreezeTabById(tab_id);
+  }
+
   void Discard(bool urgent, DiscardCallback callback) override {
     resource_coordinator::TabManager* tab_manager =
         g_browser_process->GetTabManager();
diff --git a/chrome/browser/ui/webui/extensions/install_extension_handler.cc b/chrome/browser/ui/webui/extensions/install_extension_handler.cc
index 953306e..cafe0181 100644
--- a/chrome/browser/ui/webui/extensions/install_extension_handler.cc
+++ b/chrome/browser/ui/webui/extensions/install_extension_handler.cc
@@ -20,6 +20,7 @@
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/common/drop_data.h"
+#include "content/public/common/service_manager_connection.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/feature_switch.h"
 #include "net/base/filename_util.h"
@@ -100,6 +101,7 @@
 
   if (file_display_name_.MatchesExtension(FILE_PATH_LITERAL(".zip"))) {
     ZipFileInstaller::Create(
+        content::ServiceManagerConnection::GetForProcess()->GetConnector(),
         MakeRegisterInExtensionServiceCallback(
             ExtensionSystem::Get(profile)->extension_service()))
         ->LoadFromZipFile(file_to_install_);
diff --git a/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc b/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
index ad2dc069..810480f0 100644
--- a/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
+++ b/chrome/browser/ui/webui/settings/incompatible_applications_handler_win.cc
@@ -141,7 +141,7 @@
 
   const base::Value& callback_id = args->GetList()[0];
   int num_applications = args->GetList()[1].GetInt();
-  DCHECK_GT(0, num_applications);
+  DCHECK_GT(num_applications, 0);
 
   ResolveJavascriptCallback(
       callback_id,
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 5910e52..7e813f7 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -250,9 +250,8 @@
                           arraysize(localized_strings));
 
 #if defined(OS_CHROMEOS)
-  html_source->AddString(
-      "a11yLearnMoreUrl",
-      GetHelpUrlWithBoard(chrome::kChromeAccessibilityHelpURL));
+  html_source->AddString("a11yLearnMoreUrl",
+                         chrome::kChromeAccessibilityHelpURL);
 
   html_source->AddBoolean(
       "showExperimentalA11yFeatures",
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 771ffffa..bcc195a4 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -28,6 +28,7 @@
     "assets_load_status.h",
     "assets_loader.cc",
     "assets_loader.h",
+    "audio_delegate.h",
     "browser_ui_interface.h",
     "content_input_delegate.cc",
     "content_input_delegate.h",
@@ -82,6 +83,8 @@
     "elements/render_text_wrapper.h",
     "elements/repositioner.cc",
     "elements/repositioner.h",
+    "elements/resizer.cc",
+    "elements/resizer.h",
     "elements/reticle.cc",
     "elements/reticle.h",
     "elements/scaled_depth_adjuster.cc",
@@ -183,6 +186,8 @@
     "service/vr_service_impl.h",
     "sliding_average.cc",
     "sliding_average.h",
+    "sounds_manager_audio_delegate.cc",
+    "sounds_manager_audio_delegate.h",
     "speech_recognizer.cc",
     "speech_recognizer.h",
     "target_property.cc",
@@ -236,6 +241,7 @@
     "//content/public/common",
     "//device/vr",
     "//device/vr/public/mojom",
+    "//media",
     "//net",
     "//skia",
     "//ui/base",
@@ -258,6 +264,7 @@
     "elements/omnibox_text_field_unittest.cc",
     "elements/rect_unittest.cc",
     "elements/repositioner_unittest.cc",
+    "elements/resizer_unittest.cc",
     "elements/scaled_depth_adjuster_unittest.cc",
     "elements/shadow_unittest.cc",
     "elements/spinner_unittest.cc",
diff --git a/chrome/browser/vr/assets_loader.cc b/chrome/browser/vr/assets_loader.cc
index 5926379..11c72a99 100644
--- a/chrome/browser/vr/assets_loader.cc
+++ b/chrome/browser/vr/assets_loader.cc
@@ -22,6 +22,9 @@
 
 namespace {
 
+constexpr char kMinVersionWithGradients[] = "1.1";
+constexpr char kMinVersionWithSounds[] = "2.0";
+
 static const base::FilePath::CharType kBackgroundBaseFilename[] =
     FILE_PATH_LITERAL("background");
 static const base::FilePath::CharType kNormalGradientBaseFilename[] =
@@ -35,7 +38,12 @@
 static const base::FilePath::CharType kJpegExtension[] =
     FILE_PATH_LITERAL("jpeg");
 
-constexpr char kMinVersionWithGradients[] = "1.1";
+static const base::FilePath::CharType kButtonHoverSoundFilename[] =
+    FILE_PATH_LITERAL("button_hover.wav");
+static const base::FilePath::CharType kButtonClickSoundFilename[] =
+    FILE_PATH_LITERAL("button_click.wav");
+static const base::FilePath::CharType kBackButtonClickSoundFilename[] =
+    FILE_PATH_LITERAL("back_button_click.wav");
 
 }  // namespace
 
@@ -147,6 +155,23 @@
   return AssetsLoadStatus::kSuccess;
 }
 
+AssetsLoadStatus LoadSound(const base::FilePath& component_install_dir,
+                           const base::FilePath::CharType* file_name,
+                           std::unique_ptr<std::string>* out_buffer) {
+  base::FilePath file_path = component_install_dir.Append(file_name);
+  if (!base::PathExists(file_path)) {
+    return AssetsLoadStatus::kNotFound;
+  }
+
+  auto buffer = std::make_unique<std::string>();
+  if (!base::ReadFileToString(file_path, buffer.get())) {
+    return AssetsLoadStatus::kParseFailure;
+  }
+
+  *out_buffer = std::move(buffer);
+  return AssetsLoadStatus::kSuccess;
+}
+
 // static
 void AssetsLoader::LoadAssetsTask(
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -174,6 +199,22 @@
     }
   }
 
+  if (status == AssetsLoadStatus::kSuccess &&
+      component_version >= base::Version(kMinVersionWithSounds)) {
+    std::vector<std::pair<const base::FilePath::CharType*,
+                          std::unique_ptr<std::string>*>>
+        sounds = {
+            {kButtonHoverSoundFilename, &assets->button_hover_sound},
+            {kButtonClickSoundFilename, &assets->button_click_sound},
+            {kBackButtonClickSoundFilename, &assets->back_button_click_sound},
+        };
+    for (auto& sound : sounds) {
+      status = LoadSound(component_install_dir, sound.first, sound.second);
+      if (status != AssetsLoadStatus::kSuccess)
+        break;
+    }
+  }
+
   if (status != AssetsLoadStatus::kSuccess) {
     assets.reset();
   }
diff --git a/chrome/browser/vr/assets_loader.h b/chrome/browser/vr/assets_loader.h
index eba61390..306ccb3 100644
--- a/chrome/browser/vr/assets_loader.h
+++ b/chrome/browser/vr/assets_loader.h
@@ -22,7 +22,7 @@
 
 namespace vr {
 
-constexpr uint32_t kCompatibleMajorVrAssetsComponentVersion = 1;
+constexpr uint32_t kCompatibleMajorVrAssetsComponentVersion = 2;
 
 class MetricsHelper;
 struct AssetsLoaderSingletonTrait;
diff --git a/chrome/browser/vr/audio_delegate.h b/chrome/browser/vr/audio_delegate.h
new file mode 100644
index 0000000..1f5cfc9
--- /dev/null
+++ b/chrome/browser/vr/audio_delegate.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VR_AUDIO_DELEGATE_H_
+#define CHROME_BROWSER_VR_AUDIO_DELEGATE_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/vr/model/sound_id.h"
+
+namespace vr {
+
+// This delegate interface describes an audio implementation supplied to the UI.
+class AudioDelegate {
+ public:
+  virtual ~AudioDelegate() {}
+
+  // Clears all registered sounds. This must be done before re-registering a
+  // particular sound.
+  virtual void ResetSounds() = 0;
+
+  // The delegate must assume ownership of the audio data. A sound may only be
+  // registered once.  To change the sound later, call ResetSounds and
+  // re-register all sounds.
+  virtual bool RegisterSound(SoundId id, std::unique_ptr<std::string> data) = 0;
+
+  virtual void PlaySound(SoundId id) = 0;
+};
+
+}  //  namespace vr
+
+#endif  // CHROME_BROWSER_VR_AUDIO_DELEGATE_H_
diff --git a/chrome/browser/vr/elements/disc_button.cc b/chrome/browser/vr/elements/disc_button.cc
index 7242996..55c06a5 100644
--- a/chrome/browser/vr/elements/disc_button.cc
+++ b/chrome/browser/vr/elements/disc_button.cc
@@ -21,7 +21,8 @@
 }  // namespace
 
 DiscButton::DiscButton(base::RepeatingCallback<void()> click_handler,
-                       const gfx::VectorIcon& icon)
+                       const gfx::VectorIcon& icon,
+                       AudioDelegate* audio_delegate)
     : Button(click_handler) {
   auto vector_icon = std::make_unique<VectorIcon>(512);
   vector_icon->SetType(kTypeButtonForeground);
@@ -35,6 +36,8 @@
   auto target = RemoveChild(hit_plane());
   vector_icon->AddChild(std::move(target));
   AddChild(std::move(vector_icon));
+
+  SetSounds(kSoundButtonHover, kSoundBackButtonClick, audio_delegate);
 }
 
 DiscButton::~DiscButton() = default;
diff --git a/chrome/browser/vr/elements/disc_button.h b/chrome/browser/vr/elements/disc_button.h
index 5bbfc62f..68123f8 100644
--- a/chrome/browser/vr/elements/disc_button.h
+++ b/chrome/browser/vr/elements/disc_button.h
@@ -22,7 +22,8 @@
 class DiscButton : public Button {
  public:
   DiscButton(base::RepeatingCallback<void()> click_handler,
-             const gfx::VectorIcon& icon);
+             const gfx::VectorIcon& icon,
+             AudioDelegate* audio_delegate);
   ~DiscButton() override;
 
   VectorIcon* foreground() const { return foreground_; }
diff --git a/chrome/browser/vr/elements/disc_button_unittest.cc b/chrome/browser/vr/elements/disc_button_unittest.cc
index 4e7b2c30..b6975c74 100644
--- a/chrome/browser/vr/elements/disc_button_unittest.cc
+++ b/chrome/browser/vr/elements/disc_button_unittest.cc
@@ -18,7 +18,8 @@
 namespace vr {
 
 TEST(DiscButton, HoverTest) {
-  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon);
+  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon,
+                    nullptr);
   button.SetSize(1.0f, 1.0f);
   button.set_hover_offset(0.5f);
 
@@ -45,7 +46,8 @@
 }
 
 TEST(DiscButton, SizePropagatesToSubElements) {
-  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon);
+  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon,
+                    nullptr);
   gfx::SizeF size(1000.0f, 1000.0f);
   gfx::SizeF icon_size = size;
   icon_size.Scale(0.5f);
@@ -69,7 +71,8 @@
 }
 
 TEST(DiscButton, DrawPhasePropagatesToSubElements) {
-  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon);
+  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon,
+                    nullptr);
   button.SetDrawPhase(kPhaseOverlayForeground);
 
   for (auto& child : button.children()) {
@@ -78,7 +81,8 @@
 }
 
 TEST(DiscButton, NamePropagatesToSubElements) {
-  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon);
+  DiscButton button(base::RepeatingCallback<void()>(), vector_icons::kMicIcon,
+                    nullptr);
   button.SetName(kCloseButton);
 
   for (auto& child : button.children()) {
diff --git a/chrome/browser/vr/elements/repositioner.h b/chrome/browser/vr/elements/repositioner.h
index 832f459..33487b5 100644
--- a/chrome/browser/vr/elements/repositioner.h
+++ b/chrome/browser/vr/elements/repositioner.h
@@ -9,7 +9,6 @@
 
 #include "base/macros.h"
 #include "chrome/browser/vr/elements/ui_element.h"
-#include "chrome/browser/vr/model/reticle_model.h"
 #include "ui/gfx/transform.h"
 
 namespace vr {
diff --git a/chrome/browser/vr/elements/resizer.cc b/chrome/browser/vr/elements/resizer.cc
new file mode 100644
index 0000000..0e6498f
--- /dev/null
+++ b/chrome/browser/vr/elements/resizer.cc
@@ -0,0 +1,80 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/vr/elements/resizer.h"
+
+#include "base/numerics/math_constants.h"
+#include "base/numerics/ranges.h"
+#include "chrome/browser/vr/pose_util.h"
+#include "chrome/browser/vr/ui_scene_constants.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/geometry/angle_conversions.h"
+#include "ui/gfx/geometry/quaternion.h"
+
+namespace vr {
+
+namespace {
+
+// Fraction here is in reference to "time fraction" terminology in web
+// animations.
+constexpr float kDefaultFraction = 0.5f;
+
+static_assert(0.5f * (kMinResizerScale + kMaxResizerScale) == 1.0f,
+              "1.0 must be the midpoint of the min and max scale");
+
+}  // namespace
+
+Resizer::Resizer() : t_(kDefaultFraction), initial_t_(kDefaultFraction) {
+  set_bounds_contain_children(true);
+}
+
+Resizer::~Resizer() = default;
+
+gfx::Transform Resizer::LocalTransform() const {
+  return transform_;
+}
+
+gfx::Transform Resizer::GetTargetLocalTransform() const {
+  return transform_;
+}
+
+void Resizer::SetTouchingTouchpad(bool touching) {
+  if (enabled_ && touching) {
+    initial_t_ = t_;
+    initial_touchpad_position_ = touchpad_position_;
+  }
+}
+
+void Resizer::SetEnabled(bool enabled) {
+  enabled_ = enabled;
+  if (enabled) {
+    initial_t_ = t_;
+    initial_touchpad_position_ = touchpad_position_;
+  }
+}
+
+void Resizer::Reset() {
+  transform_.MakeIdentity();
+  t_ = initial_t_ = kDefaultFraction;
+}
+
+void Resizer::UpdateTransform(const gfx::Transform& head_pose) {
+  float delta = touchpad_position_.y() - initial_touchpad_position_.y();
+  t_ = base::ClampToRange(initial_t_ - delta, 0.0f, 1.0f);
+  float scale =
+      gfx::Tween::FloatValueBetween(t_, kMinResizerScale, kMaxResizerScale);
+  transform_.MakeIdentity();
+  transform_.Scale(scale, scale);
+}
+
+bool Resizer::OnBeginFrame(const base::TimeTicks& time,
+                           const gfx::Transform& head_pose) {
+  if (enabled_) {
+    UpdateTransform(head_pose);
+    return true;
+  }
+  return false;
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/elements/resizer.h b/chrome/browser/vr/elements/resizer.h
new file mode 100644
index 0000000..f79427cb1
--- /dev/null
+++ b/chrome/browser/vr/elements/resizer.h
@@ -0,0 +1,55 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VR_ELEMENTS_RESIZER_H_
+#define CHROME_BROWSER_VR_ELEMENTS_RESIZER_H_
+
+#include <sstream>
+
+#include "base/macros.h"
+#include "chrome/browser/vr/elements/ui_element.h"
+#include "ui/gfx/transform.h"
+
+namespace vr {
+
+// When enabled, a resizer scales its descendant elements in response to
+// trackpad use.
+class Resizer : public UiElement {
+ public:
+  Resizer();
+  ~Resizer() override;
+
+  void set_touch_position(const gfx::PointF& position) {
+    touchpad_position_ = position;
+  }
+
+  void SetTouchingTouchpad(bool touching);
+
+  void SetEnabled(bool enabled);
+  void Reset();
+
+ private:
+  gfx::Transform LocalTransform() const override;
+  gfx::Transform GetTargetLocalTransform() const override;
+  void UpdateTransform(const gfx::Transform& head_pose);
+  bool OnBeginFrame(const base::TimeTicks& time,
+                    const gfx::Transform& head_pose) override;
+
+  bool enabled_ = false;
+
+  // Initialized via constants.
+  float t_;
+  float initial_t_;
+
+  gfx::Transform transform_;
+
+  gfx::PointF touchpad_position_;
+  gfx::PointF initial_touchpad_position_;
+
+  DISALLOW_COPY_AND_ASSIGN(Resizer);
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_VR_ELEMENTS_RESIZER_H_
diff --git a/chrome/browser/vr/elements/resizer_unittest.cc b/chrome/browser/vr/elements/resizer_unittest.cc
new file mode 100644
index 0000000..659c7ab
--- /dev/null
+++ b/chrome/browser/vr/elements/resizer_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/vr/elements/resizer.h"
+
+#include <memory>
+
+#include "base/strings/stringprintf.h"
+#include "cc/test/geometry_test_utils.h"
+#include "chrome/browser/vr/test/animation_utils.h"
+#include "chrome/browser/vr/test/constants.h"
+#include "chrome/browser/vr/ui_scene.h"
+#include "chrome/browser/vr/ui_scene_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace vr {
+
+namespace {
+
+class ResizerTest : public testing::Test {
+ public:
+  ResizerTest() {}
+  ~ResizerTest() override {}
+
+  void SetUp() override {
+    auto resizer = std::make_unique<Resizer>();
+    resizer->set_touch_position({0.5f, 0.5f});
+    resizer->SetEnabled(true);
+    resizer_ = resizer.get();
+    scene_.AddUiElement(kRoot, std::move(resizer));
+  }
+
+  void Move(const gfx::PointF& from, const gfx::PointF& to) {
+    resizer_->set_touch_position(from);
+    resizer_->SetTouchingTouchpad(true);
+    scene_.OnBeginFrame(MsToTicks(1), gfx::Transform());
+    resizer_->set_touch_position(to);
+    scene_.OnBeginFrame(MsToTicks(1), gfx::Transform());
+    resizer_->SetTouchingTouchpad(false);
+  }
+
+  float ComputeScale() {
+    gfx::Vector3dF v = {1.0f, 0.0f, 0.0f};
+    static_cast<UiElement*>(resizer_)->LocalTransform().TransformVector(&v);
+    return v.x();
+  }
+
+  void CheckScale(float scale) { EXPECT_FLOAT_EQ(scale, ComputeScale()); }
+
+ protected:
+  Resizer* resizer_ = nullptr;
+  UiScene scene_;
+};
+
+}  // namespace
+
+TEST_F(ResizerTest, HorizontalMove) {
+  Move({0.5f, 0.5f}, {1.0f, 0.5f});
+  CheckScale(1.0f);
+}
+
+TEST_F(ResizerTest, UpwardMove) {
+  Move({0.5f, 0.5f}, {0.5f, 1.0f});
+  CheckScale(kMinResizerScale);
+}
+
+TEST_F(ResizerTest, DownwardMove) {
+  Move({0.5f, 0.5f}, {0.5f, 0.0f});
+  CheckScale(kMaxResizerScale);
+}
+
+TEST_F(ResizerTest, AccumulatedMove) {
+  Move({0.5f, 0.5f}, {0.5f, 0.25f});
+  float scale = ComputeScale();
+  EXPECT_LT(1.0f, scale);
+  EXPECT_GT(kMaxResizerScale, scale);
+  Move({0.5f, 0.5f}, {0.5f, 0.25f});
+  CheckScale(kMaxResizerScale);
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/elements/shadow.cc b/chrome/browser/vr/elements/shadow.cc
index 20232f4..8474533 100644
--- a/chrome/browser/vr/elements/shadow.cc
+++ b/chrome/browser/vr/elements/shadow.cc
@@ -149,6 +149,12 @@
     set_corner_radius(children().front()->corner_radii().MaxRadius());
 }
 
+gfx::SizeF Shadow::ContributedSize() const {
+  gfx::RectF bounds(size());
+  bounds.Inset(x_padding(), y_padding());
+  return bounds.size();
+}
+
 Shadow::Renderer::Renderer()
     : BaseQuadRenderer(kVertexShader, kFragmentShader) {
   model_view_proj_matrix_handle_ =
diff --git a/chrome/browser/vr/elements/shadow.h b/chrome/browser/vr/elements/shadow.h
index bb3edcc..c773c0f 100644
--- a/chrome/browser/vr/elements/shadow.h
+++ b/chrome/browser/vr/elements/shadow.h
@@ -25,6 +25,8 @@
   void LayOutChildren() override;
   void set_intensity(float intensity) { intensity_ = intensity; }
 
+  gfx::SizeF ContributedSize() const override;
+
   class Renderer : public BaseQuadRenderer {
    public:
     Renderer();
@@ -55,6 +57,7 @@
  private:
   float depth_;
   float intensity_ = 1.0f;
+  gfx::SizeF contributed_size_;
 
   DISALLOW_COPY_AND_ASSIGN(Shadow);
 };
diff --git a/chrome/browser/vr/elements/ui_element.cc b/chrome/browser/vr/elements/ui_element.cc
index 15fc1a7..b8d51e07 100644
--- a/chrome/browser/vr/elements/ui_element.cc
+++ b/chrome/browser/vr/elements/ui_element.cc
@@ -13,6 +13,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "chrome/browser/vr/model/camera_model.h"
+#include "chrome/browser/vr/model/sound_id.h"
 #include "third_party/WebKit/public/platform/WebGestureEvent.h"
 #include "third_party/skia/include/core/SkRRect.h"
 #include "third_party/skia/include/core/SkRect.h"
@@ -139,6 +140,9 @@
 void UiElement::Initialize(SkiaSurfaceProvider* provider) {}
 
 void UiElement::OnHoverEnter(const gfx::PointF& position) {
+  if (hover_sound_id_ != kSoundNone && audio_delegate_) {
+    audio_delegate_->PlaySound(hover_sound_id_);
+  }
   if (event_handlers_.hover_enter) {
     event_handlers_.hover_enter.Run();
   } else if (parent() && bubble_events()) {
@@ -163,6 +167,9 @@
 }
 
 void UiElement::OnButtonDown(const gfx::PointF& position) {
+  if (hover_sound_id_ != kSoundNone && audio_delegate_) {
+    audio_delegate_->PlaySound(click_sound_id_);
+  }
   if (event_handlers_.button_down) {
     event_handlers_.button_down.Run();
   } else if (parent() && bubble_events()) {
@@ -248,6 +255,10 @@
 
 void UiElement::OnSetSize(const gfx::SizeF& size) {}
 
+gfx::SizeF UiElement::ContributedSize() const {
+  return size();
+}
+
 void UiElement::SetVisible(bool visible) {
   SetOpacity(visible ? opacity_when_visible_ : 0.0);
 }
@@ -527,6 +538,14 @@
 }
 #endif
 
+void UiElement::SetSounds(SoundId hover,
+                          SoundId click,
+                          AudioDelegate* delegate) {
+  hover_sound_id_ = hover;
+  click_sound_id_ = click;
+  audio_delegate_ = delegate;
+}
+
 void UiElement::OnUpdatedWorldSpaceTransform() {}
 
 gfx::SizeF UiElement::stale_size() const {
@@ -665,13 +684,13 @@
 
   gfx::RectF bounds;
   for (auto& child : children_) {
-    if (!child->IsVisible() || child->size().IsEmpty() ||
+    gfx::SizeF size = child->ContributedSize();
+    if (!child->IsVisible() || size.IsEmpty() ||
         !child->contributes_to_parent_bounds()) {
       continue;
     }
     gfx::Point3F child_center(child->local_origin());
-    gfx::Vector3dF corner_offset(child->size().width(), child->size().height(),
-                                 0);
+    gfx::Vector3dF corner_offset(size.width(), size.height(), 0);
     corner_offset.Scale(-0.5);
     gfx::Point3F child_upper_left = child_center + corner_offset;
     gfx::Point3F child_lower_right = child_center - corner_offset;
diff --git a/chrome/browser/vr/elements/ui_element.h b/chrome/browser/vr/elements/ui_element.h
index 5bdcfd8d..5cf5467 100644
--- a/chrome/browser/vr/elements/ui_element.h
+++ b/chrome/browser/vr/elements/ui_element.h
@@ -15,6 +15,7 @@
 #include "cc/animation/animation_target.h"
 #include "cc/animation/transform_operations.h"
 #include "chrome/browser/vr/animation.h"
+#include "chrome/browser/vr/audio_delegate.h"
 #include "chrome/browser/vr/databinding/binding_base.h"
 #include "chrome/browser/vr/elements/corner_radii.h"
 #include "chrome/browser/vr/elements/draw_phase.h"
@@ -57,11 +58,11 @@
 struct EventHandlers {
   EventHandlers();
   ~EventHandlers();
-  base::Callback<void()> hover_enter;
-  base::Callback<void()> hover_leave;
-  base::Callback<void(const gfx::PointF&)> hover_move;
-  base::Callback<void()> button_down;
-  base::Callback<void()> button_up;
+  base::RepeatingCallback<void()> hover_enter;
+  base::RepeatingCallback<void()> hover_leave;
+  base::RepeatingCallback<void(const gfx::PointF&)> hover_move;
+  base::RepeatingCallback<void()> button_down;
+  base::RepeatingCallback<void()> button_up;
   base::RepeatingCallback<void(bool)> focus_change;
 };
 
@@ -223,6 +224,12 @@
   void SetSize(float width, float hight);
   virtual void OnSetSize(const gfx::SizeF& size);
 
+  // Elements may report a different size to parents that resize to contain
+  // their children. Eg, for shadows.
+  // TODO(crbug.com/820507): change this to LayoutSize and update all layout
+  // code to make use of this instead of size().
+  virtual gfx::SizeF ContributedSize() const;
+
   gfx::PointF local_origin() const { return local_origin_; }
 
   // These are convenience functions for setting the transform operations. They
@@ -443,6 +450,10 @@
   // change your size based on your old size).
   gfx::SizeF stale_size() const;
 
+  // Set the sounds that play when an applicable handler is executed.  Elements
+  // that override element hover and click methods must manage their own sounds.
+  void SetSounds(SoundId hover, SoundId click, AudioDelegate* delegate);
+
  protected:
   Animation& animation() { return animation_; }
 
@@ -568,6 +579,10 @@
 
   UpdatePhase phase_ = kClean;
 
+  AudioDelegate* audio_delegate_ = nullptr;
+  SoundId hover_sound_id_ = kSoundNone;
+  SoundId click_sound_id_ = kSoundNone;
+
   DISALLOW_COPY_AND_ASSIGN(UiElement);
 };
 
diff --git a/chrome/browser/vr/elements/ui_element_name.cc b/chrome/browser/vr/elements/ui_element_name.cc
index d01825af..8fb4ec8 100644
--- a/chrome/browser/vr/elements/ui_element_name.cc
+++ b/chrome/browser/vr/elements/ui_element_name.cc
@@ -24,6 +24,7 @@
     "k2dBrowsingViewportAwareRoot",
     "kWebVrRoot",
     "kWebVrViewportAwareRoot",
+    "kContentResizer",
     "kContentQuad",
     "kContentQuadShadow",
     "kContentQuadRepositionButton",
@@ -41,6 +42,7 @@
     "kFloor",
     "kStars",
     "kUpdateKeyboardPrompt",
+    "kUrlBarPositioner",
     "kUrlBarDmmRoot",
     "kUrlBar",
     "kUrlBarLayout",
diff --git a/chrome/browser/vr/elements/ui_element_name.h b/chrome/browser/vr/elements/ui_element_name.h
index 6d5db0f..882ea8d1 100644
--- a/chrome/browser/vr/elements/ui_element_name.h
+++ b/chrome/browser/vr/elements/ui_element_name.h
@@ -23,6 +23,7 @@
   k2dBrowsingViewportAwareRoot,
   kWebVrRoot,
   kWebVrViewportAwareRoot,
+  kContentResizer,
   kContentQuad,
   kContentQuadShadow,
   kContentQuadRepositionButton,
@@ -40,6 +41,7 @@
   kFloor,
   kStars,
   kUpdateKeyboardPrompt,
+  kUrlBarPositioner,
   kUrlBarDmmRoot,
   kUrlBar,
   kUrlBarLayout,
diff --git a/chrome/browser/vr/model/assets.h b/chrome/browser/vr/model/assets.h
index ff402e0..3d0f7e9 100644
--- a/chrome/browser/vr/model/assets.h
+++ b/chrome/browser/vr/model/assets.h
@@ -29,6 +29,10 @@
   std::unique_ptr<SkBitmap> incognito_gradient;
   std::unique_ptr<SkBitmap> fullscreen_gradient;
 
+  std::unique_ptr<std::string> button_hover_sound;
+  std::unique_ptr<std::string> button_click_sound;
+  std::unique_ptr<std::string> back_button_click_sound;
+
   base::Version version;
 };
 
diff --git a/chrome/browser/vr/model/sound_id.h b/chrome/browser/vr/model/sound_id.h
new file mode 100644
index 0000000..6b2ccd3
--- /dev/null
+++ b/chrome/browser/vr/model/sound_id.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VR_MODEL_SOUND_ID_H_
+#define CHROME_BROWSER_VR_MODEL_SOUND_ID_H_
+
+namespace vr {
+
+enum SoundId {
+  kSoundNone,
+  kSoundButtonHover,
+  kSoundButtonClick,
+  kSoundBackButtonClick,
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_VR_MODEL_SOUND_ID_H_
diff --git a/chrome/browser/vr/sounds_manager_audio_delegate.cc b/chrome/browser/vr/sounds_manager_audio_delegate.cc
new file mode 100644
index 0000000..b4dc3d02
--- /dev/null
+++ b/chrome/browser/vr/sounds_manager_audio_delegate.cc
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/vr/sounds_manager_audio_delegate.h"
+#include "media/audio/sounds/sounds_manager.h"
+
+namespace vr {
+
+SoundsManagerAudioDelegate::SoundsManagerAudioDelegate() {}
+
+SoundsManagerAudioDelegate::~SoundsManagerAudioDelegate() {
+  ResetSounds();
+}
+
+void SoundsManagerAudioDelegate::ResetSounds() {
+  // Because SoundsManager cannot replace a registered sound, start fresh
+  // with a new manager if needed.
+  if (!sounds_.empty()) {
+    media::SoundsManager::Shutdown();
+    sounds_.clear();
+  }
+}
+
+bool SoundsManagerAudioDelegate::RegisterSound(
+    SoundId id,
+    std::unique_ptr<std::string> data) {
+  DCHECK_NE(id, kSoundNone);
+  DCHECK(sounds_.find(id) == sounds_.end());
+
+  if (sounds_.empty())
+    media::SoundsManager::Create();
+
+  sounds_[id] = std::move(data);
+  return media::SoundsManager::Get()->Initialize(id, *sounds_[id]);
+}
+
+void SoundsManagerAudioDelegate::PlaySound(SoundId id) {
+  if (sounds_.find(id) != sounds_.end())
+    media::SoundsManager::Get()->Play(id);
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/sounds_manager_audio_delegate.h b/chrome/browser/vr/sounds_manager_audio_delegate.h
new file mode 100644
index 0000000..48e6a826
--- /dev/null
+++ b/chrome/browser/vr/sounds_manager_audio_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VR_SOUNDS_MANAGER_AUDIO_DELEGATE_H_
+#define CHROME_BROWSER_VR_SOUNDS_MANAGER_AUDIO_DELEGATE_H_
+
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "chrome/browser/vr/audio_delegate.h"
+
+namespace vr {
+
+class SoundsManagerAudioDelegate : public AudioDelegate {
+ public:
+  SoundsManagerAudioDelegate();
+  ~SoundsManagerAudioDelegate() override;
+
+  // AudioDelegate implementation.
+  void ResetSounds() override;
+  bool RegisterSound(SoundId, std::unique_ptr<std::string> data) override;
+  void PlaySound(SoundId id) override;
+
+ private:
+  std::unordered_map<SoundId, std::unique_ptr<std::string>> sounds_;
+
+  DISALLOW_COPY_AND_ASSIGN(SoundsManagerAudioDelegate);
+};
+
+}  //  namespace vr
+
+#endif  // CHROME_BROWSER_VR_SOUNDS_MANAGER_AUDIO_DELEGATE_H_
diff --git a/chrome/browser/vr/test/ui_pixel_test.cc b/chrome/browser/vr/test/ui_pixel_test.cc
index 785d82a1..2b57fb29a 100644
--- a/chrome/browser/vr/test/ui_pixel_test.cc
+++ b/chrome/browser/vr/test/ui_pixel_test.cc
@@ -57,7 +57,7 @@
 
 void UiPixelTest::MakeUi(const UiInitialState& ui_initial_state,
                          const ToolbarState& toolbar_state) {
-  ui_ = std::make_unique<Ui>(browser_.get(), nullptr, nullptr, nullptr,
+  ui_ = std::make_unique<Ui>(browser_.get(), nullptr, nullptr, nullptr, nullptr,
                              ui_initial_state);
   ui_->OnGlInitialized(content_texture_,
                        vr::UiElementRenderer::kTextureLocationLocal,
diff --git a/chrome/browser/vr/test/ui_test.cc b/chrome/browser/vr/test/ui_test.cc
index 47d5110..44b738eb 100644
--- a/chrome/browser/vr/test/ui_test.cc
+++ b/chrome/browser/vr/test/ui_test.cc
@@ -76,7 +76,7 @@
   content_input_delegate_ = content_input_delegate.get();
   ui_ = std::make_unique<Ui>(std::move(browser_.get()),
                              std::move(content_input_delegate), nullptr,
-                             nullptr, state);
+                             nullptr, nullptr, state);
   scene_ = ui_->scene();
   model_ = ui_->model_for_test();
   model_->controller.transform.Translate3d(kStartControllerPosition);
diff --git a/chrome/browser/vr/testapp/vr_test_context.cc b/chrome/browser/vr/testapp/vr_test_context.cc
index eecd327..1b25a21 100644
--- a/chrome/browser/vr/testapp/vr_test_context.cc
+++ b/chrome/browser/vr/testapp/vr_test_context.cc
@@ -57,6 +57,8 @@
 constexpr gfx::Point3F kDefaultLaserOrigin = {0.5f, -0.5f, 0.f};
 constexpr gfx::Vector3dF kLaserLocalOffset = {0.f, -0.0075f, -0.05f};
 constexpr float kControllerScaleFactor = 1.5f;
+constexpr float kTouchpadPositionDelta = 0.05f;
+constexpr gfx::PointF kInitialTouchPosition = {0.5f, 0.5f};
 
 void RotateToward(const gfx::Vector3dF& fwd, gfx::Transform* transform) {
   gfx::Quaternion quat(kForwardVector, fwd);
@@ -90,7 +92,8 @@
 
   UiInitialState ui_initial_state;
   ui_ = std::make_unique<Ui>(this, nullptr, keyboard_delegate_.get(),
-                             text_input_delegate_.get(), ui_initial_state);
+                             text_input_delegate_.get(), nullptr,
+                             ui_initial_state);
   LoadAssets();
 
   text_input_delegate_->SetRequestFocusCallback(
@@ -102,6 +105,8 @@
                           base::Unretained(keyboard_delegate_.get())));
   keyboard_delegate_->SetUiInterface(ui_.get());
 
+  touchpad_touch_position_ = kInitialTouchPosition;
+
   model_ = ui_->model_for_test();
 
   CycleOrigin();
@@ -201,6 +206,9 @@
       case ui::DomCode::US_X:
         ui_->OnAppButtonClicked();
         break;
+      case ui::DomCode::US_T:
+        touching_touchpad_ = !touching_touchpad_;
+        break;
       case ui::DomCode::US_Q:
         model_->active_modal_prompt_type =
             kModalPromptTypeGenericUnsupportedFeature;
@@ -214,9 +222,15 @@
   if (event->IsMouseWheelEvent()) {
     int direction =
         base::ClampToRange(event->AsMouseWheelEvent()->y_offset(), -1, 1);
-    view_scale_factor_ *= (1 + direction * kViewScaleAdjustmentFactor);
-    view_scale_factor_ = base::ClampToRange(
-        view_scale_factor_, kMinViewScaleFactor, kMaxViewScaleFactor);
+    if (event->IsControlDown()) {
+      touchpad_touch_position_.set_y(base::ClampToRange(
+          touchpad_touch_position_.y() + kTouchpadPositionDelta * direction,
+          0.0f, 1.0f));
+    } else {
+      view_scale_factor_ *= (1 + direction * kViewScaleAdjustmentFactor);
+      view_scale_factor_ = base::ClampToRange(
+          view_scale_factor_, kMinViewScaleFactor, kMaxViewScaleFactor);
+    }
     return;
   }
 
@@ -300,6 +314,8 @@
   ControllerModel controller_model;
   controller_model.touchpad_button_state =
       touchpad_pressed_ ? UiInputManager::DOWN : UiInputManager::UP;
+  controller_model.touchpad_touch_position = touchpad_touch_position_;
+  controller_model.touching_touchpad = touching_touchpad_;
 
   controller_model.laser_origin = mouse_point_near;
   controller_model.laser_direction = mouse_point_far - mouse_point_near;
diff --git a/chrome/browser/vr/testapp/vr_test_context.h b/chrome/browser/vr/testapp/vr_test_context.h
index 5ee4ae5a..0aa8e97 100644
--- a/chrome/browser/vr/testapp/vr_test_context.h
+++ b/chrome/browser/vr/testapp/vr_test_context.h
@@ -80,6 +80,7 @@
   int last_drag_y_pixels_ = 0;
   gfx::Point last_mouse_point_;
   bool touchpad_pressed_ = false;
+  gfx::PointF touchpad_touch_position_;
 
   float view_scale_factor_ = 1.f;
 
@@ -90,6 +91,7 @@
   bool incognito_ = false;
   bool show_web_vr_splash_screen_ = false;
   bool voice_search_enabled_ = false;
+  bool touching_touchpad_ = false;
   base::TimeTicks page_load_start_;
 
   ControllerModel last_controller_model_;
diff --git a/chrome/browser/vr/ui.cc b/chrome/browser/vr/ui.cc
index ea703bf..b349a86 100644
--- a/chrome/browser/vr/ui.cc
+++ b/chrome/browser/vr/ui.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/vr/model/assets.h"
 #include "chrome/browser/vr/model/model.h"
 #include "chrome/browser/vr/model/omnibox_suggestions.h"
+#include "chrome/browser/vr/model/sound_id.h"
 #include "chrome/browser/vr/speech_recognizer.h"
 #include "chrome/browser/vr/ui_browser_interface.h"
 #include "chrome/browser/vr/ui_element_renderer.h"
@@ -32,25 +33,29 @@
 
 Ui::Ui(UiBrowserInterface* browser,
        ContentInputForwarder* content_input_forwarder,
-       vr::KeyboardDelegate* keyboard_delegate,
-       vr::TextInputDelegate* text_input_delegate,
+       KeyboardDelegate* keyboard_delegate,
+       TextInputDelegate* text_input_delegate,
+       AudioDelegate* audio_delegate,
        const UiInitialState& ui_initial_state)
     : Ui(browser,
          std::make_unique<ContentInputDelegate>(content_input_forwarder),
          keyboard_delegate,
          text_input_delegate,
+         audio_delegate,
          ui_initial_state) {}
 
 Ui::Ui(UiBrowserInterface* browser,
        std::unique_ptr<ContentInputDelegate> content_input_delegate,
-       vr::KeyboardDelegate* keyboard_delegate,
-       vr::TextInputDelegate* text_input_delegate,
+       KeyboardDelegate* keyboard_delegate,
+       TextInputDelegate* text_input_delegate,
+       AudioDelegate* audio_delegate,
        const UiInitialState& ui_initial_state)
     : browser_(browser),
       scene_(std::make_unique<UiScene>()),
       model_(std::make_unique<Model>()),
       content_input_delegate_(std::move(content_input_delegate)),
       input_manager_(std::make_unique<UiInputManager>(scene_.get())),
+      audio_delegate_(audio_delegate),
       weak_ptr_factory_(this) {
   UiInitialState state = ui_initial_state;
   if (keyboard_delegate != nullptr)
@@ -58,7 +63,8 @@
   InitializeModel(state);
 
   UiSceneCreator(browser, scene_.get(), this, content_input_delegate_.get(),
-                 keyboard_delegate, text_input_delegate, model_.get())
+                 keyboard_delegate, text_input_delegate, audio_delegate,
+                 model_.get())
       .CreateScene();
 }
 
@@ -404,6 +410,19 @@
 
   ColorScheme::UpdateForComponent(component_version);
   model_->background_loaded = true;
+
+  if (audio_delegate_) {
+    std::vector<std::pair<SoundId, std::unique_ptr<std::string>&>> sounds = {
+        {kSoundButtonHover, assets->button_hover_sound},
+        {kSoundButtonClick, assets->button_click_sound},
+        {kSoundBackButtonClick, assets->back_button_click_sound},
+    };
+    audio_delegate_->ResetSounds();
+    for (auto& sound : sounds) {
+      if (sound.second)
+        audio_delegate_->RegisterSound(sound.first, std::move(sound.second));
+    }
+  }
 }
 
 void Ui::OnAssetsUnavailable() {
diff --git a/chrome/browser/vr/ui.h b/chrome/browser/vr/ui.h
index ee25e69..decd571 100644
--- a/chrome/browser/vr/ui.h
+++ b/chrome/browser/vr/ui.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/vr/ui_element_renderer.h"
 
 namespace vr {
+class AudioDelegate;
 class BrowserUiInterface;
 class ContentInputDelegate;
 class ContentInputForwarder;
@@ -51,14 +52,16 @@
  public:
   Ui(UiBrowserInterface* browser,
      ContentInputForwarder* content_input_forwarder,
-     vr::KeyboardDelegate* keyboard_delegate,
-     vr::TextInputDelegate* text_input_delegate,
+     KeyboardDelegate* keyboard_delegate,
+     TextInputDelegate* text_input_delegate,
+     AudioDelegate* audio_delegate,
      const UiInitialState& ui_initial_state);
 
   Ui(UiBrowserInterface* browser,
      std::unique_ptr<ContentInputDelegate> content_input_delegate,
-     vr::KeyboardDelegate* keyboard_delegate,
-     vr::TextInputDelegate* text_input_delegate,
+     KeyboardDelegate* keyboard_delegate,
+     TextInputDelegate* text_input_delegate,
+     AudioDelegate* audio_delegate,
      const UiInitialState& ui_initial_state);
 
   ~Ui() override;
@@ -71,7 +74,7 @@
   UiRenderer* ui_renderer() { return ui_renderer_.get(); }
   UiInputManager* input_manager() { return input_manager_.get(); }
 
-  base::WeakPtr<vr::BrowserUiInterface> GetBrowserUiWeakPtr();
+  base::WeakPtr<BrowserUiInterface> GetBrowserUiWeakPtr();
 
   // BrowserUiInterface
   void SetWebVrMode(bool enabled, bool show_toast) override;
@@ -165,14 +168,16 @@
   UiBrowserInterface* browser_;
 
   // This state may be further abstracted into a SkiaUi object.
-  std::unique_ptr<vr::UiScene> scene_;
-  std::unique_ptr<vr::Model> model_;
-  std::unique_ptr<vr::ContentInputDelegate> content_input_delegate_;
-  std::unique_ptr<vr::UiElementRenderer> ui_element_renderer_;
-  std::unique_ptr<vr::UiInputManager> input_manager_;
-  std::unique_ptr<vr::UiRenderer> ui_renderer_;
+  std::unique_ptr<UiScene> scene_;
+  std::unique_ptr<Model> model_;
+  std::unique_ptr<ContentInputDelegate> content_input_delegate_;
+  std::unique_ptr<UiElementRenderer> ui_element_renderer_;
+  std::unique_ptr<UiInputManager> input_manager_;
+  std::unique_ptr<UiRenderer> ui_renderer_;
   std::unique_ptr<SkiaSurfaceProvider> provider_;
 
+  AudioDelegate* audio_delegate_ = nullptr;
+
   base::WeakPtrFactory<Ui> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(Ui);
diff --git a/chrome/browser/vr/ui_scene_constants.h b/chrome/browser/vr/ui_scene_constants.h
index a38e736f..985c683 100644
--- a/chrome/browser/vr/ui_scene_constants.h
+++ b/chrome/browser/vr/ui_scene_constants.h
@@ -56,6 +56,10 @@
 static constexpr float kUrlBarDistance = 2.4f;
 static constexpr float kUrlBarWidthDMM = 0.672f;
 static constexpr float kUrlBarHeightDMM = 0.088f;
+// This is the non-DMM relative offset of the URL bar. It is used to position
+// the DMM root of the URL bar.
+static constexpr float kUrlBarRelativeOffset = -0.45f;
+// This is the absolute offset of the URL bar's neutral position in DMM.
 static constexpr float kUrlBarVerticalOffsetDMM = -0.516f;
 static constexpr float kUrlBarRotationRad = gfx::DegToRad(-10.0f);
 static constexpr float kUrlBarFontHeightDMM = 0.027f;
@@ -299,6 +303,9 @@
 static constexpr float kPromptShadowOffsetDMM = 0.1f;
 static constexpr float kPromptDistance = 2.4f;
 
+static constexpr float kMinResizerScale = 0.5f;
+static constexpr float kMaxResizerScale = 1.5f;
+
 }  // namespace vr
 
 #endif  // CHROME_BROWSER_VR_UI_SCENE_CONSTANTS_H_
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 1772b61..7b26b553 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -33,6 +33,7 @@
 #include "chrome/browser/vr/elements/prompt.h"
 #include "chrome/browser/vr/elements/rect.h"
 #include "chrome/browser/vr/elements/repositioner.h"
+#include "chrome/browser/vr/elements/resizer.h"
 #include "chrome/browser/vr/elements/reticle.h"
 #include "chrome/browser/vr/elements/scaled_depth_adjuster.h"
 #include "chrome/browser/vr/elements/spinner.h"
@@ -132,6 +133,7 @@
                             UiBrowserInterface* browser,
                             Ui* ui,
                             Model* model,
+                            AudioDelegate* audio_delegate,
                             SuggestionBinding* element_binding) {
   auto icon = std::make_unique<VectorIcon>(100);
   icon->SetDrawPhase(kPhaseForeground);
@@ -197,6 +199,7 @@
   background->set_bubble_events(true);
   background->set_bounds_contain_children(true);
   background->set_hover_offset(0.0);
+  background->SetSounds(kSoundButtonHover, kSoundButtonClick, audio_delegate);
   VR_BIND_BUTTON_COLORS(model, background.get(),
                         &ColorScheme::suggestion_button_colors,
                         &Button::SetButtonColors);
@@ -473,6 +476,7 @@
   auto scaler = Create<ScaledDepthAdjuster>(name, kPhaseNone, kPromptDistance);
   scaler->SetType(kTypeScaledDepthAdjuster);
   scaler->AddChild(std::move(backplane));
+  scaler->set_contributes_to_parent_bounds(false);
   return scaler;
 }
 
@@ -592,6 +596,7 @@
                                ContentInputDelegate* content_input_delegate,
                                KeyboardDelegate* keyboard_delegate,
                                TextInputDelegate* text_input_delegate,
+                               AudioDelegate* audio_delegate,
                                Model* model)
     : browser_(browser),
       scene_(scene),
@@ -599,6 +604,7 @@
       content_input_delegate_(content_input_delegate),
       keyboard_delegate_(keyboard_delegate),
       text_input_delegate_(text_input_delegate),
+      audio_delegate_(audio_delegate),
       model_(model) {}
 
 UiSceneCreator::~UiSceneCreator() {}
@@ -706,6 +712,7 @@
   scene_->AddUiElement(k2dBrowsingRoot, std::move(element));
 
   auto repositioner = Create<Repositioner>(k2dBrowsingRepositioner, kPhaseNone);
+  repositioner->set_bounds_contain_children(true);
   repositioner->AddBinding(
       VR_BIND_FUNC(bool, Model, model_, model->reposition_window_enabled(),
                    Repositioner, repositioner.get(), SetEnabled));
@@ -718,10 +725,12 @@
   scene_->AddUiElement(k2dBrowsingRoot, std::move(repositioner));
 
   element = Create<UiElement>(k2dBrowsingVisibiltyControlForVoice, kPhaseNone);
+  element->set_bounds_contain_children(true);
   scene_->AddUiElement(k2dBrowsingRepositioner, std::move(element));
 
   element =
       Create<UiElement>(k2dBrowsingVisibilityControlForPrompt, kPhaseNone);
+  element->set_bounds_contain_children(true);
   VR_BIND_VISIBILITY(
       element,
       model->active_modal_prompt_type == kModalPromptTypeNone ||
@@ -732,6 +741,7 @@
 
   element = Create<UiElement>(k2dBrowsingVisibiltyControlForSiteInfoPrompt,
                               kPhaseNone);
+  element->set_bounds_contain_children(true);
   VR_BIND_VISIBILITY(element, model->active_modal_prompt_type !=
                                   kModalPromptTypeExitVRForSiteInfo);
 
@@ -740,6 +750,7 @@
 
   element = Create<UiElement>(k2dBrowsingOpacityControlForAudioPermissionPrompt,
                               kPhaseNone);
+  element->set_bounds_contain_children(true);
   element->AddBinding(
       VR_BIND(bool, Model, model_,
               model->active_modal_prompt_type !=
@@ -751,6 +762,7 @@
 
   element = Create<UiElement>(k2dBrowsingOpacityControlForNativeDialogPrompt,
                               kPhaseNone);
+  element->set_bounds_contain_children(true);
   element->SetTransitionedProperties({OPACITY});
   element->AddBinding(VR_BIND(
       bool, Model, model_, !model->native_ui.hosted_ui_enabled, UiElement,
@@ -760,6 +772,7 @@
 
   element = Create<UiElement>(k2dBrowsingOpacityControlForUpdateKeyboardPrompt,
                               kPhaseNone);
+  element->set_bounds_contain_children(true);
   element->SetTransitionedProperties({OPACITY});
   element->AddBinding(
       VR_BIND(bool, Model, model_,
@@ -770,6 +783,7 @@
                        std::move(element));
 
   element = Create<UiElement>(k2dBrowsingForeground, kPhaseNone);
+  element->set_bounds_contain_children(true);
   element->SetTransitionedProperties({OPACITY});
   element->SetTransitionDuration(base::TimeDelta::FromMilliseconds(
       kSpeechRecognitionOpacityAnimationDurationMs));
@@ -780,8 +794,8 @@
 
   element = Create<UiElement>(k2dBrowsingContentGroup, kPhaseNone);
   element->SetTranslate(0, kContentVerticalOffset, -kContentDistance);
-  element->SetSize(kContentWidth, kContentHeight);
   element->SetTransitionedProperties({TRANSFORM});
+  element->set_bounds_contain_children(true);
   element->AddBinding(
       VR_BIND(bool, Model, model_, model->fullscreen_enabled(), UiElement,
               element.get(),
@@ -866,6 +880,7 @@
   indicator_layout->SetTranslate(0, kIndicatorVerticalOffset,
                                  kIndicatorDistanceOffset);
   indicator_layout->set_margin(kIndicatorGap);
+  indicator_layout->set_contributes_to_parent_bounds(false);
   VR_BIND_VISIBILITY(indicator_layout, !model->fullscreen_enabled());
 
   for (const auto& indicator : indicators) {
@@ -912,8 +927,23 @@
   // reticle roughly planar with the content if near content.
   auto hit_plane = Create<InvisibleHitTarget>(kBackplane, kPhaseForeground);
   hit_plane->SetSize(kBackplaneSize, kSceneHeight);
+  hit_plane->set_contributes_to_parent_bounds(false);
   scene_->AddUiElement(k2dBrowsingContentGroup, std::move(hit_plane));
 
+  auto resizer = Create<Resizer>(kContentResizer, kPhaseNone);
+  resizer->AddBinding(VR_BIND_FUNC(bool, Model, model_,
+                                   model->reposition_window_enabled(), Resizer,
+                                   resizer.get(), SetEnabled));
+  resizer->AddBinding(VR_BIND_FUNC(gfx::PointF, Model, model_,
+                                   model->controller.touchpad_touch_position,
+                                   Resizer, resizer.get(), set_touch_position));
+  resizer->AddBinding(VR_BIND_FUNC(bool, Model, model_,
+                                   model->controller.touching_touchpad, Resizer,
+                                   resizer.get(), SetTouchingTouchpad));
+  resizer->AddBinding(VR_BIND(bool, Model, model_, model->controller.recentered,
+                              Resizer, resizer.get(),
+                              if (value) { view->Reset(); }));
+
   auto shadow = Create<Shadow>(kContentQuadShadow, kPhaseForeground);
   shadow->set_intensity(kContentShadowIntesity);
   shadow->SetTranslate(0, 0, -kContentShadowOffset);
@@ -983,7 +1013,8 @@
                         const EditedText& value) { e->UpdateInput(value); },
                      base::Unretained(main_content.get()))));
   shadow->AddChild(std::move(main_content));
-  scene_->AddUiElement(k2dBrowsingContentGroup, std::move(shadow));
+  resizer->AddChild(std::move(shadow));
+  scene_->AddUiElement(k2dBrowsingContentGroup, std::move(resizer));
 
   // Limit reticle distance to a sphere based on maximum content distance.
   scene_->set_background_distance(kFullscreenDistance *
@@ -1130,7 +1161,7 @@
       Create<DiscButton>(kWebVrTimeoutMessageButton, kPhaseForeground,
                          base::BindRepeating(&UiBrowserInterface::ExitPresent,
                                              base::Unretained(browser_)),
-                         vector_icons::kClose16Icon);
+                         vector_icons::kClose16Icon, audio_delegate_);
   button->SetVisible(false);
   button->SetTranslate(0, -kTimeoutMessageTextWidthDMM, 0);
   button->SetRotate(1, 0, 0, kTimeoutButtonRotationRad);
@@ -1271,12 +1302,14 @@
 
   element = std::make_unique<ViewportAwareRoot>();
   element->SetName(k2dBrowsingViewportAwareRoot);
+  element->set_contributes_to_parent_bounds(false);
   scene_->AddUiElement(k2dBrowsingRepositioner, std::move(element));
 }
 
 void UiSceneCreator::CreateVoiceSearchUiGroup() {
   auto speech_recognition_root = std::make_unique<UiElement>();
   speech_recognition_root->SetName(kSpeechRecognitionRoot);
+  speech_recognition_root->set_contributes_to_parent_bounds(false);
   speech_recognition_root->SetTranslate(0.f, 0.f, -kContentDistance);
   speech_recognition_root->SetTransitionedProperties({OPACITY});
   speech_recognition_root->SetTransitionDuration(
@@ -1403,7 +1436,7 @@
       kSpeechRecognitionListeningCloseButton, kPhaseForeground,
       base::BindRepeating(&UiBrowserInterface::SetVoiceSearchActive,
                           base::Unretained(browser_), false),
-      vector_icons::kClose16Icon);
+      vector_icons::kClose16Icon, audio_delegate_);
   close_button->SetSize(kVoiceSearchCloseButtonDiameter,
                         kVoiceSearchCloseButtonDiameter);
   close_button->set_hover_offset(kButtonZOffsetHoverDMM * kContentDistance);
@@ -1448,7 +1481,7 @@
       base::BindRepeating(
           [](Model* model) { model->push_mode(kModeRepositionWindow); },
           base::Unretained(model_)),
-      kRepositionIcon);
+      kRepositionIcon, audio_delegate_);
   reposition_button->SetSize(kRepositionButtonDiameter,
                              kRepositionButtonDiameter);
   reposition_button->set_y_anchoring(BOTTOM);
@@ -1463,6 +1496,7 @@
   reposition_button->background()->SetTransitionedProperties(
       {BACKGROUND_COLOR, TRANSFORM});
   reposition_button->SetOpacity(kRepositionButtonMinOpacity);
+  reposition_button->SetSounds(kSoundNone, kSoundNone, nullptr);
   reposition_button->AddBinding(std::make_unique<Binding<float>>(
       VR_BIND_LAMBDA(
           [](Model* model, Button* button) {
@@ -1494,6 +1528,7 @@
   label_background->set_corner_radius(kRepositionLabelBackgroundCornerRadius);
   label_background->set_padding(kRepositionLabelBackgroundPadding,
                                 kRepositionLabelBackgroundPadding);
+  label_background->set_contributes_to_parent_bounds(false);
   VR_BIND_COLOR(model_, label_background.get(),
                 &ColorScheme::reposition_label_background, &Rect::SetColor);
   VR_BIND_VISIBILITY(label_background, model->reposition_window_enabled());
@@ -1515,14 +1550,16 @@
   auto content_toggle =
       Create<UiElement>(kContentRepositionVisibilityToggle, kPhaseNone);
   content_toggle->SetTransitionedProperties({OPACITY});
+  content_toggle->set_bounds_contain_children(true);
   content_toggle->AddBinding(VR_BIND_FUNC(
       float, Model, model_,
       model->reposition_window_enabled() ? kRepositionContentOpacity : 1.0f,
       UiElement, content_toggle.get(), SetOpacity));
-  scene_->AddParentUiElement(kContentQuadShadow, std::move(content_toggle));
+  scene_->AddParentUiElement(kContentResizer, std::move(content_toggle));
 
   auto hit_plane =
       Create<InvisibleHitTarget>(kContentRepositionHitPlane, kPhaseForeground);
+  hit_plane->set_contributes_to_parent_bounds(false);
   hit_plane->SetSize(kSceneSize, kSceneSize);
   hit_plane->SetTranslate(0.0f, 0.0f, -kContentDistance);
   EventHandlers event_handlers;
@@ -1625,6 +1662,7 @@
 void UiSceneCreator::CreateKeyboard() {
   auto visibility_control_root =
       Create<UiElement>(kKeyboardVisibilityControlForVoice, kPhaseNone);
+  visibility_control_root->set_contributes_to_parent_bounds(false);
   BIND_VISIBILITY_CONTROL_FOR_VOICE(visibility_control_root.get(), model_,
                                     editing_enabled());
 
@@ -1657,12 +1695,18 @@
 }
 
 void UiSceneCreator::CreateUrlBar() {
+  auto positioner = Create<UiElement>(kUrlBarPositioner, kPhaseNone);
+  positioner->set_y_anchoring(BOTTOM);
+  positioner->SetTranslate(0, kUrlBarRelativeOffset, 0);
+  positioner->set_contributes_to_parent_bounds(false);
+  scene_->AddUiElement(k2dBrowsingForeground, std::move(positioner));
+
   auto scaler = std::make_unique<ScaledDepthAdjuster>(kUrlBarDistance);
   scaler->SetName(kUrlBarDmmRoot);
-  scene_->AddUiElement(k2dBrowsingForeground, std::move(scaler));
+  scaler->set_contributes_to_parent_bounds(false);
+  scene_->AddUiElement(kUrlBarPositioner, std::move(scaler));
 
   auto url_bar = Create<UiElement>(kUrlBar, kPhaseNone);
-  url_bar->SetTranslate(0, kUrlBarVerticalOffsetDMM, 0);
   url_bar->SetRotate(1, 0, 0, kUrlBarRotationRad);
   url_bar->set_bounds_contain_children(true);
   VR_BIND_VISIBILITY(url_bar, !model->fullscreen_enabled());
@@ -1681,6 +1725,7 @@
   back_button->SetCornerRadii(
       {kUrlBarHeightDMM / 2, 0, kUrlBarHeightDMM / 2, 0});
   back_button->set_hover_offset(0.0f);
+  back_button->SetSounds(kSoundButtonHover, kSoundButtonClick, audio_delegate_);
   back_button->AddBinding(VR_BIND_FUNC(bool, Model, model_,
                                        model->can_navigate_back, Button,
                                        back_button.get(), set_enabled));
@@ -1826,6 +1871,7 @@
 void UiSceneCreator::CreateOmnibox() {
   auto visibility_control_root =
       Create<UiElement>(kOmniboxVisibiltyControlForVoice, kPhaseNone);
+  visibility_control_root->set_contributes_to_parent_bounds(false);
 
   auto scaler = std::make_unique<ScaledDepthAdjuster>(kUrlBarDistance);
   scaler->SetName(kOmniboxDmmRoot);
@@ -2043,6 +2089,8 @@
   mic_icon_box->SetSize(kOmniboxTextFieldIconButtonSizeDMM,
                         kOmniboxTextFieldIconButtonSizeDMM);
   mic_icon_box->set_corner_radius(kOmniboxTextFieldIconButtonRadiusDMM);
+  mic_icon_box->SetSounds(kSoundButtonHover, kSoundButtonClick,
+                          audio_delegate_);
   VR_BIND_VISIBILITY(mic_icon_box,
                      model->speech.has_or_can_request_audio_permission &&
                          !model->incognito &&
@@ -2072,10 +2120,10 @@
   text_field_layout->AddChild(std::move(right_spacer));
 
   // Set up the vector binding to manage suggestions dynamically.
-  SuggestionSetBinding::ModelAddedCallback added_callback =
-      base::BindRepeating(&OnSuggestionModelAdded, base::Unretained(scene_),
-                          base::Unretained(browser_), base::Unretained(ui_),
-                          base::Unretained(model_));
+  SuggestionSetBinding::ModelAddedCallback added_callback = base::BindRepeating(
+      &OnSuggestionModelAdded, base::Unretained(scene_),
+      base::Unretained(browser_), base::Unretained(ui_),
+      base::Unretained(model_), base::Unretained(audio_delegate_));
   SuggestionSetBinding::ModelRemovedCallback removed_callback =
       base::BindRepeating(&OnSuggestionModelRemoved, base::Unretained(scene_));
 
@@ -2097,7 +2145,7 @@
       base::BindRepeating(
           [](Model* model) { model->pop_mode(kModeEditingOmnibox); },
           base::Unretained(model_)),
-      vector_icons::kBackArrowIcon);
+      vector_icons::kBackArrowIcon, audio_delegate_);
   close_button->SetSize(kOmniboxCloseButtonDiameterDMM,
                         kOmniboxCloseButtonDiameterDMM);
   close_button->SetTranslate(0, kOmniboxCloseButtonVerticalOffsetDMM, 0);
@@ -2148,7 +2196,8 @@
       base::Unretained(model_), base::Unretained(browser_));
   std::unique_ptr<DiscButton> element =
       Create<DiscButton>(kCloseButton, kPhaseForeground, click_handler,
-                         vector_icons::kClose16Icon);
+                         vector_icons::kClose16Icon, audio_delegate_);
+  element->set_contributes_to_parent_bounds(false);
   element->SetSize(kCloseButtonDiameter, kCloseButtonDiameter);
   element->set_hover_offset(kButtonZOffsetHoverDMM * kCloseButtonDistance);
   element->SetTranslate(0, kCloseButtonVerticalOffset, -kCloseButtonDistance);
@@ -2190,6 +2239,7 @@
   auto backplane = std::make_unique<InvisibleHitTarget>();
   backplane->SetDrawPhase(kPhaseForeground);
   backplane->SetName(kExitPromptBackplane);
+  backplane->set_contributes_to_parent_bounds(false);
   backplane->SetSize(kBackplaneSize, kBackplaneSize);
   backplane->SetTranslate(0.0,
                           kContentVerticalOffset + kExitPromptVerticalOffset,
@@ -2368,6 +2418,7 @@
 void UiSceneCreator::CreateFullscreenToast() {
   auto parent = CreateTransientParent(kExclusiveScreenToastTransientParent,
                                       kToastTimeoutSeconds, false);
+  parent->set_contributes_to_parent_bounds(false);
   VR_BIND_VISIBILITY(parent, model->fullscreen_enabled());
   scene_->AddUiElement(k2dBrowsingForeground, std::move(parent));
 
diff --git a/chrome/browser/vr/ui_scene_creator.h b/chrome/browser/vr/ui_scene_creator.h
index f8b77bac..8395a0b7 100644
--- a/chrome/browser/vr/ui_scene_creator.h
+++ b/chrome/browser/vr/ui_scene_creator.h
@@ -29,6 +29,7 @@
                  ContentInputDelegate* content_input_delegate,
                  KeyboardDelegate* keyboard_delegate,
                  TextInputDelegate* text_input_delegate,
+                 AudioDelegate* audio_delegate,
                  Model* model);
   ~UiSceneCreator();
 
@@ -66,6 +67,7 @@
   ContentInputDelegate* content_input_delegate_;
   KeyboardDelegate* keyboard_delegate_;
   TextInputDelegate* text_input_delegate_;
+  AudioDelegate* audio_delegate_;
   Model* model_;
 
   DISALLOW_COPY_AND_ASSIGN(UiSceneCreator);
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index a3b91be2..ed2dd20d 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -1225,17 +1225,24 @@
 
 TEST_F(UiTest, ResetRepositioner) {
   CreateScene(kNotInCct, kNotInWebVr);
+
   Repositioner* repositioner = static_cast<Repositioner*>(
       scene_->GetUiElementByName(k2dBrowsingRepositioner));
+
+  OnBeginFrame();
+  gfx::Transform original = repositioner->world_space_transform();
+
   repositioner->set_laser_direction(kForwardVector);
   repositioner->SetEnabled(true);
   repositioner->set_laser_direction({0, 1, 0});
   OnBeginFrame();
-  EXPECT_FALSE(repositioner->world_space_transform().IsIdentity());
+
+  EXPECT_NE(original, repositioner->world_space_transform());
   repositioner->SetEnabled(false);
   model_->controller.recentered = true;
+
   OnBeginFrame();
-  EXPECT_TRUE(repositioner->world_space_transform().IsIdentity());
+  EXPECT_EQ(original, repositioner->world_space_transform());
 }
 
 // No element in the controller root's subtree should be hit testable.
@@ -1246,4 +1253,20 @@
     EXPECT_FALSE(child.IsHitTestable());
 }
 
+TEST_F(UiTest, BrowsingRootBounds) {
+  CreateScene(kNotInCct, kNotInWebVr);
+  auto* elem = scene_->GetUiElementByName(k2dBrowsingContentGroup);
+  auto* root = scene_->GetUiElementByName(k2dBrowsingRepositioner);
+  for (; elem; elem = elem->parent()) {
+    int num_bounds_contributors = 0;
+    for (auto& child : elem->children()) {
+      if (child->contributes_to_parent_bounds())
+        num_bounds_contributors++;
+    }
+    EXPECT_EQ(1, num_bounds_contributors);
+    if (elem == root)
+      break;
+  }
+}
+
 }  // namespace vr
diff --git a/chrome/common/media_router/media_status.cc b/chrome/common/media_router/media_status.cc
index 3f21339..2b20f826 100644
--- a/chrome/common/media_router/media_status.cc
+++ b/chrome/common/media_router/media_status.cc
@@ -20,7 +20,7 @@
 MediaStatus& MediaStatus::operator=(const MediaStatus& other) = default;
 
 bool MediaStatus::operator==(const MediaStatus& other) const {
-  return title == other.title && description == other.description &&
+  return title == other.title &&
          can_play_pause == other.can_play_pause && can_mute == other.can_mute &&
          can_set_volume == other.can_set_volume && can_seek == other.can_seek &&
          play_state == other.play_state && is_muted == other.is_muted &&
diff --git a/chrome/common/media_router/media_status.h b/chrome/common/media_router/media_status.h
index c2d464ac..9fbd24f 100644
--- a/chrome/common/media_router/media_status.h
+++ b/chrome/common/media_router/media_status.h
@@ -43,12 +43,6 @@
   // a YouTube Cast session, this could be the title of the video.
   std::string title;
 
-  // Text describing the media, or a secondary title. For example, in a
-  // MediaStatus representing a YouTube Cast session, this could be "YouTube".
-  //
-  // DEPRECATED.  TODO(crbug.com/786208): Remove this when no longer used.
-  std::string description;
-
   // If this is true, the media can be played and paused.
   bool can_play_pause = false;
 
diff --git a/chrome/common/media_router/mojo/media_status.mojom b/chrome/common/media_router/mojo/media_status.mojom
index 09b6684..6811111 100644
--- a/chrome/common/media_router/mojo/media_status.mojom
+++ b/chrome/common/media_router/mojo/media_status.mojom
@@ -22,15 +22,6 @@
   // For a Presentation API route, it is the presentation page title.
   string title;
 
-  // Text describing the media, or a secondary title. For example, in a
-  // MediaStatus representing a YouTube Cast session, this could be "YouTube".
-  //
-  // DEPRECATED.  This is used to override MediaRoute.description (defined in
-  // media_router.mojom) in the Media Router UX.  We will be using
-  // MediaRoute.description exclusively once all MRPs have been updated.
-  // TODO(crbug.com/786208): Remove this when that is done.
-  string description;
-
   // If this is true, the media can be played and paused through its
   // MediaController.
   bool can_play_pause;
diff --git a/chrome/common/media_router/mojo/media_status_struct_traits.cc b/chrome/common/media_router/mojo/media_status_struct_traits.cc
index fd37cf88..9e4179ae 100644
--- a/chrome/common/media_router/mojo/media_status_struct_traits.cc
+++ b/chrome/common/media_router/mojo/media_status_struct_traits.cc
@@ -17,11 +17,6 @@
   if (!data.ReadTitle(&out->title) || !base::IsStringUTF8(out->title))
     return false;
 
-  if (!data.ReadDescription(&out->description) ||
-      !base::IsStringUTF8(out->description)) {
-    return false;
-  }
-
   out->can_play_pause = data.can_play_pause();
   out->can_mute = data.can_mute();
   out->can_set_volume = data.can_set_volume();
diff --git a/chrome/common/media_router/mojo/media_status_struct_traits.h b/chrome/common/media_router/mojo/media_status_struct_traits.h
index 4fe09f7..312fae4 100644
--- a/chrome/common/media_router/mojo/media_status_struct_traits.h
+++ b/chrome/common/media_router/mojo/media_status_struct_traits.h
@@ -58,11 +58,6 @@
     return status.title;
   }
 
-  static const std::string& description(
-      const media_router::MediaStatus& status) {
-    return status.description;
-  }
-
   static bool can_play_pause(const media_router::MediaStatus& status) {
     return status.can_play_pause;
   }
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 301d6a6..3874615c 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1372,12 +1372,6 @@
   return module_name == "Native Client";
 }
 
-bool ChromeContentRendererClient::IsOriginIsolatedPepperPlugin(
-    const base::FilePath& plugin_path) {
-  return plugin_path ==
-         base::FilePath::FromUTF8Unsafe(ChromeContentClient::kPDFPluginPath);
-}
-
 #if BUILDFLAG(ENABLE_PLUGINS) && BUILDFLAG(ENABLE_EXTENSIONS)
 bool ChromeContentRendererClient::IsExtensionOrSharedModuleWhitelisted(
     const GURL& url,
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index 1b30fe68..d3bdf1b 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -169,7 +169,6 @@
       const content::RenderFrame* render_frame,
       blink::mojom::PageVisibilityState* override_state) override;
   bool IsExternalPepperPlugin(const std::string& module_name) override;
-  bool IsOriginIsolatedPepperPlugin(const base::FilePath& plugin_path) override;
   std::unique_ptr<blink::WebSocketHandshakeThrottle>
   CreateWebSocketHandshakeThrottle() override;
   std::unique_ptr<blink::WebSpeechSynthesizer> OverrideSpeechSynthesizer(
diff --git a/chrome/renderer/resources/extensions/media_router_bindings.js b/chrome/renderer/resources/extensions/media_router_bindings.js
index b270be5..1260b400 100644
--- a/chrome/renderer/resources/extensions/media_router_bindings.js
+++ b/chrome/renderer/resources/extensions/media_router_bindings.js
@@ -160,7 +160,6 @@
  */
 function MediaStatusAdapter(fields) {
   this.title = null;
-  this.description = null;
   this.can_play_pause = false;
   this.can_mute = false;
   this.can_set_volume = false;
@@ -180,7 +179,6 @@
 MediaStatusAdapter.prototype.toNewVersion = function() {
   return new mediaRouter.mojom.MediaStatus({
     'title': this.title,
-    'description': this.description,
     'canPlayPause': this.can_play_pause,
     'canMute': this.can_mute,
     'canSetVolume': this.can_set_volume,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 143d085..4cad318 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2124,10 +2124,7 @@
 group("performance_test_suite") {
   testonly = true
   deps = [
-    # Comment this in when testing on a trybot so it does
-    # not have to rebuild chrome each time.
-    "//chrome/test:telemetry_perf_tests_experimental",
-    #"//chrome/test:telemetry_perf_tests",
+    "//chrome/test:telemetry_perf_tests",
   ]
 
   data = [
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index e74c5a3..758e3be 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -285,6 +285,7 @@
         'ElementAttributeTest.testShouldReturnValueOfOnClickAttribute',
         'ElementAttributeTest.testCanRetrieveTheCurrentValueOfATextFormField_textInput',
         'ElementAttributeTest.testGetAttributeDoesNotReturnAnObjectForSvgProperties',
+        'ElementAttributeTest.testShouldReturnNullForNonPresentBooleanAttributes',
         'JavascriptEnabledDriverTest.testShouldBeAbleToFindElementAfterJavascriptCausesANewPageToLoad',
         'JavascriptEnabledDriverTest.testShouldBeAbleToSwitchToFocusedElement',
         'JavascriptEnabledDriverTest.testShouldBeAbleToDetermineTheLocationOfAnElement',
@@ -292,11 +293,14 @@
         'JavascriptEnabledDriverTest.testShouldWaitForLoadsToCompleteAfterJavascriptCausesANewPageToLoad',
         'FormHandlingTest.testShouldSubmitAFormUsingTheEnterKey',
         'FormHandlingTest.testShouldSubmitAFormUsingTheNewlineLiteral',
+        'FormHandlingTest.testCanClickOnAnExternalSubmitButton',
+        'FormHandlingTest.testShouldBeAbleToUploadTheSameFileTwice',
         'BasicMouseInterfaceTest.testDoubleClickThenGet',
         'MiscTest.testShouldReturnTheSourceOfAPage',
         'MiscTest.testClickingShouldNotTrampleWOrHInGlobalScope',
         'MiscTest.testShouldReportTheCurrentUrlCorrectly',
         'MiscTest.testStimulatesStrangeOnloadInteractionInFirefox',
+        'TypingTest.testShouldBeAbleToUseArrowKeys',
     ]
 )
 _OS_NEGATIVE_FILTER['android:chrome_beta'] = (
diff --git a/chrome/test/data/android/popup_on_click.html b/chrome/test/data/android/popup_on_click.html
new file mode 100644
index 0000000..61c3ba6
--- /dev/null
+++ b/chrome/test/data/android/popup_on_click.html
@@ -0,0 +1,2 @@
+<p>This page opens new windows/tabs based on what is clicked.</p>
+<a id="link" href="popup_on_click.html" target="_blank">Click for new tab</a>
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
index 2481d41..4dba9dbd 100644
--- a/chrome/utility/BUILD.gn
+++ b/chrome/utility/BUILD.gn
@@ -116,7 +116,6 @@
       "//chrome/common/extensions/api",
       "//chrome/services/media_gallery_util:lib",
       "//chrome/services/removable_storage_writer:lib",
-      "//extensions/utility",
     ]
 
     public_deps += [ "//chrome/common/extensions/api" ]
diff --git a/chrome/utility/DEPS b/chrome/utility/DEPS
index 37bfc09..4f9afedc1 100644
--- a/chrome/utility/DEPS
+++ b/chrome/utility/DEPS
@@ -29,7 +29,6 @@
   "+content/public/utility",
   "+extensions/common",
   "+extensions/buildflags",
-  "+extensions/utility",
   "+media",
   "+services/network/public",
   "+services/network/url_request_context_builder_mojo.h",
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index aabd610..c3a84e6 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -51,7 +51,6 @@
 #include "chrome/services/removable_storage_writer/public/mojom/constants.mojom.h"
 #include "chrome/services/removable_storage_writer/removable_storage_writer_service.h"
 #include "chrome/utility/extensions/extensions_handler.h"
-#include "extensions/utility/utility_handler.h"
 #if defined(OS_WIN)
 #include "chrome/services/wifi_util_win/public/mojom/constants.mojom.h"
 #include "chrome/services/wifi_util_win/wifi_util_win_service.h"
@@ -118,10 +117,6 @@
 ChromeContentUtilityClient::~ChromeContentUtilityClient() = default;
 
 void ChromeContentUtilityClient::UtilityThreadStarted() {
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  extensions::utility_handler::UtilityThreadStarted();
-#endif
-
 #if defined(OS_WIN)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   utility_process_running_elevated_ = command_line->HasSwitch(
@@ -137,10 +132,6 @@
     return;
 
   auto registry = std::make_unique<service_manager::BinderRegistry>();
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  extensions::utility_handler::ExposeInterfacesToBrowser(
-      registry.get(), utility_process_running_elevated_);
-#endif
   // If our process runs with elevated privileges, only add elevated Mojo
   // interfaces to the interface registry.
   if (!utility_process_running_elevated_) {
diff --git a/chrome/utility/extensions/DEPS b/chrome/utility/extensions/DEPS
index a62f289..4959379 100644
--- a/chrome/utility/extensions/DEPS
+++ b/chrome/utility/extensions/DEPS
@@ -1,5 +1,5 @@
 include_rules = [
   "+extensions/buildflags",
   "+extensions/common",
-  "+extensions/utility",
+  "+extensions/features",
 ]
diff --git a/chromecast/browser/cast_display_configurator.cc b/chromecast/browser/cast_display_configurator.cc
index df84a61..b5dfca3 100644
--- a/chromecast/browser/cast_display_configurator.cc
+++ b/chromecast/browser/cast_display_configurator.cc
@@ -4,6 +4,8 @@
 
 #include "chromecast/browser/cast_display_configurator.h"
 
+#include <math.h>
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
@@ -66,8 +68,28 @@
 }
 
 float GetDeviceScaleFactor(gfx::Size display_resolution) {
-  // Device scale factor computed relative to 720p display
-  return display_resolution.height() / 720.0f;
+  // TODO(spang): Look into tightening up the allowed scale factors here
+  // rather than allowing all scales >= 1.f
+  int smaller_dimension =
+      std::min(display_resolution.width(), display_resolution.height());
+  float ratio = smaller_dimension / 720.f;
+  if (ratio < 1.f)
+    return 1.f;
+  return ratio;
+}
+
+gfx::Rect GetScreenBounds(const gfx::Size& size_in_pixels,
+                          display::Display::Rotation rotation) {
+  switch (rotation) {
+    case display::Display::ROTATE_90:
+    case display::Display::ROTATE_270:
+      return gfx::Rect(
+          gfx::Size(size_in_pixels.height(), size_in_pixels.width()));
+    case display::Display::ROTATE_0:
+    case display::Display::ROTATE_180:
+    default:
+      return gfx::Rect(size_in_pixels);
+  }
 }
 
 }  // namespace
@@ -181,7 +203,7 @@
     float device_scale_factor,
     display::Display::Rotation rotation) {
   cast_screen_->OnDisplayChanged(display_id, device_scale_factor, rotation,
-                                 bounds);
+                                 GetScreenBounds(bounds.size(), rotation));
   touch_device_manager_->OnDisplayConfigured(display_id, rotation, bounds);
 }
 
diff --git a/chromecast/browser/cast_touch_device_manager.cc b/chromecast/browser/cast_touch_device_manager.cc
index bc61fa3..a167c3a 100644
--- a/chromecast/browser/cast_touch_device_manager.cc
+++ b/chromecast/browser/cast_touch_device_manager.cc
@@ -27,20 +27,9 @@
   touch_device_transform.transform.Translate(native_bounds_in_pixel.x(),
                                              native_bounds_in_pixel.y());
 
-  float touchscreen_width = touchscreen_size.width();
-  float touchscreen_height = touchscreen_size.height();
-
-  // If the display orientation is rotated between portrait and landscape,
-  // the width and height of the touchscreen must be swapped as well.
-  if (rotation == display::Display::Rotation::ROTATE_90 ||
-      rotation == display::Display::Rotation::ROTATE_270) {
-    touchscreen_width = touchscreen_size.height();
-    touchscreen_height = touchscreen_size.width();
-  }
-
   touch_device_transform.transform.Scale(
-      native_bounds_in_pixel.width() / touchscreen_width,
-      native_bounds_in_pixel.height() / touchscreen_height);
+      native_bounds_in_pixel.width() / touchscreen_size.width(),
+      native_bounds_in_pixel.height() / touchscreen_size.height());
 
   return touch_device_transform;
 }
diff --git a/chromecast/graphics/cast_window_manager_aura.cc b/chromecast/graphics/cast_window_manager_aura.cc
index faf7a86..d24469d 100644
--- a/chromecast/graphics/cast_window_manager_aura.cc
+++ b/chromecast/graphics/cast_window_manager_aura.cc
@@ -46,6 +46,23 @@
   return rotation;
 }
 
+gfx::Rect GetPrimaryDisplayHostBounds() {
+  display::Display display(display::Screen::GetScreen()->GetPrimaryDisplay());
+  gfx::Point display_origin_in_pixel = display.bounds().origin();
+  gfx::Size display_size_in_pixel = display.GetSizeInPixel();
+  switch (display.rotation()) {
+    case display::Display::ROTATE_90:
+    case display::Display::ROTATE_270:
+      return gfx::Rect(display_origin_in_pixel,
+                       gfx::Size(display_size_in_pixel.height(),
+                                 display_size_in_pixel.width()));
+    case display::Display::ROTATE_0:
+    case display::Display::ROTATE_180:
+      // default:
+      return gfx::Rect(display_origin_in_pixel, display_size_in_pixel);
+  }
+}
+
 }  // namespace
 
 // An ui::EventTarget that ignores events.
@@ -224,20 +241,11 @@
 
   ui::InitializeInputMethodForTesting();
 
-  gfx::Size display_size =
-      display::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
-  display::Display::Rotation rotation =
-      display::Screen::GetScreen()->GetPrimaryDisplay().rotation();
-  if (rotation == display::Display::ROTATE_90 ||
-      rotation == display::Display::ROTATE_270) {
-    display_size = gfx::Size(display_size.height(), display_size.width());
-  }
+  gfx::Rect host_bounds = GetPrimaryDisplayHostBounds();
 
-  LOG(INFO) << "Starting window manager, screen size: " << display_size.width()
-            << "x" << display_size.height();
+  LOG(INFO) << "Starting window manager, bounds: " << host_bounds.ToString();
   CHECK(aura::Env::GetInstance());
-  window_tree_host_.reset(
-      new CastWindowTreeHost(enable_input_, gfx::Rect(display_size)));
+  window_tree_host_.reset(new CastWindowTreeHost(enable_input_, host_bounds));
   window_tree_host_->InitHost();
   window_tree_host_->window()->SetLayoutManager(new CastLayoutManager());
   window_tree_host_->SetRootTransform(GetPrimaryDisplayRotationTransform());
diff --git a/chromeos/dbus/fake_smb_provider_client.cc b/chromeos/dbus/fake_smb_provider_client.cc
index a1657ab..b7e8f09 100644
--- a/chromeos/dbus/fake_smb_provider_client.cc
+++ b/chromeos/dbus/fake_smb_provider_client.cc
@@ -131,4 +131,12 @@
       FROM_HERE, base::BindOnce(std::move(callback), smbprovider::ERROR_OK));
 }
 
+void FakeSmbProviderClient::GetDeleteList(int32_t mount_id,
+                                          const base::FilePath& entry_path,
+                                          GetDeleteListCallback callback) {
+  smbprovider::DeleteListProto delete_list;
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), smbprovider::ERROR_OK, delete_list));
+}
 }  // namespace chromeos
diff --git a/chromeos/dbus/fake_smb_provider_client.h b/chromeos/dbus/fake_smb_provider_client.h
index 6692b36..b64e5a7 100644
--- a/chromeos/dbus/fake_smb_provider_client.h
+++ b/chromeos/dbus/fake_smb_provider_client.h
@@ -76,6 +76,10 @@
                  const base::FilePath& target_path,
                  StatusCallback callback) override;
 
+  void GetDeleteList(int32_t mount_id,
+                     const base::FilePath& entry_path,
+                     GetDeleteListCallback callback) override;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(FakeSmbProviderClient);
 };
diff --git a/chromeos/dbus/smb_provider_client.cc b/chromeos/dbus/smb_provider_client.cc
index c53f945..e7eb9f2 100644
--- a/chromeos/dbus/smb_provider_client.cc
+++ b/chromeos/dbus/smb_provider_client.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/files/file_util.h"
 #include "base/memory/weak_ptr.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
@@ -45,6 +46,16 @@
   return smbprovider::ERROR_OK;
 }
 
+bool ParseDeleteList(const base::ScopedFD& fd,
+                     int32_t bytes_written,
+                     smbprovider::DeleteListProto* delete_list) {
+  DCHECK(delete_list);
+  std::vector<uint8_t> buffer(bytes_written);
+  return base::ReadFromFD(fd.get(), reinterpret_cast<char*>(buffer.data()),
+                          buffer.size()) &&
+         delete_list->ParseFromArray(buffer.data(), buffer.size());
+}
+
 class SmbProviderClientImpl : public SmbProviderClient {
  public:
   SmbProviderClientImpl() : weak_ptr_factory_(this) {}
@@ -208,6 +219,16 @@
     CallDefaultMethod(smbprovider::kCopyEntryMethod, options, &callback);
   }
 
+  void GetDeleteList(int32_t mount_id,
+                     const base::FilePath& entry_path,
+                     GetDeleteListCallback callback) override {
+    smbprovider::GetDeleteListOptionsProto options;
+    options.set_mount_id(mount_id);
+    options.set_entry_path(entry_path.value());
+    CallMethod(smbprovider::kGetDeleteListMethod, options,
+               &SmbProviderClientImpl::HandleGetDeleteListCallback, &callback);
+  }
+
  protected:
   // DBusClient override.
   void Init(dbus::Bus* bus) override {
@@ -335,6 +356,39 @@
     std::move(callback).Run(smbprovider::ERROR_OK, fd);
   }
 
+  // Handles D-Bus callback for GetDeleteList.
+  void HandleGetDeleteListCallback(GetDeleteListCallback callback,
+                                   dbus::Response* response) {
+    base::ScopedFD fd;
+    smbprovider::DeleteListProto delete_list;
+    if (!response) {
+      LOG(ERROR) << "GetDeleteList: failed to call smbprovider";
+      std::move(callback).Run(smbprovider::ERROR_DBUS_PARSE_FAILED,
+                              delete_list);
+      return;
+    }
+
+    dbus::MessageReader reader(response);
+    smbprovider::ErrorType error = GetErrorFromReader(&reader);
+    if (error != smbprovider::ERROR_OK) {
+      std::move(callback).Run(error, delete_list);
+      return;
+    }
+
+    int32_t bytes_written;
+    bool success = reader.PopFileDescriptor(&fd) &&
+                   reader.PopInt32(&bytes_written) &&
+                   ParseDeleteList(fd, bytes_written, &delete_list);
+    if (!success) {
+      LOG(ERROR) << "GetDeleteList: parse failure.";
+      std::move(callback).Run(smbprovider::ERROR_DBUS_PARSE_FAILED,
+                              delete_list);
+      return;
+    }
+
+    std::move(callback).Run(smbprovider::ERROR_OK, delete_list);
+  }
+
   // Default callback handler for D-Bus calls.
   void HandleDefaultCallback(const std::string& method_name,
                              StatusCallback callback,
diff --git a/chromeos/dbus/smb_provider_client.h b/chromeos/dbus/smb_provider_client.h
index a67d564..88a7846 100644
--- a/chromeos/dbus/smb_provider_client.h
+++ b/chromeos/dbus/smb_provider_client.h
@@ -34,6 +34,9 @@
   using StatusCallback = base::OnceCallback<void(smbprovider::ErrorType error)>;
   using ReadFileCallback = base::OnceCallback<void(smbprovider::ErrorType error,
                                                    const base::ScopedFD& fd)>;
+  using GetDeleteListCallback =
+      base::OnceCallback<void(smbprovider::ErrorType error,
+                              const smbprovider::DeleteListProto& delete_list)>;
 
   ~SmbProviderClient() override;
 
@@ -144,6 +147,13 @@
                          const base::FilePath& target_path,
                          StatusCallback callback) = 0;
 
+  // Calls GetDeleteList. Using the corresponding |mount_id|, this generates an
+  // ordered list of individual entries that must be deleted in order to delete
+  // |entry_path|. This operations does not modify the filesystem.
+  virtual void GetDeleteList(int32_t mount_id,
+                             const base::FilePath& entry_path,
+                             GetDeleteListCallback callback) = 0;
+
  protected:
   // Create() should be used instead.
   SmbProviderClient();
diff --git a/components/component_updater/component_installer_unittest.cc b/components/component_updater/component_installer_unittest.cc
index 4c52a80e..959ec1f 100644
--- a/components/component_updater/component_installer_unittest.cc
+++ b/components/component_updater/component_installer_unittest.cc
@@ -114,10 +114,10 @@
   ~MockUpdateClient() override {}
 };
 
-class FakeInstallerPolicy : public ComponentInstallerPolicy {
+class MockInstallerPolicy : public ComponentInstallerPolicy {
  public:
-  FakeInstallerPolicy() {}
-  ~FakeInstallerPolicy() override {}
+  MockInstallerPolicy() {}
+  ~MockInstallerPolicy() override {}
 
   bool VerifyInstallation(const base::DictionaryValue& manifest,
                           const base::FilePath& dir) const override {
@@ -274,7 +274,7 @@
   EXPECT_CALL(update_client(), Stop()).Times(1);
 
   auto installer = base::MakeRefCounted<ComponentInstaller>(
-      std::make_unique<FakeInstallerPolicy>());
+      std::make_unique<MockInstallerPolicy>());
   installer->Register(component_updater(), base::OnceClosure());
 
   RunThreads();
@@ -301,7 +301,7 @@
 // Tests that the unpack path is removed when the install succeeded.
 TEST_F(ComponentInstallerTest, UnpackPathInstallSuccess) {
   auto installer = base::MakeRefCounted<ComponentInstaller>(
-      std::make_unique<FakeInstallerPolicy>());
+      std::make_unique<MockInstallerPolicy>());
 
   Unpack(test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
 
@@ -329,7 +329,7 @@
 // Tests that the unpack path is removed when the install failed.
 TEST_F(ComponentInstallerTest, UnpackPathInstallError) {
   auto installer = base::MakeRefCounted<ComponentInstaller>(
-      std::make_unique<FakeInstallerPolicy>());
+      std::make_unique<MockInstallerPolicy>());
 
   Unpack(test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));
 
diff --git a/components/component_updater/timer_unittest.cc b/components/component_updater/timer_unittest.cc
index b0523e9..9d5c63ea 100644
--- a/components/component_updater/timer_unittest.cc
+++ b/components/component_updater/timer_unittest.cc
@@ -5,6 +5,7 @@
 #include <string>
 #include <utility>
 
+#include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/time/time.h"
@@ -27,9 +28,9 @@
 };
 
 TEST_F(ComponentUpdaterTimerTest, Start) {
-  class TimerClientFake {
+  class TimerClientMock {
    public:
-    TimerClientFake(int max_count, base::OnceClosure quit_closure)
+    TimerClientMock(int max_count, base::OnceClosure quit_closure)
         : max_count_(max_count),
           quit_closure_(std::move(quit_closure)),
           count_(0) {}
@@ -50,13 +51,14 @@
   };
 
   base::RunLoop run_loop;
-  TimerClientFake timer_client_fake(3, run_loop.QuitClosure());
+  TimerClientMock timer_client_fake(3, run_loop.QuitClosure());
   EXPECT_EQ(0, timer_client_fake.count());
 
   Timer timer;
   const base::TimeDelta delay(base::TimeDelta::FromMilliseconds(1));
-  timer.Start(delay, delay, base::Bind(&TimerClientFake::OnTimerEvent,
-                                       base::Unretained(&timer_client_fake)));
+  timer.Start(delay, delay,
+              base::BindRepeating(&TimerClientMock::OnTimerEvent,
+                                  base::Unretained(&timer_client_fake)));
   run_loop.Run();
   timer.Stop();
 
diff --git a/components/download/content/factory/download_service_factory.cc b/components/download/content/factory/download_service_factory.cc
index 26686cb..a2f01ef2 100644
--- a/components/download/content/factory/download_service_factory.cc
+++ b/components/download/content/factory/download_service_factory.cc
@@ -23,7 +23,7 @@
 #include "components/download/internal/background_service/proto/entry.pb.h"
 #include "components/download/internal/background_service/scheduler/scheduler_impl.h"
 #include "components/leveldb_proto/proto_database_impl.h"
-#include "net/url_request/url_request_context_getter.h"
+#include "content/public/browser/storage_partition.h"
 
 #if defined(OS_ANDROID)
 #include "components/download/internal/background_service/android/battery_status_listener_android.h"
@@ -111,12 +111,16 @@
     content::BrowserContext* browser_context,
     std::unique_ptr<DownloadClientMap> clients,
     const base::FilePath& storage_dir,
-    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
     BlobTaskProxy::BlobContextGetter blob_context_getter,
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
   auto config = Configuration::CreateFromFinch();
+  auto* url_loader_factory =
+      content::BrowserContext::GetDefaultStoragePartition(browser_context)
+          ->GetURLLoaderFactoryForBrowserProcess()
+          .get();
+  DCHECK(url_loader_factory);
   auto download_factory = std::make_unique<InMemoryDownloadFactory>(
-      request_context_getter, blob_context_getter, io_task_runner);
+      url_loader_factory, blob_context_getter, io_task_runner);
   auto driver =
       std::make_unique<InMemoryDownloadDriver>(std::move(download_factory));
   auto store = std::make_unique<NoopStore>();
diff --git a/components/download/content/factory/download_service_factory.h b/components/download/content/factory/download_service_factory.h
index 9dbb7da..a1142c1 100644
--- a/components/download/content/factory/download_service_factory.h
+++ b/components/download/content/factory/download_service_factory.h
@@ -17,10 +17,6 @@
 class BrowserContext;
 }  // namespace content
 
-namespace net {
-class URLRequestContextGetter;
-}  // namespace net
-
 namespace download {
 
 class DownloadService;
@@ -49,7 +45,6 @@
     content::BrowserContext* browser_context,
     std::unique_ptr<DownloadClientMap> clients,
     const base::FilePath& storage_dir,
-    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
     BlobTaskProxy::BlobContextGetter blob_context_getter,
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
 
diff --git a/components/download/internal/background_service/BUILD.gn b/components/download/internal/background_service/BUILD.gn
index 3f0a725..f695f87f 100644
--- a/components/download/internal/background_service/BUILD.gn
+++ b/components/download/internal/background_service/BUILD.gn
@@ -87,6 +87,7 @@
     "//components/download/public/background_service:public",
     "//components/leveldb_proto",
     "//net",
+    "//services/network/public/cpp",
     "//storage/browser",
   ]
 
@@ -152,7 +153,7 @@
     "//components/download/internal/background_service/test:test_support",
     "//components/download/public/background_service/test:test_support",
     "//components/leveldb_proto:test_support",
-    "//net:test_support",
+    "//services/network:test_support",
     "//storage/browser",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/download/internal/background_service/DEPS b/components/download/internal/background_service/DEPS
index ed5bc8c..9500a4e 100644
--- a/components/download/internal/background_service/DEPS
+++ b/components/download/internal/background_service/DEPS
@@ -5,6 +5,8 @@
   "+base",
   "+jni",
   "+net",
+  "+services/network/public",
+  "+services/network/test",
   "+storage/browser",
   "+storage/common",
 ]
diff --git a/components/download/internal/background_service/in_memory_download.cc b/components/download/internal/background_service/in_memory_download.cc
index 091f0e1..18b72b7 100644
--- a/components/download/internal/background_service/in_memory_download.cc
+++ b/components/download/internal/background_service/in_memory_download.cc
@@ -8,102 +8,14 @@
 #include <string>
 
 #include "base/bind.h"
-#include "base/strings/string_util.h"
 #include "components/download/internal/background_service/blob_task_proxy.h"
-#include "net/base/completion_callback.h"
-#include "net/base/io_buffer.h"
-#include "net/base/net_errors.h"
-#include "net/http/http_status_code.h"
+#include "net/base/load_flags.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
-#include "net/url_request/url_fetcher.h"
 #include "storage/browser/blob/blob_data_handle.h"
 #include "storage/browser/blob/blob_storage_context.h"
 
 namespace download {
 
-namespace {
-
-// Converts a string to HTTP method used by URLFetcher.
-net::URLFetcher::RequestType ToRequestType(const std::string& method) {
-  // Only supports GET and POST.
-  if (base::EqualsCaseInsensitiveASCII(method, "GET"))
-    return net::URLFetcher::RequestType::GET;
-  if (base::EqualsCaseInsensitiveASCII(method, "POST"))
-    return net::URLFetcher::RequestType::POST;
-
-  NOTREACHED();
-  return net::URLFetcher::RequestType::GET;
-}
-
-}  // namespace
-
-InMemoryDownloadImpl::ResponseWriter::ResponseWriter(
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
-    : paused_on_io_(false), io_task_runner_(io_task_runner) {}
-
-InMemoryDownloadImpl::ResponseWriter::~ResponseWriter() = default;
-
-void InMemoryDownloadImpl::ResponseWriter::Pause() {
-  io_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ResponseWriter::PauseOnIO, base::Unretained(this)));
-}
-
-void InMemoryDownloadImpl::ResponseWriter::PauseOnIO() {
-  io_task_runner_->BelongsToCurrentThread();
-  paused_on_io_ = true;
-}
-
-void InMemoryDownloadImpl::ResponseWriter::Resume() {
-  io_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ResponseWriter::ResumeOnIO, base::Unretained(this)));
-}
-
-void InMemoryDownloadImpl::ResponseWriter::ResumeOnIO() {
-  io_task_runner_->BelongsToCurrentThread();
-  paused_on_io_ = false;
-
-  // Continue read from network layer. Since we didn't write on pause, report
-  // 0 byte to network layer.
-  if (!write_callback_.is_null()) {
-    base::ResetAndReturn(&write_callback_).Run(0u);
-  }
-}
-
-std::unique_ptr<std::string> InMemoryDownloadImpl::ResponseWriter::TakeData() {
-  return std::move(data_);
-}
-
-int InMemoryDownloadImpl::ResponseWriter::Initialize(
-    const net::CompletionCallback& callback) {
-  data_ = std::make_unique<std::string>();
-  return net::OK;
-}
-
-int InMemoryDownloadImpl::ResponseWriter::Write(
-    net::IOBuffer* buffer,
-    int num_bytes,
-    const net::CompletionCallback& callback) {
-  io_task_runner_->BelongsToCurrentThread();
-
-  if (paused_on_io_) {
-    write_callback_ = callback;
-    return net::ERR_IO_PENDING;
-  }
-
-  DCHECK(data_);
-  data_->append(buffer->data(), num_bytes);
-  return num_bytes;
-}
-
-int InMemoryDownloadImpl::ResponseWriter::Finish(
-    int net_error,
-    const net::CompletionCallback& callback) {
-  io_task_runner_->BelongsToCurrentThread();
-  return net::OK;
-}
-
 InMemoryDownload::InMemoryDownload(const std::string& guid)
     : guid_(guid), state_(State::INITIAL), bytes_downloaded_(0u) {}
 
@@ -114,13 +26,13 @@
     const RequestParams& request_params,
     const net::NetworkTrafficAnnotationTag& traffic_annotation,
     Delegate* delegate,
-    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+    network::mojom::URLLoaderFactory* url_loader_factory,
     BlobTaskProxy::BlobContextGetter blob_context_getter,
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
     : InMemoryDownload(guid),
       request_params_(request_params),
       traffic_annotation_(traffic_annotation),
-      request_context_getter_(request_context_getter),
+      url_loader_factory_(url_loader_factory),
       blob_task_proxy_(
           BlobTaskProxy::Create(blob_context_getter, io_task_runner)),
       io_task_runner_(io_task_runner),
@@ -142,30 +54,11 @@
 }
 
 void InMemoryDownloadImpl::Pause() {
-  if (paused_)
-    return;
-  paused_ = true;
-
-  switch (state_) {
-    case State::INITIAL:
-      // Do nothing.
-      return;
-    case State::IN_PROGRESS:
-      // Do nothing if network operation is done.
-      DCHECK(response_writer_);
-      response_writer_->Pause();
-      return;
-    case State::FAILED:
-      return;
-    case State::COMPLETE:
-      // Do nothing.
-      return;
-  }
+  if (state_ == State::IN_PROGRESS)
+    paused_ = true;
 }
 
 void InMemoryDownloadImpl::Resume() {
-  if (!paused_)
-    return;
   paused_ = false;
 
   switch (state_) {
@@ -173,12 +66,13 @@
       NOTREACHED();
       return;
     case State::IN_PROGRESS:
-      // Do nothing if network operation is done.
-      DCHECK(response_writer_);
-      response_writer_->Resume();
+      // Let the network pipe continue to read data.
+      if (resume_callback_)
+        std::move(resume_callback_).Run();
       return;
     case State::FAILED:
-      // Restart the network layer. No ongoing blob task should exist.
+      // Restart the download.
+      Reset();
       SendRequest();
       state_ = State::IN_PROGRESS;
       return;
@@ -199,64 +93,48 @@
   return bytes_downloaded_;
 }
 
-void InMemoryDownloadImpl::OnURLFetchDownloadProgress(
-    const net::URLFetcher* source,
-    int64_t current,
-    int64_t total,
-    int64_t current_network_bytes) {
-  bytes_downloaded_ = current;
+void InMemoryDownloadImpl::OnDataReceived(base::StringPiece string_piece,
+                                          base::OnceClosure resume) {
+  size_t size = string_piece.as_string().size();
+  data_.append(string_piece.as_string().data(), size);
+  bytes_downloaded_ += size;
+
+  if (paused_) {
+    // Read data later and cache the resumption callback when paused.
+    resume_callback_ = std::move(resume);
+    return;
+  }
+
+  // Continue to read data.
+  std::move(resume).Run();
 
   // TODO(xingliu): Throttle the update frequency. See https://crbug.com/809674.
   if (delegate_)
     delegate_->OnDownloadProgress(this);
 }
 
-void InMemoryDownloadImpl::OnURLFetchComplete(const net::URLFetcher* source) {
-  DCHECK(source);
-  response_headers_ = source->GetResponseHeaders();
-
-  switch (source->GetStatus().status()) {
-    case net::URLRequestStatus::Status::SUCCESS:
-      if (HandleResponseCode(source->GetResponseCode())) {
-        SaveAsBlob();
-        return;
-      }
-
-      state_ = State::FAILED;
-      NotifyDelegateDownloadComplete();
-      return;
-    case net::URLRequestStatus::Status::IO_PENDING:
-      return;
-    case net::URLRequestStatus::Status::CANCELED:
-    case net::URLRequestStatus::Status::FAILED:
-      state_ = State::FAILED;
-      NotifyDelegateDownloadComplete();
-      return;
+void InMemoryDownloadImpl::OnComplete(bool success) {
+  if (success) {
+    SaveAsBlob();
+    return;
   }
+
+  state_ = State::FAILED;
+
+  // Release download data.
+  data_.clear();
+  NotifyDelegateDownloadComplete();
 }
 
-bool InMemoryDownloadImpl::HandleResponseCode(int response_code) {
-  switch (response_code) {
-    case -1:  // Non-HTTP request.
-    case net::HTTP_OK:
-    case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
-    case net::HTTP_PARTIAL_CONTENT:
-    case net::HTTP_CREATED:
-    case net::HTTP_ACCEPTED:
-    case net::HTTP_NO_CONTENT:
-    case net::HTTP_RESET_CONTENT:
-      return true;
-    // All other codes are considered as failed.
-    default:
-      return false;
-  }
+void InMemoryDownloadImpl::OnRetry(base::OnceClosure start_retry) {
+  Reset();
+  std::move(start_retry).Run();
 }
 
 void InMemoryDownloadImpl::SaveAsBlob() {
-  DCHECK(url_fetcher_);
-  std::unique_ptr<std::string> data = response_writer_->TakeData();
   auto callback = base::BindOnce(&InMemoryDownloadImpl::OnSaveBlobDone,
                                  weak_ptr_factory_.GetWeakPtr());
+  auto data = std::make_unique<std::string>(std::move(data_));
   blob_task_proxy_->SaveAsBlob(std::move(data), std::move(callback));
 }
 
@@ -270,12 +148,13 @@
 
   // TODO(xingliu): Add metric for blob status code. If failed, consider remove
   // |blob_data_handle_|. See https://crbug.com/809674.
+  DCHECK(data_.empty())
+      << "Download data should be contained in |blob_data_handle_|.";
   blob_data_handle_ = std::move(blob_handle);
   completion_time_ = base::Time::Now();
 
   // Resets network backend.
-  response_writer_ = nullptr;
-  url_fetcher_.reset();
+  loader_.reset();
 
   // Not considering |paused_| here, if pause after starting a blob operation,
   // just let it finish.
@@ -292,21 +171,25 @@
 }
 
 void InMemoryDownloadImpl::SendRequest() {
-  url_fetcher_ = net::URLFetcher::Create(request_params_.url,
-                                         ToRequestType(request_params_.method),
-                                         this, traffic_annotation_);
-  url_fetcher_->SetRequestContext(request_context_getter_.get());
-  url_fetcher_->SetExtraRequestHeaders(
-      request_params_.request_headers.ToString());
-  response_writer_ =
-      new ResponseWriter(request_context_getter_->GetNetworkTaskRunner());
-  url_fetcher_->SaveResponseWithWriter(
-      std::unique_ptr<ResponseWriter>(response_writer_));
-  url_fetcher_->Start();
+  auto request = std::make_unique<network::ResourceRequest>();
+  request->url = request_params_.url;
+  request->method = request_params_.method;
+  request->headers = request_params_.request_headers;
+  request->load_flags = net::LOAD_DISABLE_CACHE;
 
-  // Pause on network thread if needed.
-  if (paused_)
-    response_writer_->Pause();
+  loader_ =
+      network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
+
+  // TODO(xingliu): Use SimpleURLLoader's retry when it won't hit CHECK in
+  // SharedURLLoaderFactory.
+  loader_->DownloadAsStream(url_loader_factory_, this);
+}
+
+void InMemoryDownloadImpl::Reset() {
+  data_.clear();
+  bytes_downloaded_ = 0u;
+  completion_notified_ = false;
+  resume_callback_.Reset();
 }
 
 }  // namespace download
diff --git a/components/download/internal/background_service/in_memory_download.h b/components/download/internal/background_service/in_memory_download.h
index 8f97c9e..9b43751 100644
--- a/components/download/internal/background_service/in_memory_download.h
+++ b/components/download/internal/background_service/in_memory_download.h
@@ -14,13 +14,11 @@
 #include "base/single_thread_task_runner.h"
 #include "components/download/internal/background_service/blob_task_proxy.h"
 #include "components/download/public/background_service/download_params.h"
-#include "net/base/completion_callback.h"
-#include "net/url_request/url_fetcher_delegate.h"
-#include "net/url_request/url_fetcher_response_writer.h"
-#include "net/url_request/url_request_context_getter.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 
 namespace net {
-class URLFetcher;
 struct NetworkTrafficAnnotationTag;
 }  // namespace net
 
@@ -78,9 +76,7 @@
     FAILED,
 
     // Download is completed, and data is successfully saved as a blob.
-    // 1. We guarantee the states of network responses.
-    // 2. Do not guarantee the state of blob data. The consumer of blob
-    // should validate its state when using it on IO thread.
+    // Guarantee the blob is fully constructed.
     COMPLETE,
   };
 
@@ -129,14 +125,15 @@
   DISALLOW_COPY_AND_ASSIGN(InMemoryDownload);
 };
 
-// Implementation of InMemoryDownload and uses URLFetcher as network backend.
+// Implementation of InMemoryDownload and uses SimpleURLLoader as network
+// backend.
 // Threading contract:
 // 1. This object lives on the main thread.
 // 2. Reading/writing IO buffer from network is done on another thread,
 // based on |request_context_getter_|. When complete, main thread is notified.
 // 3. After network IO is done, Blob related work is done on IO thread with
 // |blob_task_proxy_|, then notify the result to main thread.
-class InMemoryDownloadImpl : public net::URLFetcherDelegate,
+class InMemoryDownloadImpl : public network::SimpleURLLoaderStreamConsumer,
                              public InMemoryDownload {
  public:
   InMemoryDownloadImpl(
@@ -144,51 +141,13 @@
       const RequestParams& request_params,
       const net::NetworkTrafficAnnotationTag& traffic_annotation,
       Delegate* delegate,
-      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+      network::mojom::URLLoaderFactory* url_loader_factory,
       BlobTaskProxy::BlobContextGetter blob_context_getter,
       scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+
   ~InMemoryDownloadImpl() override;
 
  private:
-  // Response writer that supports pause and resume operations.
-  class ResponseWriter : public net::URLFetcherResponseWriter {
-   public:
-    ResponseWriter(scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
-    ~ResponseWriter() override;
-
-    // Pause writing data from pipe into |data_|.
-    void Pause();
-
-    // Resume writing data from the pipe into |data_|.
-    void Resume();
-
-    // Take the data, must be called after the network layer completes its job.
-    std::unique_ptr<std::string> TakeData();
-
-   private:
-    // net::URLFetcherResponseWriter implementation.
-    int Initialize(const net::CompletionCallback& callback) override;
-    int Write(net::IOBuffer* buffer,
-              int num_bytes,
-              const net::CompletionCallback& callback) override;
-    int Finish(int net_error, const net::CompletionCallback& callback) override;
-
-    void PauseOnIO();
-    void ResumeOnIO();
-
-    // Download data, should be moved to avoid extra copy.
-    std::unique_ptr<std::string> data_;
-
-    bool paused_on_io_;
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
-
-    // When paused, cached callback to trigger the next read. Must be set and
-    // called on fetcher's IO thread.
-    net::CompletionCallback write_callback_;
-
-    DISALLOW_COPY_AND_ASSIGN(ResponseWriter);
-  };
-
   // InMemoryDownload implementation.
   void Start() override;
   void Pause() override;
@@ -197,16 +156,11 @@
   std::unique_ptr<storage::BlobDataHandle> ResultAsBlob() override;
   size_t EstimateMemoryUsage() const override;
 
-  // net::URLFetcherDelegate implementation.
-  void OnURLFetchDownloadProgress(const net::URLFetcher* source,
-                                  int64_t current,
-                                  int64_t total,
-                                  int64_t current_network_bytes) override;
-  void OnURLFetchComplete(const net::URLFetcher* source) override;
-
-  // Handles response code and change the state accordingly.
-  // Returns if the response code is considered as successful code.
-  bool HandleResponseCode(int response_code);
+  // network::SimpleURLLoaderStreamConsumer implementation.
+  void OnDataReceived(base::StringPiece string_piece,
+                      base::OnceClosure resume) override;
+  void OnComplete(bool success) override;
+  void OnRetry(base::OnceClosure start_retry) override;
 
   // Saves the download data into blob storage.
   void SaveAsBlob();
@@ -218,26 +172,23 @@
   // call.
   void NotifyDelegateDownloadComplete();
 
-  // Sends the network request.
+  // Sends a new network request.
   void SendRequest();
 
+  // Resets local states.
+  void Reset();
+
   // Request parameters of the download.
   const RequestParams request_params_;
 
   // Traffic annotation of the request.
   const net::NetworkTrafficAnnotationTag traffic_annotation_;
 
-  // Used to send requests to servers. Also contains the download data in its
-  // string buffer. We should avoid extra copy on the data and release the
-  // memory when needed.
-  std::unique_ptr<net::URLFetcher> url_fetcher_;
+  // Used to send requests to servers.
+  std::unique_ptr<network::SimpleURLLoader> loader_;
 
-  // Owned by |url_fetcher_|. Lives on fetcher's delegate thread, perform
-  // network IO on fetcher's IO thread.
-  ResponseWriter* response_writer_;
-
-  // Request context getter used by |url_fetcher_|.
-  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+  // Used to handle network response.
+  network::mojom::URLLoaderFactory* url_loader_factory_;
 
   // Worker that does blob related task on IO thread.
   std::unique_ptr<BlobTaskProxy> blob_task_proxy_;
@@ -253,6 +204,12 @@
 
   bool paused_;
 
+  // Data downloaded from network, should be moved to avoid extra copy.
+  std::string data_;
+
+  // Cached callback to let network backend continue to pull data.
+  base::OnceClosure resume_callback_;
+
   // Ensures Delegate::OnDownloadComplete is only called once.
   bool completion_notified_;
 
diff --git a/components/download/internal/background_service/in_memory_download_driver.cc b/components/download/internal/background_service/in_memory_download_driver.cc
index 186fda2..7f51c7c 100644
--- a/components/download/internal/background_service/in_memory_download_driver.cc
+++ b/components/download/internal/background_service/in_memory_download_driver.cc
@@ -47,10 +47,10 @@
 }  // namespace
 
 InMemoryDownloadFactory::InMemoryDownloadFactory(
-    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+    network::mojom::URLLoaderFactory* url_loader_factory,
     BlobTaskProxy::BlobContextGetter blob_context_getter,
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
-    : request_context_getter_(request_context_getter),
+    : url_loader_factory_(url_loader_factory),
       blob_context_getter_(blob_context_getter),
       io_task_runner_(io_task_runner) {}
 
@@ -61,9 +61,10 @@
     const RequestParams& request_params,
     const net::NetworkTrafficAnnotationTag& traffic_annotation,
     InMemoryDownload::Delegate* delegate) {
+  DCHECK(url_loader_factory_);
   return std::make_unique<InMemoryDownloadImpl>(
-      guid, request_params, traffic_annotation, delegate,
-      request_context_getter_, blob_context_getter_, io_task_runner_);
+      guid, request_params, traffic_annotation, delegate, url_loader_factory_,
+      blob_context_getter_, io_task_runner_);
 }
 
 InMemoryDownloadDriver::InMemoryDownloadDriver(
diff --git a/components/download/internal/background_service/in_memory_download_driver.h b/components/download/internal/background_service/in_memory_download_driver.h
index 019f7577..881a0b9 100644
--- a/components/download/internal/background_service/in_memory_download_driver.h
+++ b/components/download/internal/background_service/in_memory_download_driver.h
@@ -12,6 +12,7 @@
 
 #include "base/macros.h"
 #include "components/download/internal/background_service/in_memory_download.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 
 namespace download {
 
@@ -21,7 +22,7 @@
 class InMemoryDownloadFactory : public InMemoryDownload::Factory {
  public:
   InMemoryDownloadFactory(
-      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+      network::mojom::URLLoaderFactory* url_loader_factory,
       BlobTaskProxy::BlobContextGetter blob_context_getter,
       scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
   ~InMemoryDownloadFactory() override;
@@ -34,7 +35,8 @@
       const net::NetworkTrafficAnnotationTag& traffic_annotation,
       InMemoryDownload::Delegate* delegate) override;
 
-  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+  network::mojom::URLLoaderFactory* url_loader_factory_;
+
   BlobTaskProxy::BlobContextGetter blob_context_getter_;
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
 
diff --git a/components/download/internal/background_service/in_memory_download_unittest.cc b/components/download/internal/background_service/in_memory_download_unittest.cc
index 145f697..397bca1 100644
--- a/components/download/internal/background_service/in_memory_download_unittest.cc
+++ b/components/download/internal/background_service/in_memory_download_unittest.cc
@@ -4,16 +4,14 @@
 
 #include "components/download/internal/background_service/in_memory_download.h"
 
-#include "base/files/file_util.h"
 #include "base/guid.h"
-#include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/base/io_buffer.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "net/url_request/url_request_test_util.h"
+#include "services/network/test/test_url_loader_factory.h"
 #include "storage/browser/blob/blob_reader.h"
 #include "storage/browser/blob/blob_storage_context.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -22,18 +20,9 @@
 namespace download {
 namespace {
 
-// Posts a dummy task on |task_runner| and wait for its callback, to drain all
-// previous tasks on |task_runner|.
-void DrainPreviousTasks(
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-  base::RunLoop run_loop;
-
-  auto dummy_task = []() {};
-  task_runner->PostTaskAndReply(FROM_HERE, base::BindRepeating(dummy_task),
-                                run_loop.QuitClosure());
-
-  run_loop.Run();
-}
+const char kTestDownloadData[] =
+    "In earlier tellings, the dog had a better reputation than the cat, "
+    "however the president veto it.";
 
 // Dummy callback used for IO_PENDING state in blob operations, this is not
 // called when the blob operation is done, but called when chained with other
@@ -78,14 +67,10 @@
   ~InMemoryDownloadTest() override = default;
 
   void SetUp() override {
-    test_server_.ServeFilesFromDirectory(GetTestDataDirectory());
-    ASSERT_TRUE(test_server_.Start());
-
     io_thread_.reset(new base::Thread("Network and Blob IO thread"));
     base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
     io_thread_->StartWithOptions(options);
-    request_context_getter_ =
-        new net::TestURLRequestContextGetter(io_thread_->task_runner());
+
     base::RunLoop loop;
     io_thread_->task_runner()->PostTask(
         FROM_HERE, base::BindLambdaForTesting([&]() {
@@ -103,17 +88,11 @@
   }
 
  protected:
-  base::FilePath GetTestDataDirectory() {
-    base::FilePath test_data_dir;
-    EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
-    return test_data_dir.AppendASCII("components/test/data/download");
-  }
-
   // Helper method to create a download with request_params.
   void CreateDownload(const RequestParams& request_params) {
     download_ = std::make_unique<InMemoryDownloadImpl>(
         base::GenerateGUID(), request_params, TRAFFIC_ANNOTATION_FOR_TESTS,
-        delegate(), request_context_getter_,
+        delegate(), &url_loader_factory_,
         base::BindRepeating(&BlobStorageContextGetter,
                             blob_storage_context_.get()),
         io_thread_->task_runner());
@@ -121,9 +100,8 @@
 
   InMemoryDownload* download() { return download_.get(); }
   MockDelegate* delegate() { return &mock_delegate_; }
-  net::EmbeddedTestServer* test_server() { return &test_server_; }
-  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter() {
-    return request_context_getter_;
+  network::TestURLLoaderFactory* url_loader_factory() {
+    return &url_loader_factory_;
   }
 
   // Verifies if data read from |blob| is identical as |expected|.
@@ -174,56 +152,27 @@
   std::unique_ptr<InMemoryDownloadImpl> download_;
   MockDelegate mock_delegate_;
 
-  // Used by URLFetcher network backend.
-  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+  // Used by SimpleURLLoader network backend.
+  network::TestURLLoaderFactory url_loader_factory_;
 
   // Memory backed blob storage that can never page to disk.
   std::unique_ptr<storage::BlobStorageContext> blob_storage_context_;
 
-  net::EmbeddedTestServer test_server_;
-
   DISALLOW_COPY_AND_ASSIGN(InMemoryDownloadTest);
 };
 
 TEST_F(InMemoryDownloadTest, DownloadTest) {
   RequestParams request_params;
-  request_params.url = test_server()->GetURL("/text_data.json");
   CreateDownload(request_params);
+  url_loader_factory()->AddResponse(request_params.url.spec(),
+                                    kTestDownloadData);
+  // TODO(xingliu): More tests on pause/resume.
   download()->Start();
   delegate()->WaitForCompletion();
 
   EXPECT_EQ(InMemoryDownload::State::COMPLETE, download()->state());
   auto blob = download()->ResultAsBlob();
-
-  std::string expected;
-  EXPECT_TRUE(ReadFileToString(
-      GetTestDataDirectory().AppendASCII("text_data.json"), &expected));
-  VerifyBlobData(expected, blob.get());
-}
-
-TEST_F(InMemoryDownloadTest, PauseResume) {
-  RequestParams request_params;
-  request_params.url = test_server()->GetURL("/text_data.json");
-  CreateDownload(request_params);
-
-  // Pause before sending request.
-  download()->Pause();
-  download()->Start();
-
-  // Force to return ERR_IO_PENDING on network thread in
-  // InMemoryDownloadImpl::ResponseWriter::Write.
-  DrainPreviousTasks(request_context_getter()->GetNetworkTaskRunner());
-
-  download()->Resume();
-  delegate()->WaitForCompletion();
-
-  EXPECT_EQ(InMemoryDownload::State::COMPLETE, download()->state());
-  auto blob = download()->ResultAsBlob();
-
-  std::string expected;
-  EXPECT_TRUE(ReadFileToString(
-      GetTestDataDirectory().AppendASCII("text_data.json"), &expected));
-  VerifyBlobData(expected, blob.get());
+  VerifyBlobData(kTestDownloadData, blob.get());
 }
 
 }  // namespace
diff --git a/components/download/internal/common/stream_handle_input_stream.cc b/components/download/internal/common/stream_handle_input_stream.cc
index eeea7be..a7e7a16c 100644
--- a/components/download/internal/common/stream_handle_input_stream.cc
+++ b/components/download/internal/common/stream_handle_input_stream.cc
@@ -100,12 +100,7 @@
 void StreamHandleInputStream::OnStreamCompleted(
     mojom::NetworkRequestStatus status) {
   // This can be called before or after data pipe is completely drained.
-  OnResponseCompleted(ConvertMojoNetworkRequestStatusToInterruptReason(status));
-}
-
-void StreamHandleInputStream::OnResponseCompleted(
-    DownloadInterruptReason status) {
-  completion_status_ = status;
+  completion_status_ = ConvertMojoNetworkRequestStatusToInterruptReason(status);
   is_response_completed_ = true;
   if (completion_callback_)
     std::move(completion_callback_).Run();
diff --git a/components/download/public/common/BUILD.gn b/components/download/public/common/BUILD.gn
index 07d258c..e070097 100644
--- a/components/download/public/common/BUILD.gn
+++ b/components/download/public/common/BUILD.gn
@@ -38,6 +38,7 @@
     "download_url_parameters.cc",
     "download_url_parameters.h",
     "download_utils.h",
+    "input_stream.cc",
     "input_stream.h",
     "rate_estimator.h",
     "resume_mode.h",
diff --git a/components/download/public/common/input_stream.cc b/components/download/public/common/input_stream.cc
new file mode 100644
index 0000000..83558fa
--- /dev/null
+++ b/components/download/public/common/input_stream.cc
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/public/common/input_stream.h"
+
+namespace download {
+
+InputStream::~InputStream() = default;
+
+void InputStream::Initialize() {}
+
+void InputStream::RegisterDataReadyCallback(
+    const mojo::SimpleWatcher::ReadyCallback& callback) {}
+
+void InputStream::ClearDataReadyCallback() {}
+
+void InputStream::RegisterCompletionCallback(base::OnceClosure callback) {}
+
+}  // namespace download
diff --git a/components/download/public/common/input_stream.h b/components/download/public/common/input_stream.h
index e6c88dd..938daba2 100644
--- a/components/download/public/common/input_stream.h
+++ b/components/download/public/common/input_stream.h
@@ -24,21 +24,21 @@
     COMPLETE,
   };
 
-  virtual ~InputStream() = default;
+  virtual ~InputStream();
 
   // Initializes the inputStream object.
-  virtual void Initialize() {}
+  virtual void Initialize();
 
   // Returns true if the input stream contains no data, or false otherwise.
   virtual bool IsEmpty() = 0;
 
   // Register/clear callbacks when data become available.
   virtual void RegisterDataReadyCallback(
-      const mojo::SimpleWatcher::ReadyCallback& callback) {}
-  virtual void ClearDataReadyCallback() {}
+      const mojo::SimpleWatcher::ReadyCallback& callback);
+  virtual void ClearDataReadyCallback();
 
   // Registers stream completion callback if needed.
-  virtual void RegisterCompletionCallback(base::OnceClosure callback) {}
+  virtual void RegisterCompletionCallback(base::OnceClosure callback);
 
   // Reads data from the stream into |data|, |length| is the number of bytes
   // returned.
@@ -47,9 +47,6 @@
 
   // Returns the completion status.
   virtual DownloadInterruptReason GetCompletionStatus() = 0;
-
-  // Mark the InputStream as completed and set the completion status.
-  virtual void OnResponseCompleted(DownloadInterruptReason status){};
 };
 
 }  // namespace download
diff --git a/components/download/public/common/stream_handle_input_stream.h b/components/download/public/common/stream_handle_input_stream.h
index 810ed005..a54097e 100644
--- a/components/download/public/common/stream_handle_input_stream.h
+++ b/components/download/public/common/stream_handle_input_stream.h
@@ -32,7 +32,6 @@
   InputStream::StreamState Read(scoped_refptr<net::IOBuffer>* data,
                                           size_t* length) override;
   DownloadInterruptReason GetCompletionStatus() override;
-  void OnResponseCompleted(DownloadInterruptReason status) override;
 
   // mojom::DownloadStreamClient
   void OnStreamCompleted(mojom::NetworkRequestStatus status) override;
diff --git a/components/mirroring/browser/cast_remoting_sender.cc b/components/mirroring/browser/cast_remoting_sender.cc
index 2f365ac1..63f3557 100644
--- a/components/mirroring/browser/cast_remoting_sender.cc
+++ b/components/mirroring/browser/cast_remoting_sender.cc
@@ -362,7 +362,7 @@
   // Always force a post task to prevent the stack from growing too deep.
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&CastRemotingSender::ProcessNextInputTask,
-                                base::Unretained(this)));
+                                weak_factory_.GetWeakPtr()));
 }
 
 void CastRemotingSender::ReadFrame(uint32_t size) {
diff --git a/components/printing/browser/print_manager_utils.cc b/components/printing/browser/print_manager_utils.cc
index 5bd1fd71..6dffcb52 100644
--- a/components/printing/browser/print_manager_utils.cc
+++ b/components/printing/browser/print_manager_utils.cc
@@ -37,8 +37,10 @@
           switches::kSitePerProcess) ||
       base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kIsolateOrigins) ||
-      base::FeatureList::IsEnabled(features::kSitePerProcess) ||
-      base::FeatureList::IsEnabled(features::kIsolateOrigins) ||
+      ((base::FeatureList::IsEnabled(features::kSitePerProcess) ||
+        base::FeatureList::IsEnabled(features::kIsolateOrigins)) &&
+       !base::CommandLine::ForCurrentProcess()->HasSwitch(
+           switches::kDisableSiteIsolationTrials)) ||
       base::FeatureList::IsEnabled(features::kTopDocumentIsolation)) {
     PrintCompositeClient::CreateForWebContents(web_contents);
     if (for_preview) {
diff --git a/components/test/data/download/text_data.json b/components/test/data/download/text_data.json
deleted file mode 100644
index 369f7de..0000000
--- a/components/test/data/download/text_data.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{  
-   "data":"In earlier tellings, the dog had a better reputation than the cat, however the president veto it."
-}
\ No newline at end of file
diff --git a/components/update_client/component.h b/components/update_client/component.h
index c7112400..5293f3a 100644
--- a/components/update_client/component.h
+++ b/components/update_client/component.h
@@ -119,7 +119,7 @@
   std::string session_id() const;
 
  private:
-  friend class FakePingManagerImpl;
+  friend class MockPingManagerImpl;
   friend class UpdateCheckerTest;
 
   FRIEND_TEST_ALL_PREFIXES(PingManagerTest, SendPing);
diff --git a/components/update_client/ping_manager_unittest.cc b/components/update_client/ping_manager_unittest.cc
index 4d8b2fb..25f46ad 100644
--- a/components/update_client/ping_manager_unittest.cc
+++ b/components/update_client/ping_manager_unittest.cc
@@ -34,7 +34,7 @@
   ~PingManagerTest() override {}
 
   PingManager::Callback MakePingCallback();
-  scoped_refptr<UpdateContext> MakeFakeUpdateContext() const;
+  scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
 
   // Overrides from testing::Test.
   void SetUp() override;
@@ -96,7 +96,7 @@
   Quit();
 }
 
-scoped_refptr<UpdateContext> PingManagerTest::MakeFakeUpdateContext() const {
+scoped_refptr<UpdateContext> PingManagerTest::MakeMockUpdateContext() const {
   return base::MakeRefCounted<UpdateContext>(
       config_, false, std::vector<std::string>(),
       UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
@@ -110,7 +110,7 @@
   EXPECT_TRUE(interceptor);
 
   // Test eventresult="1" is sent for successful updates.
-  const auto update_context = MakeFakeUpdateContext();
+  const auto update_context = MakeMockUpdateContext();
 
   {
     Component component(*update_context, "abc");
@@ -303,7 +303,7 @@
 TEST_F(PingManagerTest, RequiresEncryption) {
   config_->SetPingUrl(GURL("http:\\foo\bar"));
 
-  const auto update_context = MakeFakeUpdateContext();
+  const auto update_context = MakeMockUpdateContext();
 
   Component component(*update_context, "abc");
 
diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc
index d37f2c9..ea07e24 100644
--- a/components/update_client/update_checker_unittest.cc
+++ b/components/update_client/update_checker_unittest.cc
@@ -138,7 +138,7 @@
   scoped_refptr<UpdateContext> update_context_;
 
  private:
-  scoped_refptr<UpdateContext> MakeFakeUpdateContext() const;
+  scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   base::OnceClosure quit_closure_;
@@ -171,7 +171,7 @@
 
   error_ = 0;
   retry_after_sec_ = 0;
-  update_context_ = MakeFakeUpdateContext();
+  update_context_ = MakeMockUpdateContext();
 }
 
 void UpdateCheckerTest::TearDown() {
@@ -204,7 +204,7 @@
   Quit();
 }
 
-scoped_refptr<UpdateContext> UpdateCheckerTest::MakeFakeUpdateContext() const {
+scoped_refptr<UpdateContext> UpdateCheckerTest::MakeMockUpdateContext() const {
   return base::MakeRefCounted<UpdateContext>(
       config_, false, std::vector<std::string>(),
       UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc
index b59c57b..4b9c0b1f 100644
--- a/components/update_client/update_client_unittest.cc
+++ b/components/update_client/update_client_unittest.cc
@@ -87,7 +87,7 @@
 
 using std::string;
 
-class FakePingManagerImpl : public PingManager {
+class MockPingManagerImpl : public PingManager {
  public:
   struct PingData {
     std::string id;
@@ -101,7 +101,7 @@
     bool diff_update_failed = false;
   };
 
-  explicit FakePingManagerImpl(scoped_refptr<Configurator> config);
+  explicit MockPingManagerImpl(scoped_refptr<Configurator> config);
 
   void SendPing(const Component& component, Callback callback) override;
 
@@ -110,21 +110,20 @@
   const std::vector<std::string>& events() const;
 
  protected:
-  ~FakePingManagerImpl() override;
+  ~MockPingManagerImpl() override;
 
  private:
   std::vector<PingData> ping_data_;
   std::vector<std::string> events_;
-  DISALLOW_COPY_AND_ASSIGN(FakePingManagerImpl);
+  DISALLOW_COPY_AND_ASSIGN(MockPingManagerImpl);
 };
 
-FakePingManagerImpl::FakePingManagerImpl(scoped_refptr<Configurator> config)
+MockPingManagerImpl::MockPingManagerImpl(scoped_refptr<Configurator> config)
     : PingManager(config) {}
 
-FakePingManagerImpl::~FakePingManagerImpl() {
-}
+MockPingManagerImpl::~MockPingManagerImpl() {}
 
-void FakePingManagerImpl::SendPing(const Component& component,
+void MockPingManagerImpl::SendPing(const Component& component,
                                    Callback callback) {
   PingData ping_data;
   ping_data.id = component.id_;
@@ -144,12 +143,12 @@
   std::move(callback).Run(0, "");
 }
 
-const std::vector<FakePingManagerImpl::PingData>&
-FakePingManagerImpl::ping_data() const {
+const std::vector<MockPingManagerImpl::PingData>&
+MockPingManagerImpl::ping_data() const {
   return ping_data_;
 }
 
-const std::vector<std::string>& FakePingManagerImpl::events() const {
+const std::vector<std::string>& MockPingManagerImpl::events() const {
   return events_;
 }
 
@@ -212,7 +211,7 @@
 // Tests the scenario where one update check is done for one CRX. The CRX
 // has no update.
 TEST_F(UpdateClientTest, OneCrxNoUpdate) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -225,7 +224,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -233,12 +232,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -268,33 +267,33 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   InSequence seq;
@@ -307,8 +306,8 @@
 
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), true,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), true,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -318,7 +317,7 @@
 // Tests the scenario where two CRXs are checked for updates. On CRX has
 // an update, the other CRX does not.
 TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -339,7 +338,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -347,12 +346,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -362,7 +361,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -430,15 +429,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -461,24 +460,24 @@
       result.total_bytes = 1843;
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(1u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
@@ -490,8 +489,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -521,8 +520,8 @@
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
                                         "abagagagagagagagagagagagagagagag"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -531,7 +530,7 @@
 
 // Tests the update check for two CRXs scenario. Both CRXs have updates.
 TEST_F(UpdateClientTest, TwoCrxUpdate) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -552,7 +551,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -560,12 +559,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -575,7 +574,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -666,15 +665,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -717,24 +716,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(2u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
@@ -751,8 +750,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -791,8 +790,8 @@
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
                                         "ihfokbkgjpifnbbojhneepfflplebdkc"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -803,7 +802,7 @@
 // CRX. The update for the first CRX fails. The update client waits before
 // attempting the update for the second CRX. This update succeeds.
 TEST_F(UpdateClientTest, TwoCrxUpdateDownloadTimeout) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -824,7 +823,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -832,12 +831,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -847,7 +846,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -935,15 +934,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -983,24 +982,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(2u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
@@ -1017,8 +1016,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -1057,8 +1056,8 @@
                                         "ihfokbkgjpifnbbojhneepfflplebdkc"};
 
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -1067,7 +1066,7 @@
 
 // Tests the differential update scenario for one CRX.
 TEST_F(UpdateClientTest, OneCrxDiffUpdate) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -1095,7 +1094,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -1103,12 +1102,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -1126,7 +1125,7 @@
 
       if (num_call == 1) {
         /*
-        Fake the following response:
+        Mock the following response:
         <?xml version='1.0' encoding='UTF-8'?>
         <response protocol='3.1'>
           <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
@@ -1166,7 +1165,7 @@
         component->SetParseResult(result);
       } else if (num_call == 2) {
         /*
-        Fake the following response:
+        Mock the following response:
         <?xml version='1.0' encoding='UTF-8'?>
         <response protocol='3.1'>
           <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
@@ -1223,15 +1222,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -1274,24 +1273,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(2u, ping_data.size());
       EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
       EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
@@ -1311,8 +1310,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -1346,18 +1345,18 @@
   const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
   {
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
 
   {
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
@@ -1407,7 +1406,7 @@
     base::FilePath unpack_path_;
   };
 
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -1428,7 +1427,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -1436,12 +1435,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -1451,7 +1450,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -1498,15 +1497,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -1529,24 +1528,24 @@
       result.total_bytes = 1843;
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(1u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
@@ -1558,8 +1557,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -1582,8 +1581,8 @@
 
   std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -1592,7 +1591,7 @@
 
 // Tests the fallback from differential to full update scenario for one CRX.
 TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -1620,7 +1619,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -1628,12 +1627,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -1651,7 +1650,7 @@
 
       if (num_call == 1) {
         /*
-        Fake the following response:
+        Mock the following response:
         <?xml version='1.0' encoding='UTF-8'?>
         <response protocol='3.1'>
           <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
@@ -1693,7 +1692,7 @@
         component->SetParseResult(result);
       } else if (num_call == 2) {
         /*
-        Fake the following response:
+        Mock the following response:
         <?xml version='1.0' encoding='UTF-8'?>
         <response protocol='3.1'>
           <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
@@ -1750,15 +1749,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -1813,24 +1812,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(2u, ping_data.size());
       EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
       EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
@@ -1850,8 +1849,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -1887,18 +1886,18 @@
 
   {
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
 
   {
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
@@ -1910,7 +1909,7 @@
 // done for one CRX. The second update check call is queued up and will run
 // after the first check has completed. The CRX has no updates.
 TEST_F(UpdateClientTest, OneCrxNoUpdateQueuedCall) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -1923,7 +1922,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       static int num_call = 0;
@@ -1936,12 +1935,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -1971,33 +1970,33 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   InSequence seq;
@@ -2014,11 +2013,11 @@
 
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -2027,7 +2026,7 @@
 
 // Tests the install of one CRX.
 TEST_F(UpdateClientTest, OneCrxInstall) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -2041,7 +2040,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -2049,12 +2048,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2064,7 +2063,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -2116,15 +2115,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -2151,24 +2150,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(1u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.0"), ping_data[0].previous_version);
@@ -2180,8 +2179,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   InSequence seq;
@@ -2201,8 +2200,8 @@
 
   update_client->Install(
       std::string("jebgalgnebhfojomionfpkfelancnnkf"),
-      base::BindOnce(&DataCallbackFake::Callback),
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -2211,7 +2210,7 @@
 
 // Tests that overlapping installs of the same CRX result in an error.
 TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -2225,7 +2224,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       static int num_call = 0;
@@ -2244,12 +2243,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2280,33 +2279,33 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
@@ -2320,13 +2319,13 @@
 
   update_client->Install(
       std::string("jebgalgnebhfojomionfpkfelancnnkf"),
-      base::BindOnce(&DataCallbackFake::Callback),
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   update_client->Install(
       std::string("jebgalgnebhfojomionfpkfelancnnkf"),
-      base::BindOnce(&DataCallbackFake::Callback),
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      base::BindOnce(&DataCallbackMock::Callback),
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -2336,13 +2335,13 @@
 // Tests that UpdateClient::Update returns Error::INVALID_ARGUMENT when
 // the |ids| parameter is empty.
 TEST_F(UpdateClientTest, EmptyIdList) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {}
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       DCHECK_EQ(Error::INVALID_ARGUMENT, error);
@@ -2350,12 +2349,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2368,50 +2367,50 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   const std::vector<std::string> empty_id_list;
   update_client->Update(
-      empty_id_list, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      empty_id_list, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
   RunThreads();
 }
 
 TEST_F(UpdateClientTest, SendUninstallPing) {
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       std::move(quit_closure).Run();
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
@@ -2429,7 +2428,7 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
@@ -2438,20 +2437,20 @@
     }
 
    private:
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
-    ~FakeCrxDownloader() override {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
+    ~MockCrxDownloader() override {}
 
     void DoStartDownload(const GURL& url) override {}
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(1u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("1.2.3.4"), ping_data[0].previous_version);
@@ -2462,18 +2461,18 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   update_client->SendUninstallPing(
       "jebgalgnebhfojomionfpkfelancnnkf", base::Version("1.2.3.4"), 10,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 }
 
 TEST_F(UpdateClientTest, RetryAfter) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -2486,7 +2485,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       static int num_call = 0;
@@ -2514,12 +2513,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2559,33 +2558,33 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
 
@@ -2616,9 +2615,9 @@
     // The engine handles this Update call but responds with a valid
     // |retry_after_sec|, which causes subsequent calls to fail.
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
@@ -2627,9 +2626,9 @@
     // This call will result in a completion callback invoked with
     // Error::ERROR_UPDATE_RETRY_LATER.
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
@@ -2639,8 +2638,8 @@
     // the value of |retry_after_sec| in the completion callback.
     base::RunLoop runloop;
     update_client->Install(std::string("jebgalgnebhfojomionfpkfelancnnkf"),
-                           base::BindOnce(&DataCallbackFake::Callback),
-                           base::BindOnce(&CompletionCallbackFake::Callback,
+                           base::BindOnce(&DataCallbackMock::Callback),
+                           base::BindOnce(&CompletionCallbackMock::Callback,
                                           runloop.QuitClosure()));
     runloop.Run();
   }
@@ -2648,9 +2647,9 @@
   {
     // This call succeeds.
     base::RunLoop runloop;
-    update_client->Update(ids, base::BindOnce(&DataCallbackFake::Callback),
+    update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
                           false,
-                          base::BindOnce(&CompletionCallbackFake::Callback,
+                          base::BindOnce(&CompletionCallbackMock::Callback,
                                          runloop.QuitClosure()));
     runloop.Run();
   }
@@ -2665,7 +2664,7 @@
 // the first component is not apply and the client responds with a
 // (SERVICE_ERROR, UPDATE_DISABLED)
 TEST_F(UpdateClientTest, TwoCrxUpdateOneUpdateDisabled) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -2687,7 +2686,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::NONE, error);
@@ -2695,12 +2694,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2710,7 +2709,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -2802,15 +2801,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -2837,24 +2836,24 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadProgress,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
                                     base::Unretained(this), result));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto ping_data = FakePingManagerImpl::ping_data();
+    ~MockPingManager() override {
+      const auto ping_data = MockPingManagerImpl::ping_data();
       EXPECT_EQ(2u, ping_data.size());
       EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
       EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
@@ -2873,8 +2872,8 @@
   config()->SetEnabledComponentUpdates(false);
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   {
@@ -2913,8 +2912,8 @@
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
                                         "ihfokbkgjpifnbbojhneepfflplebdkc"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -2923,7 +2922,7 @@
 
 // Tests the scenario where the update check fails.
 TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) {
-  class DataCallbackFake {
+  class DataCallbackMock {
    public:
     static void Callback(const std::vector<std::string>& ids,
                          std::vector<CrxComponent>* components) {
@@ -2936,7 +2935,7 @@
     }
   };
 
-  class CompletionCallbackFake {
+  class CompletionCallbackMock {
    public:
     static void Callback(base::OnceClosure quit_closure, Error error) {
       EXPECT_EQ(Error::UPDATE_CHECK_ERROR, error);
@@ -2944,12 +2943,12 @@
     }
   };
 
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -2970,33 +2969,33 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); }
+    ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
   };
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   MockObserver observer;
   InSequence seq;
@@ -3016,8 +3015,8 @@
 
   const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
   update_client->Update(
-      ids, base::BindOnce(&DataCallbackFake::Callback), false,
-      base::BindOnce(&CompletionCallbackFake::Callback, quit_closure()));
+      ids, base::BindOnce(&DataCallbackMock::Callback), false,
+      base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
 
   RunThreads();
 
@@ -3028,12 +3027,12 @@
 
 // Tests that a run action in invoked in the CRX install scenario.
 TEST_F(UpdateClientTest, ActionRun_Install) {
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -3043,7 +3042,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -3095,15 +3094,15 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override {
@@ -3130,20 +3129,20 @@
       }
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&FakeCrxDownloader::OnDownloadComplete,
+          FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
                                     base::Unretained(this), true, result,
                                     download_metrics));
     }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto& events = FakePingManagerImpl::events();
+    ~MockPingManager() override {
+      const auto& events = MockPingManagerImpl::events();
       EXPECT_EQ(3u, events.size());
       EXPECT_STREQ(
           "<event eventtype=\"14\" eventresult=\"1\" downloader=\"unknown\" "
@@ -3165,8 +3164,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   // The action is a program which returns 1877345072 as a hardcoded value.
   update_client->Install(
@@ -3193,12 +3192,12 @@
 // Tests that a run action is invoked in an update scenario when there was
 // no update.
 TEST_F(UpdateClientTest, ActionRun_NoUpdate) {
-  class FakeUpdateChecker : public UpdateChecker {
+  class MockUpdateChecker : public UpdateChecker {
    public:
     static std::unique_ptr<UpdateChecker> Create(
         scoped_refptr<Configurator> config,
         PersistedData* metadata) {
-      return std::make_unique<FakeUpdateChecker>();
+      return std::make_unique<MockUpdateChecker>();
     }
 
     void CheckForUpdates(const std::string& session_id,
@@ -3208,7 +3207,7 @@
                          bool enabled_component_updates,
                          UpdateCheckCallback update_check_callback) override {
       /*
-      Fake the following response:
+      Mock the following response:
 
       <?xml version='1.0' encoding='UTF-8'?>
       <response protocol='3.1'>
@@ -3241,28 +3240,28 @@
     }
   };
 
-  class FakeCrxDownloader : public CrxDownloader {
+  class MockCrxDownloader : public CrxDownloader {
    public:
     static std::unique_ptr<CrxDownloader> Create(
         bool is_background_download,
         scoped_refptr<net::URLRequestContextGetter> context_getter) {
-      return std::make_unique<FakeCrxDownloader>();
+      return std::make_unique<MockCrxDownloader>();
     }
 
-    FakeCrxDownloader() : CrxDownloader(nullptr) {}
+    MockCrxDownloader() : CrxDownloader(nullptr) {}
 
    private:
     void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
   };
 
-  class FakePingManager : public FakePingManagerImpl {
+  class MockPingManager : public MockPingManagerImpl {
    public:
-    explicit FakePingManager(scoped_refptr<Configurator> config)
-        : FakePingManagerImpl(config) {}
+    explicit MockPingManager(scoped_refptr<Configurator> config)
+        : MockPingManagerImpl(config) {}
 
    protected:
-    ~FakePingManager() override {
-      const auto& events = FakePingManagerImpl::events();
+    ~MockPingManager() override {
+      const auto& events = MockPingManagerImpl::events();
       EXPECT_EQ(1u, events.size());
       EXPECT_STREQ(
           "<event eventtype=\"42\" eventresult=\"1\" "
@@ -3309,8 +3308,8 @@
 
   scoped_refptr<UpdateClient> update_client =
       base::MakeRefCounted<UpdateClientImpl>(
-          config(), base::MakeRefCounted<FakePingManager>(config()),
-          &FakeUpdateChecker::Create, &FakeCrxDownloader::Create);
+          config(), base::MakeRefCounted<MockPingManager>(config()),
+          &MockUpdateChecker::Create, &MockCrxDownloader::Create);
 
   // The action is a program which returns 1877345072 as a hardcoded value.
   const std::vector<std::string> ids = {"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
diff --git a/components/viz/client/hit_test_data_provider_draw_quad.cc b/components/viz/client/hit_test_data_provider_draw_quad.cc
index a44809b..f571fa89 100644
--- a/components/viz/client/hit_test_data_provider_draw_quad.cc
+++ b/components/viz/client/hit_test_data_provider_draw_quad.cc
@@ -23,6 +23,14 @@
   hit_test_region_list->bounds.set_size(compositor_frame.size_in_pixels());
 
   for (const auto& render_pass : compositor_frame.render_pass_list) {
+    // Skip the render_pass if the transform is not invertible (i.e. it will not
+    // be able to receive events).
+    gfx::Transform transform_from_root_target;
+    if (!render_pass->transform_to_root_target.GetInverse(
+            &transform_from_root_target)) {
+      continue;
+    }
+
     for (const DrawQuad* quad : render_pass->quad_list) {
       if (quad->material == DrawQuad::SURFACE_CONTENT) {
         // Skip the quad if the transform is not invertible (i.e. it will not
@@ -44,7 +52,8 @@
         if (should_ask_for_child_region_)
           hit_test_region->flags |= mojom::kHitTestAsk;
         hit_test_region->rect = surface_quad->rect;
-        hit_test_region->transform = target_to_quad_transform;
+        hit_test_region->transform =
+            target_to_quad_transform * transform_from_root_target;
         hit_test_region_list->regions.push_back(std::move(hit_test_region));
       }
     }
diff --git a/components/viz/client/hit_test_data_provider_draw_quad_unittest.cc b/components/viz/client/hit_test_data_provider_draw_quad_unittest.cc
index 85eea7f5..dc0d0d6 100644
--- a/components/viz/client/hit_test_data_provider_draw_quad_unittest.cc
+++ b/components/viz/client/hit_test_data_provider_draw_quad_unittest.cc
@@ -25,24 +25,32 @@
 
 namespace {
 
-CompositorFrame MakeCompositorFrameWithChildSurface(
+SurfaceId CreateChildSurfaceId(uint32_t id) {
+  LocalSurfaceId child_local_surface_id(id, base::UnguessableToken::Create());
+  FrameSinkId frame_sink_id(id, 0);
+  SurfaceId child_surface_id(frame_sink_id, child_local_surface_id);
+  return child_surface_id;
+}
+
+std::unique_ptr<RenderPass> CreateRenderPassWithChildSurface(
     const SurfaceId& child_surface_id,
     const gfx::Rect& rect,
     const gfx::Rect& child_rect,
-    const gfx::Transform& transform) {
+    const gfx::Transform& render_pass_transform,
+    const gfx::Transform& shared_state_transform) {
   auto pass = RenderPass::Create();
-  pass->SetNew(1, rect, rect, gfx::Transform());
+  pass->SetNew(1, rect, rect, render_pass_transform);
 
   auto* shared_state = pass->CreateAndAppendSharedQuadState();
-  shared_state->SetAll(transform, rect, rect, rect, false, false, 1,
-                       SkBlendMode::kSrcOver, 0);
+  shared_state->SetAll(shared_state_transform, rect, rect, rect, false, false,
+                       1, SkBlendMode::kSrcOver, 0);
 
   auto* surface_quad = pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
   surface_quad->SetNew(pass->shared_quad_state_list.back(), child_rect,
                        child_rect, child_surface_id, base::nullopt,
                        SK_ColorWHITE, false);
 
-  return CompositorFrameBuilder().AddRenderPass(std::move(pass)).Build();
+  return pass;
 }
 
 }  // namespace
@@ -69,14 +77,18 @@
 
   // Ensure that a CompositorFrame with a child surface only set kHitTestAsk
   // for its child surface.
-  LocalSurfaceId child_local_surface_id(2, base::UnguessableToken::Create());
-  FrameSinkId frame_sink_id(2, 0);
-  SurfaceId child_surface_id(frame_sink_id, child_local_surface_id);
+  SurfaceId child_surface_id = CreateChildSurfaceId(2);
   gfx::Rect child_rect(200, 100);
-  gfx::Transform transform;
-  transform.Translate(-200, -100);
-  compositor_frame = MakeCompositorFrameWithChildSurface(
-      child_surface_id, kFrameRect, child_rect, transform);
+  gfx::Transform render_pass_transform;
+  render_pass_transform.Translate(-50, -100);
+  render_pass_transform.Skew(2, 3);
+  gfx::Transform shared_state_transform;
+  shared_state_transform.Translate(-200, -100);
+  auto pass = CreateRenderPassWithChildSurface(
+      child_surface_id, kFrameRect, child_rect, render_pass_transform,
+      shared_state_transform);
+  compositor_frame =
+      CompositorFrameBuilder().AddRenderPass(std::move(pass)).Build();
   hit_test_region_list =
       hit_test_data_provider->GetHitTestData(compositor_frame);
 
@@ -92,9 +104,70 @@
                 mojom::kHitTestChildSurface | mojom::kHitTestAsk,
             hit_test_region_list->regions[0]->flags);
   EXPECT_EQ(child_rect, hit_test_region_list->regions[0]->rect);
-  gfx::Transform transform_inverse;
-  EXPECT_TRUE(transform.GetInverse(&transform_inverse));
-  EXPECT_EQ(transform_inverse, hit_test_region_list->regions[0]->transform);
+  gfx::Transform render_pass_transform_inverse;
+  EXPECT_TRUE(render_pass_transform.GetInverse(&render_pass_transform_inverse));
+  gfx::Transform shared_state_transform_inverse;
+  EXPECT_TRUE(
+      shared_state_transform.GetInverse(&shared_state_transform_inverse));
+  EXPECT_EQ(shared_state_transform_inverse * render_pass_transform_inverse,
+            hit_test_region_list->regions[0]->transform);
+}
+
+// Test to ensure that we skip regions with a non-invertible transform when
+// preparing hit-test data.
+TEST(HitTestDataProviderDrawQuad, HitTestDataInvertibleTransform) {
+  std::unique_ptr<HitTestDataProvider> hit_test_data_provider =
+      std::make_unique<HitTestDataProviderDrawQuad>(
+          true /* should_ask_for_child_region */);
+
+  constexpr gfx::Rect kFrameRect(0, 0, 1024, 768);
+  gfx::Rect child_rect(200, 100);
+
+  // A degenerate matrix of all zeros is not invertible.
+  gfx::Transform not_invertible_transform;
+  not_invertible_transform.matrix().set(0, 0, 0.f);
+  not_invertible_transform.matrix().set(1, 1, 0.f);
+  not_invertible_transform.matrix().set(2, 2, 0.f);
+  not_invertible_transform.matrix().set(3, 3, 0.f);
+
+  gfx::Transform invertible_transform;
+  invertible_transform.Translate(-200, -100);
+
+  RenderPassList pass_list;
+
+  // A render pass that has non-invertible transform.
+  SurfaceId child_surface_id1 = CreateChildSurfaceId(2);
+  auto pass1 = CreateRenderPassWithChildSurface(
+      child_surface_id1, kFrameRect, child_rect, not_invertible_transform,
+      invertible_transform);
+  pass_list.push_back(std::move(pass1));
+
+  // A render pass with a draw quad that has non-invertible transform.
+  SurfaceId child_surface_id2 = CreateChildSurfaceId(3);
+  auto pass2 = CreateRenderPassWithChildSurface(
+      child_surface_id2, kFrameRect, child_rect, invertible_transform,
+      not_invertible_transform);
+  pass_list.push_back(std::move(pass2));
+
+  // A render pass and its draw quad both have invertible transforms
+  SurfaceId child_surface_id3 = CreateChildSurfaceId(4);
+  auto pass3 = CreateRenderPassWithChildSurface(
+      child_surface_id3, kFrameRect, child_rect, invertible_transform,
+      invertible_transform);
+  pass_list.push_back(std::move(pass3));
+
+  auto compositor_frame =
+      CompositorFrameBuilder().SetRenderPassList(std::move(pass_list)).Build();
+  mojom::HitTestRegionListPtr hit_test_region_list =
+      hit_test_data_provider->GetHitTestData(compositor_frame);
+
+  // Only pass3 should have a hit-test region that corresponds to
+  // child_surface_id3.
+  EXPECT_EQ(1u, hit_test_region_list->regions.size());
+  EXPECT_EQ(child_surface_id3.frame_sink_id(),
+            hit_test_region_list->regions[0]->frame_sink_id);
+  EXPECT_EQ(child_surface_id3.local_surface_id(),
+            hit_test_region_list->regions[0]->local_surface_id);
 }
 
 }  // namespace viz
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index facfe98..7fa21dd 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -1449,13 +1449,10 @@
           ::testing::tuple<bool, HighbitTexture>> {
  public:
   void SetSupportHighbitTexture(HighbitTexture texture) {
-    cc::TestInProcessContextProvider* context_provider =
-        GetTestInProcessContextProvider();
     switch (texture) {
       case HighbitTexture::Y8:
         break;
       case HighbitTexture::R16_EXT:
-        context_provider->SetSupportTextureNorm16(true);
         video_resource_updater_->SetUseR16ForTesting(true);
         break;
     }
diff --git a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
index 72cc46c..1749f07c 100644
--- a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
+++ b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
@@ -198,6 +198,14 @@
   hit_test_region_list->bounds.set_size(frame.size_in_pixels());
 
   for (const auto& render_pass : frame.render_pass_list) {
+    // Skip the render_pass if the transform is not invertible (i.e. it will not
+    // be able to receive events).
+    gfx::Transform transform_from_root_target;
+    if (!render_pass->transform_to_root_target.GetInverse(
+            &transform_from_root_target)) {
+      continue;
+    }
+
     for (const DrawQuad* quad : render_pass->quad_list) {
       if (quad->material == DrawQuad::SURFACE_CONTENT) {
         // Skip the quad if the transform is not invertible (i.e. it will not
@@ -217,7 +225,8 @@
         hit_test_region->flags = mojom::kHitTestMouse | mojom::kHitTestTouch |
                                  mojom::kHitTestChildSurface;
         hit_test_region->rect = surface_quad->rect;
-        hit_test_region->transform = target_to_quad_transform;
+        hit_test_region->transform =
+            target_to_quad_transform * transform_from_root_target;
         hit_test_region_list->regions.push_back(std::move(hit_test_region));
       }
     }
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index ce6a321..fcf413b 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -374,21 +374,6 @@
 MSVC_DISABLE_OPTIMIZE()
 MSVC_PUSH_DISABLE_WARNING(4748)
 
-#if defined(OS_ANDROID)
-NOINLINE void ResetThread_PROCESS_LAUNCHER(
-    std::unique_ptr<BrowserProcessSubThread> thread) {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  thread.reset();
-}
-#else   // defined(OS_ANDROID)
-NOINLINE void ResetThread_PROCESS_LAUNCHER() {
-  volatile int inhibit_comdat = __LINE__;
-  ALLOW_UNUSED_LOCAL(inhibit_comdat);
-  BrowserThreadImpl::StopRedirectionOfThreadID(BrowserThread::PROCESS_LAUNCHER);
-}
-#endif  // defined(OS_ANDROID)
-
 NOINLINE void ResetThread_IO(std::unique_ptr<BrowserProcessSubThread> thread) {
   volatile int inhibit_comdat = __LINE__;
   ALLOW_UNUSED_LOCAL(inhibit_comdat);
@@ -1022,37 +1007,6 @@
         *task_scheduler_init_params.get());
   }
 
-  TRACE_EVENT_BEGIN1("startup", "BrowserMainLoop::CreateThreads:start",
-                     "Thread", "BrowserThread::PROCESS_LAUNCHER");
-
-#if defined(OS_ANDROID)
-  // Android specializes Launcher thread so it is accessible in java.
-  // Note Android never does clean shutdown, so shutdown use-after-free
-  // concerns are not a problem in practice.
-  base::MessageLoop* message_loop = android::LauncherThread::GetMessageLoop();
-  DCHECK(message_loop);
-  // This BrowserThread will use this message loop instead of creating a new
-  // thread. Note that means this/ thread will not be joined on shutdown, and
-  // may cause use-after-free if anything tries to access objects deleted by
-  // AtExitManager, such as non-leaky LazyInstance.
-  process_launcher_thread_.reset(new BrowserProcessSubThread(
-      BrowserThread::PROCESS_LAUNCHER, message_loop));
-#else   // defined(OS_ANDROID)
-  // This thread ID will be backed by a SingleThreadTaskRunner using
-  // |task_traits|.
-  // TODO(gab): WithBaseSyncPrimitives() is likely not required here.
-  base::TaskTraits task_traits = {base::MayBlock(),
-                                  base::WithBaseSyncPrimitives(),
-                                  base::TaskPriority::USER_BLOCKING,
-                                  base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
-  scoped_refptr<base::SingleThreadTaskRunner> redirection_task_runner =
-      base::CreateSingleThreadTaskRunnerWithTraits(
-          task_traits, base::SingleThreadTaskRunnerThreadMode::DEDICATED);
-  DCHECK(redirection_task_runner);
-  BrowserThreadImpl::RedirectThreadIDToTaskRunner(
-      BrowserThread::PROCESS_LAUNCHER, std::move(redirection_task_runner));
-#endif  // defined(OS_ANDROID)
-
   // |io_thread_| is created by |PostMainMessageLoopStart()|, but its
   // full initialization is deferred until this point because it requires
   // several dependencies we don't want to depend on so early in startup.
@@ -1205,39 +1159,9 @@
 
   {
     base::ThreadRestrictions::ScopedAllowWait allow_wait_for_join;
-
-    // Must be size_t so we can subtract from it.
-    for (size_t thread_id = BrowserThread::ID_COUNT - 1;
-         thread_id >= (BrowserThread::UI + 1); --thread_id) {
-      // Find the thread object we want to stop. Looping over all valid
-      // BrowserThread IDs and DCHECKing on a missing case in the switch
-      // statement helps avoid a mismatch between this code and the
-      // BrowserThread::ID enumeration.
-      //
-      // The destruction order is the reverse order of occurrence in the
-      // BrowserThread::ID list. The rationale for the order is that he
-      // PROCESS_LAUNCHER thread must be stopped after IO in case the IO thread
-      // posted a task to terminate a process on the process launcher thread.
-      switch (thread_id) {
-        case BrowserThread::PROCESS_LAUNCHER: {
-          TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:LauncherThread");
-#if defined(OS_ANDROID)
-          ResetThread_PROCESS_LAUNCHER(std::move(process_launcher_thread_));
-#else   // defined(OS_ANDROID)
-          ResetThread_PROCESS_LAUNCHER();
-#endif  // defined(OS_ANDROID)
-          break;
-        }
-        case BrowserThread::IO: {
-          TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IOThread");
-          ResetThread_IO(std::move(io_thread_));
-          break;
-        }
-        case BrowserThread::UI:
-        case BrowserThread::ID_COUNT:
-          NOTREACHED();
-          break;
-      }
+    {
+      TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IOThread");
+      ResetThread_IO(std::move(io_thread_));
     }
 
     {
diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h
index 13a96b3..25bdb6e 100644
--- a/content/browser/browser_main_loop.h
+++ b/content/browser/browser_main_loop.h
@@ -339,11 +339,7 @@
   // Only the IO thread is a real thread by default, other BrowserThreads are
   // redirected to TaskScheduler under the hood.
   std::unique_ptr<BrowserProcessSubThread> io_thread_;
-#if defined(OS_ANDROID)
-  // On Android, the PROCESS_LAUNCHER thread is handled by Java,
-  // |process_launcher_thread_| is merely a proxy to the real message loop.
-  std::unique_ptr<BrowserProcessSubThread> process_launcher_thread_;
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
   // TaskScheduler doesn't support async I/O on Windows as CACHE thread is
   // the only user and this use case is going away in
   // https://codereview.chromium.org/2216583003/.
diff --git a/content/browser/browser_thread_impl.cc b/content/browser/browser_thread_impl.cc
index 566b0f7..c78abe0 100644
--- a/content/browser/browser_thread_impl.cc
+++ b/content/browser/browser_thread_impl.cc
@@ -32,7 +32,6 @@
 // Friendly names for the well-known threads.
 static const char* const g_browser_thread_names[BrowserThread::ID_COUNT] = {
   "",  // UI (name assembled in browser_main.cc).
-  "Chrome_ProcessLauncherThread",  // PROCESS_LAUNCHER
   "Chrome_IOThread",  // IO
 };
 
@@ -202,12 +201,6 @@
     DCHECK(globals.task_runners[identifier_]->RunsTasksInCurrentSequence());
   }
 #endif  // DCHECK_IS_ON()
-
-  if (identifier_ == BrowserThread::PROCESS_LAUNCHER) {
-    // Nesting and task observers are not allowed on redirected threads.
-    base::RunLoop::DisallowNestingOnCurrentThread();
-    message_loop()->DisallowTaskObservers();
-  }
 }
 
 // We disable optimizations for this block of functions so the compiler doesn't
@@ -221,13 +214,6 @@
   CHECK_GT(line_number, 0);
 }
 
-NOINLINE void BrowserThreadImpl::ProcessLauncherThreadRun(
-    base::RunLoop* run_loop) {
-  volatile int line_number = __LINE__;
-  Thread::Run(run_loop);
-  CHECK_GT(line_number, 0);
-}
-
 NOINLINE void BrowserThreadImpl::IOThreadRun(base::RunLoop* run_loop) {
   volatile int line_number = __LINE__;
   Thread::Run(run_loop);
@@ -254,8 +240,6 @@
   switch (identifier_) {
     case BrowserThread::UI:
       return UIThreadRun(run_loop);
-    case BrowserThread::PROCESS_LAUNCHER:
-      return ProcessLauncherThreadRun(run_loop);
     case BrowserThread::IO:
       return IOThreadRun(run_loop);
     case BrowserThread::ID_COUNT:
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index 8da5fe2..c78c0766 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -11,6 +11,7 @@
 #include "base/logging.h"
 #include "base/process/launch.h"
 #include "build/build_config.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 
@@ -62,8 +63,8 @@
     const ChildProcessLauncherPriority& priority) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::Process to_pass = process_.process.Duplicate();
-  BrowserThread::PostTask(
-      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE,
       base::BindOnce(
           &ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread,
           helper_, std::move(to_pass), priority));
diff --git a/content/browser/child_process_launcher_helper.cc b/content/browser/child_process_launcher_helper.cc
index 7ee3df4..e924b12d 100644
--- a/content/browser/child_process_launcher_helper.cc
+++ b/content/browser/child_process_launcher_helper.cc
@@ -6,18 +6,28 @@
 
 #include "base/command_line.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/single_thread_task_runner_thread_mode.h"
+#include "base/task_scheduler/task_traits.h"
 #include "content/browser/child_process_launcher.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "mojo/edk/embedder/platform_channel_pair.h"
 
+#if defined(OS_ANDROID)
+#include "content/browser/android/launcher_thread.h"
+#endif
+
 namespace content {
 namespace internal {
 
 namespace {
 
 void RecordHistogramsOnLauncherThread(base::TimeDelta launch_time) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   // Log the launch time, separating out the first one (which will likely be
   // slower due to the rest of the browser initializing at the same time).
   static bool done_first_launch = false;
@@ -85,14 +95,14 @@
     mojo_client_handle_ = channel_pair.PassClientHandle();
   }
 
-  BrowserThread::PostTask(
-      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE,
       base::BindOnce(&ChildProcessLauncherHelper::LaunchOnLauncherThread,
                      this));
 }
 
 void ChildProcessLauncherHelper::LaunchOnLauncherThread() {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
 
   begin_launch_time_ = base::TimeTicks::Now();
 
@@ -167,18 +177,52 @@
 // static
 void ChildProcessLauncherHelper::ForceNormalProcessTerminationAsync(
     ChildProcessLauncherHelper::Process process) {
-  if (BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER)) {
+  if (CurrentlyOnProcessLauncherTaskRunner()) {
     ForceNormalProcessTerminationSync(std::move(process));
     return;
   }
   // On Posix, EnsureProcessTerminated can lead to 2 seconds of sleep!
   // So don't do this on the UI/IO threads.
-  BrowserThread::PostTask(
-      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE,
       base::BindOnce(
           &ChildProcessLauncherHelper::ForceNormalProcessTerminationSync,
           std::move(process)));
 }
 
 }  // namespace internal
+
+// static
+base::SingleThreadTaskRunner* GetProcessLauncherTaskRunner() {
+#if defined(OS_ANDROID)
+  // Android specializes Launcher thread so it is accessible in java.
+  // Note Android never does clean shutdown, so shutdown use-after-free
+  // concerns are not a problem in practice.
+  // This process launcher thread will use the Java-side process-launching
+  // thread, instead of creating its own separate thread on C++ side. Note
+  // that means this thread will not be joined on shutdown, and may cause
+  // use-after-free if anything tries to access objects deleted by
+  // AtExitManager, such as non-leaky LazyInstance.
+  static base::NoDestructor<scoped_refptr<base::SingleThreadTaskRunner>>
+      launcher_task_runner(
+          android::LauncherThread::GetMessageLoop()->task_runner());
+#else   // defined(OS_ANDROID)
+  constexpr base::TaskTraits task_traits = {
+      base::MayBlock(), base::WithBaseSyncPrimitives(),
+      base::TaskPriority::USER_BLOCKING,
+      base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
+  // TODO(wez): Investigates whether we could use SequencedTaskRunner on
+  // platforms other than Windows. http://crbug.com/820200.
+  static base::NoDestructor<scoped_refptr<base::SingleThreadTaskRunner>>
+      launcher_task_runner(base::CreateSingleThreadTaskRunnerWithTraits(
+          task_traits, base::SingleThreadTaskRunnerThreadMode::DEDICATED));
+#endif  // defined(OS_ANDROID)
+  return (*launcher_task_runner).get();
+}
+
+// static
+bool CurrentlyOnProcessLauncherTaskRunner() {
+  return GetProcessLauncherTaskRunner()->RunsTasksInCurrentSequence();
+}
+
 }  // namespace content
diff --git a/content/browser/child_process_launcher_helper_android.cc b/content/browser/child_process_launcher_helper_android.cc
index 3867bf3..2c86dba 100644
--- a/content/browser/child_process_launcher_helper_android.cc
+++ b/content/browser/child_process_launcher_helper_android.cc
@@ -15,6 +15,7 @@
 #include "content/browser/posix_file_descriptor_info_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_descriptors.h"
 #include "content/public/common/content_switches.h"
@@ -32,7 +33,7 @@
 
 // Stops a child process based on the handle returned from StartChildProcess.
 void StopChildProcess(base::ProcessHandle handle) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   JNIEnv* env = AttachCurrentThread();
   DCHECK(env);
   Java_ChildProcessLauncherHelper_stop(env, static_cast<jint>(handle));
@@ -64,7 +65,7 @@
 
 std::unique_ptr<PosixFileDescriptorInfo>
 ChildProcessLauncherHelper::GetFilesToMap() {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
 
   // Android WebView runs in single process, ensure that we never get here when
   // running in single process mode.
@@ -162,15 +163,15 @@
 // static
 bool ChildProcessLauncherHelper::TerminateProcess(const base::Process& process,
                                                   int exit_code) {
-  BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
-                          base::Bind(&StopChildProcess, process.Handle()));
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&StopChildProcess, process.Handle()));
   return true;
 }
 
 // static
 void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
     ChildProcessLauncherHelper::Process process) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   VLOG(1) << "ChromeProcess: Stopping process with handle "
           << process.process.Handle();
   StopChildProcess(process.process.Handle());
@@ -211,7 +212,7 @@
     JNIEnv*,
     const base::android::JavaParamRef<jobject>& obj,
     jint handle) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   scoped_refptr<ChildProcessLauncherHelper> ref(this);
   Release();  // Balances with LaunchProcessOnLauncherThread.
 
diff --git a/content/browser/child_process_launcher_helper_fuchsia.cc b/content/browser/child_process_launcher_helper_fuchsia.cc
index 8be1b0e4..281f4609 100644
--- a/content/browser/child_process_launcher_helper_fuchsia.cc
+++ b/content/browser/child_process_launcher_helper_fuchsia.cc
@@ -8,6 +8,7 @@
 #include "base/process/launch.h"
 #include "content/browser/child_process_launcher.h"
 #include "content/common/sandbox_policy_fuchsia.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "mojo/edk/embedder/platform_channel_pair.h"
 
@@ -17,7 +18,7 @@
 void ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread(
     base::Process process,
     const ChildProcessLauncherPriority& priority) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   // TODO(fuchsia): Implement this. (crbug.com/707031)
   NOTIMPLEMENTED();
 }
@@ -64,14 +65,14 @@
 
 std::unique_ptr<FileMappedForLaunch>
 ChildProcessLauncherHelper::GetFilesToMap() {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   return std::unique_ptr<FileMappedForLaunch>();
 }
 
 bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
     const PosixFileDescriptorInfo& files_to_register,
     base::LaunchOptions* options) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
 
   mojo::edk::PlatformChannelPair::PrepareToPassHandleToChildProcess(
       mojo_client_handle(), command_line(), &options->handles_to_transfer);
@@ -87,7 +88,7 @@
     std::unique_ptr<FileMappedForLaunch> files_to_register,
     bool* is_synchronous_launch,
     int* launch_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   DCHECK(mojo_client_handle().is_valid());
 
   // TODO(750938): Implement sandboxed/isolated subprocess launching.
@@ -99,7 +100,7 @@
 void ChildProcessLauncherHelper::AfterLaunchOnLauncherThread(
     const ChildProcessLauncherHelper::Process& process,
     const base::LaunchOptions& options) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
 
   if (process.process.IsValid()) {
     // |mojo_client_handle_| has already been transferred to the child process
@@ -112,7 +113,7 @@
 // static
 void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
     ChildProcessLauncherHelper::Process process) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   process.process.Terminate(RESULT_CODE_NORMAL_EXIT, true);
 }
 
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
index 64957aa..ee4dd38 100644
--- a/content/browser/child_process_launcher_helper_linux.cc
+++ b/content/browser/child_process_launcher_helper_linux.cc
@@ -11,6 +11,7 @@
 #include "content/browser/sandbox_host_linux.h"
 #include "content/browser/zygote_host/zygote_communication_linux.h"
 #include "content/browser/zygote_host/zygote_host_impl_linux.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/common_sandbox_support_linux.h"
 #include "content/public/common/content_client.h"
@@ -36,7 +37,7 @@
 
 std::unique_ptr<FileMappedForLaunch>
 ChildProcessLauncherHelper::GetFilesToMap() {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   return CreateDefaultPosixFilesToMap(child_process_id(), mojo_client_handle(),
                                       true /* include_service_required_files */,
                                       GetProcessType(), command_line());
@@ -154,7 +155,7 @@
 void ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread(
     base::Process process,
     const ChildProcessLauncherPriority& priority) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   if (process.CanBackgroundProcesses())
     process.SetProcessBackgrounded(priority.background);
 }
diff --git a/content/browser/child_process_launcher_helper_mac.cc b/content/browser/child_process_launcher_helper_mac.cc
index 3dd188d..ad6ff62 100644
--- a/content/browser/child_process_launcher_helper_mac.cc
+++ b/content/browser/child_process_launcher_helper_mac.cc
@@ -13,6 +13,7 @@
 #include "content/browser/mach_broker_mac.h"
 #include "content/browser/sandbox_parameters_mac.h"
 #include "content/grit/content_resources.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_paths.h"
@@ -47,7 +48,7 @@
 
 std::unique_ptr<PosixFileDescriptorInfo>
 ChildProcessLauncherHelper::GetFilesToMap() {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   return CreateDefaultPosixFilesToMap(
       child_process_id(), mojo_client_handle(),
       false /* include_service_required_files */, GetProcessType(),
@@ -81,7 +82,6 @@
     case service_manager::SANDBOX_TYPE_NACL_LOADER:
     case service_manager::SANDBOX_TYPE_PDF_COMPOSITOR:
     case service_manager::SANDBOX_TYPE_PROFILING:
-    case service_manager::SANDBOX_TYPE_GPU:
       v2_process = true;
       break;
     default:
@@ -242,7 +242,7 @@
 // static
 void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
     ChildProcessLauncherHelper::Process process) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   // Client has gone away, so just kill the process.  Using exit code 0 means
   // that UMA won't treat this as a crash.
   process.process.Terminate(RESULT_CODE_NORMAL_EXIT, false);
diff --git a/content/browser/child_process_launcher_helper_win.cc b/content/browser/child_process_launcher_helper_win.cc
index bf9836e..ca73f60 100644
--- a/content/browser/child_process_launcher_helper_win.cc
+++ b/content/browser/child_process_launcher_helper_win.cc
@@ -10,6 +10,7 @@
 #include "base/win/win_util.h"
 #include "content/browser/child_process_launcher.h"
 #include "content/browser/child_process_launcher_helper.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/sandbox_init.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
@@ -46,7 +47,7 @@
 bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
     const FileMappedForLaunch& files_to_register,
     base::LaunchOptions* options) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   return true;
 }
 
@@ -56,7 +57,7 @@
     std::unique_ptr<FileMappedForLaunch> files_to_register,
     bool* is_synchronous_launch,
     int* launch_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   *is_synchronous_launch = true;
   if (delegate_->ShouldLaunchElevated()) {
     // When establishing a Mojo connection, the pipe path has already been added
@@ -85,7 +86,7 @@
 void ChildProcessLauncherHelper::AfterLaunchOnLauncherThread(
     const ChildProcessLauncherHelper::Process& process,
     const base::LaunchOptions& options) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
 }
 
 base::TerminationStatus ChildProcessLauncherHelper::GetTerminationStatus(
@@ -103,7 +104,7 @@
 
 void ChildProcessLauncherHelper::ForceNormalProcessTerminationSync(
     ChildProcessLauncherHelper::Process process) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   // Client has gone away, so just kill the process.  Using exit code 0 means
   // that UMA won't treat this as a crash.
   process.process.Terminate(RESULT_CODE_NORMAL_EXIT, false);
@@ -112,7 +113,7 @@
 void ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread(
     base::Process process,
     const ChildProcessLauncherPriority& priority) {
-  DCHECK_CURRENTLY_ON(BrowserThread::PROCESS_LAUNCHER);
+  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
   if (process.CanBackgroundProcesses())
     process.SetProcessBackgrounded(priority.background);
 }
diff --git a/content/browser/download/byte_stream_input_stream.cc b/content/browser/download/byte_stream_input_stream.cc
index 3d53718..247821957 100644
--- a/content/browser/download/byte_stream_input_stream.cc
+++ b/content/browser/download/byte_stream_input_stream.cc
@@ -56,9 +56,4 @@
   return completion_status_;
 }
 
-void ByteStreamInputStream::OnResponseCompleted(
-    download::DownloadInterruptReason status) {
-  completion_status_ = status;
-}
-
 }  // namespace content
diff --git a/content/browser/download/byte_stream_input_stream.h b/content/browser/download/byte_stream_input_stream.h
index 469839c..1deaeb4b 100644
--- a/content/browser/download/byte_stream_input_stream.h
+++ b/content/browser/download/byte_stream_input_stream.h
@@ -27,7 +27,6 @@
   download::InputStream::StreamState Read(scoped_refptr<net::IOBuffer>* data,
                                           size_t* length) override;
   download::DownloadInterruptReason GetCompletionStatus() override;
-  void OnResponseCompleted(download::DownloadInterruptReason status) override;
 
  private:
   // ByteStreamReader to read from.
diff --git a/content/browser/download/download_file_impl.cc b/content/browser/download/download_file_impl.cc
index 896cff2..6dac814 100644
--- a/content/browser/download/download_file_impl.cc
+++ b/content/browser/download/download_file_impl.cc
@@ -102,11 +102,6 @@
   input_stream_->ClearDataReadyCallback();
 }
 
-void DownloadFileImpl::SourceStream::OnResponseCompleted(
-    download::DownloadInterruptReason reason) {
-  input_stream_->OnResponseCompleted(reason);
-}
-
 download::DownloadInterruptReason
 DownloadFileImpl::SourceStream::GetCompletionStatus() const {
   return input_stream_->GetCompletionStatus();
diff --git a/content/browser/download/download_file_impl.h b/content/browser/download/download_file_impl.h
index 6b90a907..9f67af26 100644
--- a/content/browser/download/download_file_impl.h
+++ b/content/browser/download/download_file_impl.h
@@ -95,9 +95,6 @@
 
     void Initialize();
 
-    // Called when response is completed.
-    void OnResponseCompleted(download::DownloadInterruptReason reason);
-
     // Called after successfully writing a buffer to disk.
     void OnWriteBytesToDisk(int64_t bytes_write);
 
diff --git a/content/browser/download/download_file_unittest.cc b/content/browser/download/download_file_unittest.cc
index 8ae1ac9..0c5124568 100644
--- a/content/browser/download/download_file_unittest.cc
+++ b/content/browser/download/download_file_unittest.cc
@@ -23,9 +23,8 @@
 #include "components/download/public/common/download_create_info.h"
 #include "components/download/public/common/download_destination_observer.h"
 #include "components/download/public/common/download_interrupt_reasons.h"
-#include "content/browser/byte_stream.h"
-#include "content/browser/download/byte_stream_input_stream.h"
 #include "content/browser/download/download_file_impl.h"
+#include "content/browser/download/mock_input_stream.h"
 #include "content/public/browser/download_manager.h"
 #include "content/public/test/mock_download_manager.h"
 #include "net/base/file_stream.h"
@@ -70,18 +69,6 @@
   return base::HexEncode(&hash_value.front(), hash_value.size());
 }
 
-class MockByteStreamReader : public ByteStreamReader {
- public:
-  MockByteStreamReader() {}
-  ~MockByteStreamReader() {}
-
-  // ByteStream functions
-  MOCK_METHOD2(Read, ByteStreamReader::StreamState(
-      scoped_refptr<net::IOBuffer>*, size_t*));
-  MOCK_CONST_METHOD0(GetStatus, int());
-  MOCK_METHOD1(RegisterCallback, void(const base::Closure&));
-};
-
 class MockDownloadDestinationObserver
     : public download::DownloadDestinationObserver {
  public:
@@ -115,8 +102,6 @@
   MOCK_METHOD2(CurrentUpdateStatus, void(int64_t, int64_t));
 };
 
-MATCHER(IsNullCallback, "") { return (arg.is_null()); }
-
 enum DownloadFileRenameMethodType { RENAME_AND_UNIQUIFY, RENAME_AND_ANNOTATE };
 
 // This is a test DownloadFileImpl that has no retry delay and, on Posix,
@@ -174,7 +159,7 @@
         observer_factory_(observer_.get()),
         input_stream_(nullptr),
         additional_streams_(
-            std::vector<StrictMock<MockByteStreamReader>*>{nullptr, nullptr}),
+            std::vector<StrictMock<MockInputStream>*>{nullptr, nullptr}),
         bytes_(-1),
         bytes_per_sec_(-1) {}
 
@@ -202,10 +187,13 @@
   }
 
   // Mock calls to this function are forwarded here.
-  void RegisterCallback(const base::Closure& sink_callback) {
+  void RegisterCallback(
+      const mojo::SimpleWatcher::ReadyCallback& sink_callback) {
     sink_callback_ = sink_callback;
   }
 
+  void ClearCallback() { sink_callback_.Reset(); }
+
   void SetInterruptReasonCallback(const base::Closure& closure,
                                   download::DownloadInterruptReason* reason_p,
                                   download::DownloadInterruptReason reason,
@@ -227,11 +215,11 @@
     // There can be only one.
     DCHECK(!download_file_.get());
 
-    input_stream_ = new StrictMock<MockByteStreamReader>();
+    input_stream_ = new StrictMock<MockInputStream>();
 
     // TODO: Need to actually create a function that'll set the variables
     // based on the inputs from the callback.
-    EXPECT_CALL(*input_stream_, RegisterCallback(_))
+    EXPECT_CALL(*input_stream_, RegisterDataReadyCallback(_))
         .WillOnce(Invoke(this, &DownloadFileTest::RegisterCallback))
         .RetiresOnSaturation();
 
@@ -242,12 +230,11 @@
 
     download_file_.reset(new TestDownloadFileImpl(
         std::move(save_info), download_dir_.GetPath(),
-        std::make_unique<ByteStreamInputStream>(
-            std::unique_ptr<ByteStreamReader>(input_stream_)),
+        std::unique_ptr<MockInputStream>(input_stream_),
         download::DownloadItem::kInvalidId, observer_factory_.GetWeakPtr()));
 
     EXPECT_CALL(*input_stream_, Read(_, _))
-        .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+        .WillOnce(Return(download::InputStream::EMPTY))
         .RetiresOnSaturation();
 
     base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this);
@@ -285,20 +272,20 @@
   // Don't actually trigger the callback or do verifications.
   void SetupDataAppend(const char** data_chunks,
                        size_t num_chunks,
-                       MockByteStreamReader* stream_reader,
+                       MockInputStream* input_stream,
                        ::testing::Sequence s,
                        int64_t offset = -1) {
-    DCHECK(stream_reader);
+    DCHECK(input_stream);
     size_t current_pos = static_cast<size_t>(offset);
     for (size_t i = 0; i < num_chunks; i++) {
       const char *source_data = data_chunks[i];
       size_t length = strlen(source_data);
       scoped_refptr<net::IOBuffer> data = new net::IOBuffer(length);
       memcpy(data->data(), source_data, length);
-      EXPECT_CALL(*stream_reader, Read(_, _))
+      EXPECT_CALL(*input_stream, Read(_, _))
           .InSequence(s)
           .WillOnce(DoAll(SetArgPointee<0>(data), SetArgPointee<1>(length),
-                          Return(ByteStreamReader::STREAM_HAS_DATA)))
+                          Return(download::InputStream::HAS_DATA)))
           .RetiresOnSaturation();
 
       if (offset < 0) {
@@ -329,24 +316,24 @@
     SetupDataAppend(data_chunks, num_chunks, input_stream_, s1);
     EXPECT_CALL(*input_stream_, Read(_, _))
         .InSequence(s1)
-        .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+        .WillOnce(Return(download::InputStream::EMPTY))
         .RetiresOnSaturation();
-    sink_callback_.Run();
+    sink_callback_.Run(MOJO_RESULT_OK);
     VerifyStreamAndSize();
   }
 
   void SetupFinishStream(download::DownloadInterruptReason interrupt_reason,
-                         MockByteStreamReader* stream_reader,
+                         MockInputStream* input_stream,
                          ::testing::Sequence s) {
-    EXPECT_CALL(*stream_reader, Read(_, _))
+    EXPECT_CALL(*input_stream, Read(_, _))
         .InSequence(s)
-        .WillOnce(Return(ByteStreamReader::STREAM_COMPLETE))
+        .WillOnce(Return(download::InputStream::COMPLETE))
         .RetiresOnSaturation();
-    EXPECT_CALL(*stream_reader, GetStatus())
+    EXPECT_CALL(*input_stream, GetCompletionStatus())
         .InSequence(s)
         .WillOnce(Return(interrupt_reason))
         .RetiresOnSaturation();
-    EXPECT_CALL(*stream_reader, RegisterCallback(_)).RetiresOnSaturation();
+    EXPECT_CALL(*input_stream, ClearDataReadyCallback()).RetiresOnSaturation();
   }
 
   void FinishStream(download::DownloadInterruptReason interrupt_reason,
@@ -354,7 +341,7 @@
                     const std::string& expected_hash) {
     ::testing::Sequence s1;
     SetupFinishStream(interrupt_reason, input_stream_, s1);
-    sink_callback_.Run();
+    sink_callback_.Run(MOJO_RESULT_OK);
     VerifyStreamAndSize();
     if (check_observer) {
       EXPECT_CALL(*(observer_.get()),
@@ -430,16 +417,16 @@
   }
 
   // Prepare a byte stream to write to the file sink.
-  void PrepareStream(StrictMock<MockByteStreamReader>** stream,
+  void PrepareStream(StrictMock<MockInputStream>** stream,
                      int64_t offset,
                      bool create_stream,
                      bool will_finish,
                      const char** buffers,
                      size_t num_buffer) {
     if (create_stream)
-      *stream = new StrictMock<MockByteStreamReader>();
+      *stream = new StrictMock<MockInputStream>();
 
-    // Expectation on MockByteStreamReader for MultipleStreams tests:
+    // Expectation on MockInputStream for MultipleStreams tests:
     // 1. RegisterCallback: Must called twice. One to set the callback, the
     // other to release the stream.
     // 2. Read: If filled with N buffer, called (N+1) times, where the last Read
@@ -483,13 +470,13 @@
 
   // Stream for sending data into the download file.
   // Owned by download_file_; will be alive for lifetime of download_file_.
-  StrictMock<MockByteStreamReader>* input_stream_;
+  StrictMock<MockInputStream>* input_stream_;
 
   // Additional streams to test multiple stream write.
-  std::vector<StrictMock<MockByteStreamReader>*> additional_streams_;
+  std::vector<StrictMock<MockInputStream>*> additional_streams_;
 
   // Sink callback data for stream.
-  base::Closure sink_callback_;
+  mojo::SimpleWatcher::ReadyCallback sink_callback_;
 
   base::ScopedTempDir download_dir_;
 
@@ -729,7 +716,7 @@
     ASSERT_TRUE(base::MakeFileUnwritable(target_dir));
 
     // Expect nulling out of further processing.
-    EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback()));
+    EXPECT_CALL(*input_stream_, ClearDataReadyCallback());
     ExpectPermissionError(InvokeSelectedRenameMethod(target_path, nullptr));
     EXPECT_FALSE(base::PathExists(target_path_suffixed));
   }
@@ -890,7 +877,7 @@
   SetupFinishStream(download::DOWNLOAD_INTERRUPT_REASON_NONE, input_stream_,
                     s1);
   EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
-  sink_callback_.Run();
+  sink_callback_.Run(MOJO_RESULT_OK);
   VerifyStreamAndSize();
   base::RunLoop().RunUntilIdle();
   DestroyDownloadFile(0);
@@ -922,7 +909,7 @@
   EXPECT_CALL(*(observer_.get()),
               CurrentUpdateStatus(strlen(kTestData1) + strlen(kTestData2), _));
 
-  sink_callback_.Run();
+  sink_callback_.Run(MOJO_RESULT_OK);
   base::RunLoop().RunUntilIdle();
   VerifyStreamAndSize();
   DestroyDownloadFile(0);
@@ -942,16 +929,15 @@
   PrepareStream(&additional_streams_[0], stream_0_length, true, true,
                 kTestData7, 2);
 
-  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
+  EXPECT_CALL(*additional_streams_[0], RegisterDataReadyCallback(_))
       .RetiresOnSaturation();
   EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
 
   // Activate the streams.
   download_file_->AddInputStream(
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(additional_streams_[0])),
-      stream_0_length, download::DownloadSaveInfo::kLengthFullContent);
-  sink_callback_.Run();
+      std::unique_ptr<MockInputStream>(additional_streams_[0]), stream_0_length,
+      download::DownloadSaveInfo::kLengthFullContent);
+  sink_callback_.Run(MOJO_RESULT_OK);
   base::RunLoop().RunUntilIdle();
 
   SourceStreamTestData stream_data_0(0, stream_0_length, true);
@@ -984,26 +970,28 @@
   PrepareStream(&additional_streams_[1], stream_0_length + stream_1_length,
                 true, true, kTestData6, 2);
 
-  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
-      .Times(2)
+  EXPECT_CALL(*additional_streams_[0], RegisterDataReadyCallback(_))
+      .Times(1)
       .RetiresOnSaturation();
 
-  EXPECT_CALL(*additional_streams_[1], RegisterCallback(_))
+  EXPECT_CALL(*additional_streams_[0], ClearDataReadyCallback())
+      .Times(1)
+      .RetiresOnSaturation();
+
+  EXPECT_CALL(*additional_streams_[1], RegisterDataReadyCallback(_))
       .RetiresOnSaturation();
 
   EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
 
   // Activate all the streams.
   download_file_->AddInputStream(
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(additional_streams_[0])),
-      stream_0_length, stream_1_length);
+      std::unique_ptr<MockInputStream>(additional_streams_[0]), stream_0_length,
+      stream_1_length);
   download_file_->AddInputStream(
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(additional_streams_[1])),
+      std::unique_ptr<MockInputStream>(additional_streams_[1]),
       stream_0_length + stream_1_length,
       download::DownloadSaveInfo::kLengthFullContent);
-  sink_callback_.Run();
+  sink_callback_.Run(MOJO_RESULT_OK);
   base::RunLoop().RunUntilIdle();
 
   SourceStreamTestData stream_data_0(0, stream_0_length, true);
@@ -1034,17 +1022,16 @@
 
   EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
 
-  sink_callback_.Run();
+  sink_callback_.Run(MOJO_RESULT_OK);
   base::RunLoop().RunUntilIdle();
 
   // Add another stream, the file is already closed, so nothing should be
   // called.
   EXPECT_FALSE(download_file_->InProgress());
 
-  additional_streams_[0] = new StrictMock<MockByteStreamReader>();
+  additional_streams_[0] = new StrictMock<MockInputStream>();
   download_file_->AddInputStream(
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(additional_streams_[0])),
+      std::unique_ptr<MockInputStream>(additional_streams_[0]),
       stream_0_length - 1, download::DownloadSaveInfo::kLengthFullContent);
   base::RunLoop().RunUntilIdle();
 
@@ -1069,23 +1056,25 @@
 
   EXPECT_CALL(*input_stream_, Read(_, _))
       .InSequence(seq)
-      .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+      .WillOnce(Return(download::InputStream::EMPTY))
       .RetiresOnSaturation();
-  sink_callback_.Run();
+  sink_callback_.Run(MOJO_RESULT_OK);
   base::RunLoop().RunUntilIdle();
 
-  additional_streams_[0] = new StrictMock<MockByteStreamReader>();
-  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
+  additional_streams_[0] = new StrictMock<MockInputStream>();
+  EXPECT_CALL(*additional_streams_[0], RegisterDataReadyCallback(_))
       .WillRepeatedly(Invoke(this, &DownloadFileTest::RegisterCallback))
       .RetiresOnSaturation();
+  EXPECT_CALL(*additional_streams_[0], ClearDataReadyCallback())
+      .WillRepeatedly(Invoke(this, &DownloadFileTest::ClearCallback))
+      .RetiresOnSaturation();
   EXPECT_CALL(*additional_streams_[0], Read(_, _))
-      .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
+      .WillOnce(Return(download::InputStream::EMPTY))
       .RetiresOnSaturation();
 
   download_file_->AddInputStream(
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(additional_streams_[0])),
-      0, download::DownloadSaveInfo::kLengthFullContent);
+      std::unique_ptr<MockInputStream>(additional_streams_[0]), 0,
+      download::DownloadSaveInfo::kLengthFullContent);
 
   // The stream should get terminated and reset the callback.
   EXPECT_TRUE(sink_callback_.is_null());
diff --git a/content/browser/download/download_job.h b/content/browser/download/download_job.h
index 376a757..bc92618 100644
--- a/content/browser/download/download_job.h
+++ b/content/browser/download/download_job.h
@@ -10,7 +10,6 @@
 #include "base/time/time.h"
 #include "components/download/public/common/download_interrupt_reasons.h"
 #include "components/download/public/common/download_request_handle_interface.h"
-#include "content/browser/byte_stream.h"
 #include "content/browser/download/download_file.h"
 #include "content/common/content_export.h"
 
diff --git a/content/browser/download/mock_input_stream.cc b/content/browser/download/mock_input_stream.cc
new file mode 100644
index 0000000..ad3eaa3
--- /dev/null
+++ b/content/browser/download/mock_input_stream.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/download/mock_input_stream.h"
+
+namespace content {
+
+MockInputStream::MockInputStream() = default;
+
+MockInputStream::~MockInputStream() = default;
+
+}  // namespace content
diff --git a/content/browser/download/mock_input_stream.h b/content/browser/download/mock_input_stream.h
new file mode 100644
index 0000000..fb0743d9
--- /dev/null
+++ b/content/browser/download/mock_input_stream.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_DOWNLOAD_MOCK_INPUT_STREAM_H_
+#define CONTENT_BROWSER_DOWNLOAD_MOCK_INPUT_STREAM_H_
+
+#include "components/download/public/common/input_stream.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+
+class MockInputStream : public download::InputStream {
+ public:
+  MockInputStream();
+  ~MockInputStream() override;
+
+  // download::InputStream functions
+  MOCK_METHOD0(IsEmpty, bool());
+  MOCK_METHOD1(RegisterDataReadyCallback,
+               void(const mojo::SimpleWatcher::ReadyCallback&));
+  MOCK_METHOD0(ClearDataReadyCallback, void());
+  MOCK_METHOD2(Read,
+               download::InputStream::StreamState(scoped_refptr<net::IOBuffer>*,
+                                                  size_t*));
+  MOCK_METHOD0(GetCompletionStatus, download::DownloadInterruptReason());
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_DOWNLOAD_MOCK_INPUT_STREAM_H_
diff --git a/content/browser/download/parallel_download_job_unittest.cc b/content/browser/download/parallel_download_job_unittest.cc
index a52a5d1c..2a50005 100644
--- a/content/browser/download/parallel_download_job_unittest.cc
+++ b/content/browser/download/parallel_download_job_unittest.cc
@@ -13,10 +13,10 @@
 #include "base/test/scoped_task_environment.h"
 #include "components/download/public/common/download_destination_observer.h"
 #include "components/download/public/common/download_task_runner.h"
-#include "content/browser/download/byte_stream_input_stream.h"
 #include "content/browser/download/download_file_impl.h"
 #include "content/browser/download/download_item_impl_delegate.h"
 #include "content/browser/download/mock_download_item_impl.h"
+#include "content/browser/download/mock_input_stream.h"
 #include "content/browser/download/parallel_download_utils.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -57,15 +57,6 @@
   MOCK_METHOD2(CurrentUpdateStatus, void(int64_t, int64_t));
 };
 
-class MockByteStreamReader : public ByteStreamReader {
- public:
-  MOCK_METHOD2(Read,
-               ByteStreamReader::StreamState(scoped_refptr<net::IOBuffer>*,
-                                             size_t*));
-  MOCK_CONST_METHOD0(GetStatus, int());
-  MOCK_METHOD1(RegisterCallback, void(const base::Closure&));
-};
-
 }  // namespace
 
 class ParallelDownloadJobForTest : public ParallelDownloadJob {
@@ -182,9 +173,7 @@
         std::make_unique<download::DownloadCreateInfo>();
     create_info->request_handle = std::move(request_handle);
     delegate->OnUrlDownloadStarted(
-        std::move(create_info),
-        std::make_unique<ByteStreamInputStream>(
-            std::make_unique<MockByteStreamReader>()),
+        std::move(create_info), std::make_unique<MockInputStream>(),
         download::DownloadUrlParameters::OnStartedCallback());
   }
 
@@ -486,16 +475,14 @@
 // Test that parallel request is not created until download file is initialized.
 TEST_F(ParallelDownloadJobTest, ParallelRequestNotCreatedUntilFileInitialized) {
   auto save_info = std::make_unique<download::DownloadSaveInfo>();
-  StrictMock<MockByteStreamReader>* input_stream =
-      new StrictMock<MockByteStreamReader>();
+  StrictMock<MockInputStream>* input_stream = new StrictMock<MockInputStream>();
   auto observer =
       std::make_unique<StrictMock<MockDownloadDestinationObserver>>();
   base::WeakPtrFactory<download::DownloadDestinationObserver> observer_factory(
       observer.get());
   auto download_file = std::make_unique<DownloadFileImpl>(
       std::move(save_info), base::FilePath(),
-      std::make_unique<ByteStreamInputStream>(
-          std::unique_ptr<ByteStreamReader>(input_stream)),
+      std::unique_ptr<MockInputStream>(input_stream),
       download::DownloadItem::kInvalidId, observer_factory.GetWeakPtr());
   CreateParallelJob(0, 100, download::DownloadItem::ReceivedSlices(), 2, 0, 0);
   job_->Start(download_file.get(),
@@ -504,7 +491,7 @@
               download::DownloadItem::ReceivedSlices());
   EXPECT_FALSE(file_initialized_);
   EXPECT_EQ(0u, job_->workers().size());
-  EXPECT_CALL(*input_stream, RegisterCallback(_));
+  EXPECT_CALL(*input_stream, RegisterDataReadyCallback(_));
   EXPECT_CALL(*input_stream, Read(_, _));
   EXPECT_CALL(*(observer.get()), DestinationUpdate(_, _, _));
   task_environment_.RunUntilIdle();
diff --git a/content/browser/download/parallel_download_utils_unittest.cc b/content/browser/download/parallel_download_utils_unittest.cc
index 53ac459d..8589dbda 100644
--- a/content/browser/download/parallel_download_utils_unittest.cc
+++ b/content/browser/download/parallel_download_utils_unittest.cc
@@ -11,47 +11,44 @@
 #include "base/test/scoped_feature_list.h"
 #include "components/download/public/common/download_features.h"
 #include "components/download/public/common/download_save_info.h"
-#include "content/browser/byte_stream.h"
-#include "content/browser/download/byte_stream_input_stream.h"
+#include "content/browser/download/mock_input_stream.h"
 #include "content/public/browser/download_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::Return;
+using ::testing::StrictMock;
+
 namespace content {
 
 namespace {
 
 const int kErrorStreamOffset = 100;
 
-class MockByteStreamReader : public ByteStreamReader {
- public:
-  MockByteStreamReader() {}
-  ~MockByteStreamReader() override {}
-
-  // ByteStream functions
-  MOCK_METHOD2(Read,
-               ByteStreamReader::StreamState(scoped_refptr<net::IOBuffer>*,
-                                             size_t*));
-  MOCK_CONST_METHOD0(GetStatus, int());
-  MOCK_METHOD1(RegisterCallback, void(const base::Closure&));
-};
-
-// Creates a source stream to test.
-std::unique_ptr<DownloadFileImpl::SourceStream> CreateSourceStream(
-    int64_t offset,
-    int64_t length) {
-  auto input_stream = std::make_unique<ByteStreamInputStream>(
-      std::make_unique<MockByteStreamReader>());
-  return std::make_unique<DownloadFileImpl::SourceStream>(
-      offset, length, std::move(input_stream));
-}
-
 }  // namespace
 
 class ParallelDownloadUtilsTest : public testing::Test {};
 
 class ParallelDownloadUtilsRecoverErrorTest
-    : public ::testing::TestWithParam<int64_t> {};
+    : public ::testing::TestWithParam<int64_t> {
+ public:
+  ParallelDownloadUtilsRecoverErrorTest() : input_stream_(nullptr) {}
+
+  // Creates a source stream to test.
+  std::unique_ptr<DownloadFileImpl::SourceStream> CreateSourceStream(
+      int64_t offset,
+      int64_t length) {
+    input_stream_ = new StrictMock<MockInputStream>();
+    EXPECT_CALL(*input_stream_, GetCompletionStatus())
+        .WillRepeatedly(Return(download::DOWNLOAD_INTERRUPT_REASON_NONE));
+    return std::make_unique<DownloadFileImpl::SourceStream>(
+        offset, length, std::unique_ptr<MockInputStream>(input_stream_));
+  }
+
+ protected:
+  // Stream for sending data into the SourceStream.
+  StrictMock<MockInputStream>* input_stream_;
+};
 
 TEST_F(ParallelDownloadUtilsTest, FindSlicesToDownload) {
   std::vector<download::DownloadItem::ReceivedSlice> downloaded_slices;
@@ -265,6 +262,7 @@
        RecoverErrorForHalfOpenErrorStream) {
   // Create a stream that will work on byte range "100-".
   const int kErrorStreamOffset = 100;
+
   auto error_stream = CreateSourceStream(
       kErrorStreamOffset, download::DownloadSaveInfo::kLengthFullContent);
   error_stream->set_finished(true);
@@ -287,8 +285,9 @@
 
   // Half open finished preceding stream with error, should be treated as
   // failed.
-  preceding_stream->OnResponseCompleted(
-      download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE);
+  EXPECT_CALL(*input_stream_, GetCompletionStatus())
+      .WillRepeatedly(
+          Return(download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE));
   EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
 
   // Even if it has written some data.
@@ -308,8 +307,9 @@
 
   // Inject an error results in failure, even if data written exceeds the first
   // byte of error stream.
-  preceding_stream->OnResponseCompleted(
-      download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE);
+  EXPECT_CALL(*input_stream_, GetCompletionStatus())
+      .WillRepeatedly(
+          Return(download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE));
   preceding_stream->OnWriteBytesToDisk(1000u);
   EXPECT_FALSE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
 
@@ -383,8 +383,9 @@
 
   // Even if inject an error, since data written has cover the upper bound of
   // the error stream, it should succeed.
-  preceding_stream->OnResponseCompleted(
-      download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE);
+  EXPECT_CALL(*input_stream_, GetCompletionStatus())
+      .WillRepeatedly(
+          Return(download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE));
   EXPECT_TRUE(CanRecoverFromError(error_stream.get(), preceding_stream.get()));
 
   // Preceding stream that never download data won't recover the error stream.
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 0df805d1..eae2d0c 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -1174,4 +1174,52 @@
   EXPECT_NE(subframe1->GetSiteInstance(), subframe2->GetSiteInstance());
 }
 
+// Ensure that --disable-site-isolation-trials disables field trials.
+class IsolatedOriginTrialOverrideTest : public IsolatedOriginFieldTrialTest {
+ public:
+  IsolatedOriginTrialOverrideTest() {}
+
+  ~IsolatedOriginTrialOverrideTest() override {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(switches::kDisableSiteIsolationTrials);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(IsolatedOriginTrialOverrideTest);
+};
+
+IN_PROC_BROWSER_TEST_F(IsolatedOriginTrialOverrideTest, Test) {
+  if (AreAllSitesIsolatedForTesting())
+    return;
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  EXPECT_FALSE(policy->IsIsolatedOrigin(
+      url::Origin::Create(GURL("https://field.trial.com/"))));
+  EXPECT_FALSE(
+      policy->IsIsolatedOrigin(url::Origin::Create(GURL("https://bar.com/"))));
+}
+
+// Ensure that --disable-site-isolation-trials does not override the flag.
+class IsolatedOriginNoFlagOverrideTest : public IsolatedOriginTest {
+ public:
+  IsolatedOriginNoFlagOverrideTest() {}
+
+  ~IsolatedOriginNoFlagOverrideTest() override {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    IsolatedOriginTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(switches::kDisableSiteIsolationTrials);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(IsolatedOriginNoFlagOverrideTest);
+};
+
+IN_PROC_BROWSER_TEST_F(IsolatedOriginNoFlagOverrideTest, Test) {
+  GURL isolated_url(
+      embedded_test_server()->GetURL("isolated.foo.com", "/title2.html"));
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  EXPECT_TRUE(policy->IsIsolatedOrigin(url::Origin::Create(isolated_url)));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc
index 19531ae..fbb803a 100644
--- a/content/browser/renderer_host/render_process_host_browsertest.cc
+++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -11,6 +11,7 @@
 #include "build/build_config.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_process_host_observer.h"
@@ -356,8 +357,8 @@
   base::WaitableEvent launcher_thread_done(
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED);
-  BrowserThread::PostTask(
-      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+  GetProcessLauncherTaskRunner()->PostTask(
+      FROM_HERE,
       base::BindOnce([](base::WaitableEvent* done) { done->Signal(); },
                      base::Unretained(&launcher_thread_done)));
   ASSERT_TRUE(launcher_thread_done.TimedWait(TestTimeouts::action_timeout()));
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index f7dfced4..2e9574b 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -8,6 +8,7 @@
 #include "base/logging.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
+#include "components/viz/host/host_frame_sink_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "components/viz/service/surfaces/surface_hittest.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
@@ -169,6 +170,15 @@
   std::move(callback).Run(SkBitmap());
 }
 
+viz::mojom::FrameSinkVideoCapturerPtr
+RenderWidgetHostViewBase::CreateVideoCapturer() {
+  viz::mojom::FrameSinkVideoCapturerPtr video_capturer;
+  GetHostFrameSinkManager()->CreateVideoCapturer(
+      mojo::MakeRequest(&video_capturer));
+  video_capturer->ChangeTarget(GetFrameSinkId());
+  return video_capturer;
+}
+
 base::string16 RenderWidgetHostViewBase::GetSelectedText() {
   if (!GetTextInputManager())
     return base::string16();
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index e40361b6..543ce74f 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -119,6 +119,7 @@
       const gfx::Rect& src_rect,
       const gfx::Size& output_size,
       base::OnceCallback<void(const SkBitmap&)> callback) override;
+  viz::mojom::FrameSinkVideoCapturerPtr CreateVideoCapturer() override;
   void FocusedNodeTouched(bool editable) override;
   void GetScreenInfo(ScreenInfo* screen_info) const override;
   float GetDeviceScaleFactor() const final;
diff --git a/content/browser/service_worker/service_worker_handle_unittest.cc b/content/browser/service_worker/service_worker_handle_unittest.cc
index e90991b..884959f 100644
--- a/content/browser/service_worker/service_worker_handle_unittest.cc
+++ b/content/browser/service_worker/service_worker_handle_unittest.cc
@@ -325,7 +325,9 @@
   EXPECT_EQ(remaining_time, version_->remaining_timeout());
 }
 
-TEST_F(ServiceWorkerHandleTest, DispatchExtendableMessageEvent_FromClient) {
+// Constantly fails on Android/Linux CFI builder.  http://crbug.com/820620
+TEST_F(ServiceWorkerHandleTest,
+       DISABLED_DispatchExtendableMessageEvent_FromClient) {
   const int64_t kProviderId = 99;
   const GURL pattern("https://www.example.com/");
   const GURL script_url("https://www.example.com/service_worker.js");
@@ -381,7 +383,8 @@
             events[0]->source_info_for_client->client_type);
 }
 
-TEST_F(ServiceWorkerHandleTest, DispatchExtendableMessageEvent_Fail) {
+// Constantly fails on Android/Linux CFI builder.  http://crbug.com/820620
+TEST_F(ServiceWorkerHandleTest, DISABLED_DispatchExtendableMessageEvent_Fail) {
   const int64_t kProviderId = 99;
   const GURL pattern("https://www.example.com/");
   const GURL script_url("https://www.example.com/service_worker.js");
diff --git a/content/browser/site_isolation_policy.cc b/content/browser/site_isolation_policy.cc
index 509c394..6f5cd31 100644
--- a/content/browser/site_isolation_policy.cc
+++ b/content/browser/site_isolation_policy.cc
@@ -28,7 +28,9 @@
 bool SiteIsolationPolicy::UseDedicatedProcessesForAllSites() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kSitePerProcess) ||
-         base::FeatureList::IsEnabled(features::kSitePerProcess);
+         (base::FeatureList::IsEnabled(features::kSitePerProcess) &&
+          !base::CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kDisableSiteIsolationTrials));
 }
 
 // static
@@ -59,7 +61,9 @@
 bool SiteIsolationPolicy::AreIsolatedOriginsEnabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kIsolateOrigins) ||
-         base::FeatureList::IsEnabled(features::kIsolateOrigins);
+         (base::FeatureList::IsEnabled(features::kIsolateOrigins) &&
+          !base::CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kDisableSiteIsolationTrials));
 }
 
 // static
@@ -76,7 +80,9 @@
     return cmdline_origins;
   }
 
-  if (base::FeatureList::IsEnabled(features::kIsolateOrigins)) {
+  if (base::FeatureList::IsEnabled(features::kIsolateOrigins) &&
+      !base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableSiteIsolationTrials)) {
     std::string field_trial_arg = base::GetFieldTrialParamValueByFeature(
         features::kIsolateOrigins,
         features::kIsolateOriginsFieldTrialParamName);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index b1b10828..04ca34e 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1677,6 +1677,10 @@
     observer.NavigationStopped();
 }
 
+void WebContentsImpl::FreezePage() {
+  SendPageMessage(new PageMsg_FreezePage(MSG_ROUTING_NONE));
+}
+
 WebContents* WebContentsImpl::Clone() {
   // We use our current SiteInstance since the cloned entry will use it anyway.
   // We pass our own opener so that the cloned page can access it if it was set
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 4cb3b5df..d0da3b51 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -374,6 +374,7 @@
       RenderFrameHost* outer_contents_frame) override;
   void DidChangeVisibleSecurityState() override;
   void Stop() override;
+  void FreezePage() override;
   WebContents* Clone() override;
   void ReloadFocusedFrame(bool bypass_cache) override;
   void Undo() override;
diff --git a/content/common/page_messages.h b/content/common/page_messages.h
index 423a9f1..ca7f0b6e 100644
--- a/content/common/page_messages.h
+++ b/content/common/page_messages.h
@@ -50,6 +50,10 @@
 IPC_MESSAGE_ROUTED1(PageMsg_UpdateScreenInfo,
                     content::ScreenInfo /* screen_info */)
 
+// Sent to all renderers, instructing them to freeze all frames that belongs to
+// this page.
+IPC_MESSAGE_ROUTED0(PageMsg_FreezePage)
+
 // -----------------------------------------------------------------------------
 // Messages sent from the renderer to the browser.
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/LauncherThread.java b/content/public/android/java/src/org/chromium/content/browser/LauncherThread.java
index e3e6ffd91..dd98f13 100644
--- a/content/public/android/java/src/org/chromium/content/browser/LauncherThread.java
+++ b/content/public/android/java/src/org/chromium/content/browser/LauncherThread.java
@@ -12,7 +12,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 
-/** This is BrowserThread::PROCESS_LAUNCHER. It is available before native library is loaded. */
+/** This is the process launcher thread. It is available before native library is loaded. */
 @JNINamespace("content::android")
 public final class LauncherThread {
     private static final JavaHandlerThread sThread =
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index c5defd4..cd6941d 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -84,6 +84,7 @@
     "cdm_registry.h",
     "certificate_request_result_type.h",
     "child_process_data.h",
+    "child_process_launcher_utils.h",
     "child_process_security_policy.h",
     "client_certificate_delegate.h",
     "color_chooser.h",
diff --git a/content/public/browser/browser_thread.h b/content/public/browser/browser_thread.h
index a4bdf19..a9d6b08 100644
--- a/content/public/browser/browser_thread.h
+++ b/content/public/browser/browser_thread.h
@@ -67,9 +67,6 @@
     // The main thread in the browser.
     UI,
 
-    // Used to launch and terminate Chrome processes.
-    PROCESS_LAUNCHER,
-
     // This is the thread that processes non-blocking IO, i.e. IPC and network.
     // Blocking IO should happen in TaskScheduler.
     IO,
diff --git a/content/public/browser/child_process_launcher_utils.h b/content/public/browser/child_process_launcher_utils.h
new file mode 100644
index 0000000..1b9d4545
--- /dev/null
+++ b/content/public/browser/child_process_launcher_utils.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_CHILD_PROCESS_LAUNCHER_UTILS_H_
+#define CONTENT_PUBLIC_BROWSER_CHILD_PROCESS_LAUNCHER_UTILS_H_
+
+#include "content/common/content_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace content {
+
+// The caller must take a reference to the returned TaskRunner pointer if it
+// wants to use the pointer directly.
+CONTENT_EXPORT base::SingleThreadTaskRunner* GetProcessLauncherTaskRunner();
+
+CONTENT_EXPORT bool CurrentlyOnProcessLauncherTaskRunner();
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_CHILD_PROCESS_LAUNCHER_UTILES_H_
diff --git a/content/public/browser/render_widget_host_view.h b/content/public/browser/render_widget_host_view.h
index ffcf44f..f2edd26 100644
--- a/content/public/browser/render_widget_host_view.h
+++ b/content/public/browser/render_widget_host_view.h
@@ -22,10 +22,22 @@
 class Size;
 }
 
+namespace mojo {
+template <class T>
+class InterfacePtr;
+}
+
 namespace ui {
 class TextInputClient;
 }
 
+namespace viz {
+namespace mojom {
+class FrameSinkVideoCapturer;
+using FrameSinkVideoCapturerPtr = mojo::InterfacePtr<FrameSinkVideoCapturer>;
+}  // namespace mojom
+}  // namespace viz
+
 namespace content {
 
 class RenderWidgetHost;
@@ -167,7 +179,7 @@
   // Copies the given subset of the view's surface, optionally scales it, and
   // returns the result as a bitmap via the provided callback. This is meant for
   // one-off snapshots. For continuous video capture of the surface, please use
-  // viz::FrameSinkManager::CreateVideoCapturer() instead.
+  // CreateVideoCapturer() instead.
   //
   // |src_rect| is either the subset of the view's surface, in view coordinates,
   // or empty to indicate that all of it should be copied. This is NOT the same
@@ -190,6 +202,12 @@
       const gfx::Size& output_size,
       base::OnceCallback<void(const SkBitmap&)> callback) = 0;
 
+  // Creates a video capturer, which will allow the caller to receive a stream
+  // of media::VideoFrames captured from this view. The capturer is configured
+  // to target this view, so there is no need to call ChangeTarget() before
+  // Start(). See viz.mojom.FrameSinkVideoCapturer for documentation.
+  virtual viz::mojom::FrameSinkVideoCapturerPtr CreateVideoCapturer() = 0;
+
   // Notification that a node was touched.
   // The |editable| parameter indicates if the node is editable, for e.g.
   // an input field, etc.
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 9c6643a..b74438d7 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -480,6 +480,9 @@
   // Stop any pending navigation.
   virtual void Stop() = 0;
 
+  // Freeze the current page.
+  virtual void FreezePage() = 0;
+
   // Creates a new WebContents with the same state as this one. The returned
   // heap-allocated pointer is owned by the caller.
   virtual WebContents* Clone() = 0;
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index fcfaebc..63bcb877 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -247,10 +247,6 @@
 const base::Feature kPassiveEventListenersDueToFling{
     "PassiveEventListenersDueToFling", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Whether PDF files should be rendered in diffent processes based on origin.
-const base::Feature kPdfIsolation = {"PdfIsolation",
-                                     base::FEATURE_DISABLED_BY_DEFAULT};
-
 // If Pepper 3D Image Chromium is allowed, this feature controls whether it is
 // enabled.
 const base::Feature kPepper3DImageChromium {
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index a36d645..5f9db31 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -66,7 +66,6 @@
 CONTENT_EXPORT extern const base::Feature kOriginTrials;
 CONTENT_EXPORT extern const base::Feature kPassiveDocumentEventListeners;
 CONTENT_EXPORT extern const base::Feature kPassiveEventListenersDueToFling;
-CONTENT_EXPORT extern const base::Feature kPdfIsolation;
 CONTENT_EXPORT extern const base::Feature kPepper3DImageChromium;
 CONTENT_EXPORT extern const base::Feature kPurgeAndSuspend;
 CONTENT_EXPORT extern const base::Feature kPWAFullCodeCache;
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index 30188c1..4f1dc51 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -772,6 +772,9 @@
 // process consolidation, not isolation). You probably want this one.
 const char kSitePerProcess[]                = "site-per-process";
 
+// Disables enabling site isolation (i.e., --site-per-process) via field trial.
+const char kDisableSiteIsolationTrials[] = "disable-site-isolation-trials";
+
 // Specifies if the browser should start in fullscreen mode, like if the user
 // had pressed F11 right after startup.
 const char kStartFullscreen[] = "start-fullscreen";
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 855329f..b48a297 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -214,6 +214,7 @@
 extern const char kShowPaintRects[];
 CONTENT_EXPORT extern const char kSingleProcess[];
 CONTENT_EXPORT extern const char kSitePerProcess[];
+CONTENT_EXPORT extern const char kDisableSiteIsolationTrials[];
 CONTENT_EXPORT extern const char kStartFullscreen[];
 CONTENT_EXPORT extern const char kStatsCollectionController[];
 CONTENT_EXPORT extern const char kTestType[];
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 4566c564..4569941 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -173,11 +173,6 @@
   return false;
 }
 
-bool ContentRendererClient::IsOriginIsolatedPepperPlugin(
-    const base::FilePath& plugin_path) {
-  return false;
-}
-
 bool ContentRendererClient::AllowPepperMediaStreamAPI(const GURL& url) {
   return false;
 }
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index f663393a..c5da6a40 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -13,7 +13,6 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
 #include "base/strings/string16.h"
 #include "base/task_scheduler/task_scheduler.h"
@@ -262,12 +261,6 @@
   // startup steps).
   virtual bool IsExternalPepperPlugin(const std::string& module_name);
 
-  // Returns true if the given Pepper plugin should process content from
-  // different origins in different PPAPI processes. This is generally a
-  // worthwhile precaution when the plugin provides an active scripting
-  // language.
-  virtual bool IsOriginIsolatedPepperPlugin(const base::FilePath& plugin_path);
-
   // Returns true if the page at |url| can use Pepper MediaStream APIs.
   virtual bool AllowPepperMediaStreamAPI(const GURL& url);
 
diff --git a/content/public/test/test_browser_thread_bundle.cc b/content/public/test/test_browser_thread_bundle.cc
index 05b943db..8dfb2e4 100644
--- a/content/public/test/test_browser_thread_bundle.cc
+++ b/content/public/test/test_browser_thread_bundle.cc
@@ -39,8 +39,6 @@
   base::RunLoop().RunUntilIdle();
   io_thread_->Stop();
   base::RunLoop().RunUntilIdle();
-  process_launcher_thread_->Stop();
-  base::RunLoop().RunUntilIdle();
   ui_thread_->Stop();
   base::RunLoop().RunUntilIdle();
 
@@ -116,9 +114,6 @@
 void TestBrowserThreadBundle::CreateBrowserThreads() {
   CHECK(!threads_created_);
 
-  process_launcher_thread_ = std::make_unique<TestBrowserThread>(
-      BrowserThread::PROCESS_LAUNCHER, base::MessageLoop::current());
-
   if (options_ & REAL_IO_THREAD) {
     io_thread_ = std::make_unique<TestBrowserThread>(BrowserThread::IO);
     io_thread_->StartIOThread();
diff --git a/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.cc b/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.cc
index d88e5fe..ad87e7a 100644
--- a/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.cc
+++ b/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.cc
@@ -40,7 +40,7 @@
   return it;
 };
 
-MockMediaStream::MockMediaStream(const std::string& label) : label_(label) {}
+MockMediaStream::MockMediaStream(const std::string& id) : id_(id) {}
 
 bool MockMediaStream::AddTrack(AudioTrackInterface* track) {
   audio_track_vector_.push_back(track);
@@ -74,8 +74,8 @@
   return true;
 }
 
-std::string MockMediaStream::label() const {
-  return label_;
+std::string MockMediaStream::id() const {
+  return id_;
 }
 
 AudioTrackVector MockMediaStream::GetAudioTracks() {
diff --git a/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h b/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h
index 7a59178..0d1724d 100644
--- a/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h
+++ b/content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h
@@ -86,13 +86,13 @@
 
 class MockMediaStream : public webrtc::MediaStreamInterface {
  public:
-  explicit MockMediaStream(const std::string& label);
+  explicit MockMediaStream(const std::string& id);
 
   bool AddTrack(webrtc::AudioTrackInterface* track) override;
   bool AddTrack(webrtc::VideoTrackInterface* track) override;
   bool RemoveTrack(webrtc::AudioTrackInterface* track) override;
   bool RemoveTrack(webrtc::VideoTrackInterface* track) override;
-  std::string label() const override;
+  std::string id() const override;
   webrtc::AudioTrackVector GetAudioTracks() override;
   webrtc::VideoTrackVector GetVideoTracks() override;
   rtc::scoped_refptr<webrtc::AudioTrackInterface> FindAudioTrack(
@@ -108,7 +108,7 @@
  private:
   void NotifyObservers();
 
-  std::string label_;
+  std::string id_;
   webrtc::AudioTrackVector audio_track_vector_;
   webrtc::VideoTrackVector video_track_vector_;
 
diff --git a/content/renderer/media/webrtc/mock_peer_connection_impl.cc b/content/renderer/media/webrtc/mock_peer_connection_impl.cc
index 87fb40a..ab80a8a0 100644
--- a/content/renderer/media/webrtc/mock_peer_connection_impl.cc
+++ b/content/renderer/media/webrtc/mock_peer_connection_impl.cc
@@ -31,9 +31,9 @@
  public:
   size_t count() override { return streams_.size(); }
   MediaStreamInterface* at(size_t index) override { return streams_[index]; }
-  MediaStreamInterface* find(const std::string& label) override {
+  MediaStreamInterface* find(const std::string& id) override {
     for (size_t i = 0; i < streams_.size(); ++i) {
-      if (streams_[i]->label() == label)
+      if (streams_[i]->id() == id)
         return streams_[i];
     }
     return nullptr;
@@ -277,8 +277,8 @@
       return nullptr;
   }
   for (auto* stream : streams) {
-    if (!local_streams_->find(stream->label())) {
-      stream_label_ = stream->label();
+    if (!local_streams_->find(stream->id())) {
+      stream_label_ = stream->id();
       local_streams_->AddStream(stream);
     }
   }
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index f87af36..49db083 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2854,11 +2854,8 @@
         this, delegate->GetWeakPtr());
   }
 
+  // TODO(tsepez): extract origin to lock from WebPluginParams url.
   base::Optional<url::Origin> origin_lock;
-  if (base::FeatureList::IsEnabled(features::kPdfIsolation) &&
-      GetContentClient()->renderer()->IsOriginIsolatedPepperPlugin(info.path)) {
-    origin_lock = url::Origin::Create(GURL(params.url));
-  }
 
   bool pepper_plugin_was_registered = false;
   scoped_refptr<PluginModule> pepper_module(PluginModule::Create(
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 02d0c37..55120e6 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1133,6 +1133,7 @@
                         OnSetHistoryOffsetAndLength)
     IPC_MESSAGE_HANDLER(PageMsg_AudioStateChanged, OnAudioStateChanged)
     IPC_MESSAGE_HANDLER(PageMsg_UpdateScreenInfo, OnUpdateScreenInfo)
+    IPC_MESSAGE_HANDLER(PageMsg_FreezePage, OnFreezePage)
 
 #if defined(OS_MACOSX)
     IPC_MESSAGE_HANDLER(ViewMsg_GetRenderedText,
@@ -2187,6 +2188,12 @@
     screen_info_ = screen_info;
 }
 
+void RenderViewImpl::OnFreezePage() {
+  if (webview()) {
+    webview()->FreezePage();
+  }
+}
+
 GURL RenderViewImpl::GetURLForGraphicsContext3D() {
   DCHECK(webview());
   WebFrame* main_frame = webview()->MainFrame();
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 0199552..0e1fb3c 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -552,6 +552,7 @@
   void OnPageWasHidden();
   void OnPageWasShown();
   void OnUpdateScreenInfo(const ScreenInfo& screen_info);
+  void OnFreezePage();
 
   // Adding a new message handler? Please add it in alphabetical order above
   // and put it in the same position in the .cc file.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 1708eeb0..45360f9 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -50,6 +50,8 @@
     "../browser/download/mock_download_item_impl.h",
     "../browser/download/mock_download_job.cc",
     "../browser/download/mock_download_job.h",
+    "../browser/download/mock_input_stream.cc",
+    "../browser/download/mock_input_stream.h",
     "../browser/media/session/mock_media_session_observer.cc",
     "../browser/media/session/mock_media_session_observer.h",
     "../browser/service_worker/embedded_worker_test_helper.cc",
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index eec4730..3db0800 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -243,6 +243,8 @@
     # Win / Intel
     self.Fail('conformance/rendering/rendering-stencil-large-viewport.html',
         ['win', 'intel', 'd3d11'], bug=782317)
+    self.Fail('conformance2/textures/misc/angle-stuck-depth-textures.html',
+        ['win', 'intel', 'd3d11'], bug=820419)
 
     # Seems to cause the harness to fail immediately afterward
     self.Skip('conformance2/textures/video/tex-2d-rgba16f-rgba-half_float.html',
@@ -412,6 +414,9 @@
         ['mac', 'nvidia', 'intel'], bug=630800)
     self.Fail('deqp/functional/gles3/fbocompleteness.html',
         ['mac', 'nvidia', 'intel'], bug=630800)
+    self.Fail('deqp/functional/gles3/negativeshaderapi.html',
+        ['mac', 'amd', 'intel'], bug=811614)
+
 
     # Mac Retina NVIDIA
     self.Fail('deqp/functional/gles3/shaderindexing/mat_01.html',
diff --git a/device/vr/oculus/oculus_render_loop.cc b/device/vr/oculus/oculus_render_loop.cc
index e820f05..51e47a2 100644
--- a/device/vr/oculus/oculus_render_loop.cc
+++ b/device/vr/oculus/oculus_render_loop.cc
@@ -5,6 +5,7 @@
 #include "device/vr/oculus/oculus_render_loop.h"
 
 #include "device/vr/oculus/oculus_type_converters.h"
+#include "third_party/libovr/src/Include/Extras/OVR_Math.h"
 #include "third_party/libovr/src/Include/OVR_CAPI.h"
 #include "third_party/libovr/src/Include/OVR_CAPI_D3D.h"
 #include "ui/gfx/geometry/angle_conversions.h"
@@ -14,6 +15,32 @@
 #endif
 
 namespace device {
+
+namespace {
+
+// How far the index trigger needs to be pushed to be considered "pressed".
+const float kTriggerPressedThreshold = 0.6f;
+
+// WebXR reports a pointer pose separate from the grip pose, which represents a
+// pointer ray emerging from the tip of the controller. Oculus does not report
+// anything like that, but the pose they report matches WebXR's idea of the
+// pointer pose more than grip. For consistency with other WebXR backends we
+// apply a rotation and slight translation to the reported pose to get the grip
+// pose. Experimentally determined, should roughly place the grip pose origin at
+// the center of the Oculus Touch handle.
+const float kGripRotationXDelta = 45.0f;
+const float kGripOffsetZMeters = 0.025f;
+
+gfx::Transform PoseToTransform(const ovrPosef& pose) {
+  OVR::Matrix4f mat(pose);
+  return gfx::Transform(mat.M[0][0], mat.M[0][1], mat.M[0][2], mat.M[0][3],
+                        mat.M[1][0], mat.M[1][1], mat.M[1][2], mat.M[1][3],
+                        mat.M[2][0], mat.M[2][1], mat.M[2][2], mat.M[2][3],
+                        mat.M[3][0], mat.M[3][1], mat.M[3][2], mat.M[3][3]);
+}
+
+}  // namespace
+
 OculusRenderLoop::OculusRenderLoop(ovrSession session, ovrGraphicsLuid luid)
     : base::Thread("OculusRenderLoop"),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
@@ -177,6 +204,8 @@
   // able to safely ignore ones that our implementation doesn't care about.
   transport_options->wait_for_transfer_notification = true;
 
+  report_webxr_input_ = present_options->webxr_input;
+
   main_thread_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), true, std::move(transport_options)));
@@ -195,8 +224,12 @@
 
 void OculusRenderLoop::GetVSync(
     mojom::VRPresentationProvider::GetVSyncCallback callback) {
-  int16_t frame = next_frame_id_++;
   DCHECK(is_presenting_);
+  int16_t frame = next_frame_id_;
+  next_frame_id_ += 1;
+  if (next_frame_id_ < 0) {
+    next_frame_id_ = 0;
+  }
 
   auto predicted_time =
       ovr_GetPredictedDisplayTime(session_, ovr_frame_index_ + 1);
@@ -206,10 +239,124 @@
       mojo::ConvertTo<mojom::VRPosePtr>(state.HeadPose.ThePose);
   last_render_pose_ = state.HeadPose.ThePose;
 
+  if (pose && report_webxr_input_) {
+    pose->input_state = GetInputState(state);
+  }
+
   base::TimeDelta time = base::TimeDelta::FromSecondsD(predicted_time);
 
   std::move(callback).Run(std::move(pose), time, frame,
                           mojom::VRPresentationProvider::VSyncStatus::SUCCESS);
 }
 
+std::vector<mojom::XRInputSourceStatePtr> OculusRenderLoop::GetInputState(
+    const ovrTrackingState& tracking_state) {
+  std::vector<mojom::XRInputSourceStatePtr> input_states;
+
+  ovrInputState input_state;
+
+  // Get the state of the active controllers.
+  if ((OVR_SUCCESS(ovr_GetInputState(session_, ovrControllerType_Active,
+                                     &input_state)))) {
+    // Report whichever touch controllers are currently active.
+    if (input_state.ControllerType & ovrControllerType_LTouch) {
+      input_states.push_back(GetTouchData(
+          ovrControllerType_LTouch, tracking_state.HandPoses[ovrHand_Left],
+          input_state, ovrHand_Left));
+    } else {
+      primary_input_pressed[ovrControllerType_LTouch] = false;
+    }
+
+    if (input_state.ControllerType & ovrControllerType_RTouch) {
+      input_states.push_back(GetTouchData(
+          ovrControllerType_RTouch, tracking_state.HandPoses[ovrHand_Right],
+          input_state, ovrHand_Right));
+    } else {
+      primary_input_pressed[ovrControllerType_RTouch] = false;
+    }
+
+    // If an oculus remote is active, report a gaze controller.
+    if (input_state.ControllerType & ovrControllerType_Remote) {
+      device::mojom::XRInputSourceStatePtr state =
+          device::mojom::XRInputSourceState::New();
+
+      state->source_id = ovrControllerType_Remote;
+      state->primary_input_pressed =
+          (input_state.Buttons & ovrButton_Enter) != 0;
+
+      if (!state->primary_input_pressed &&
+          primary_input_pressed[ovrControllerType_Remote]) {
+        state->primary_input_clicked = true;
+      }
+
+      primary_input_pressed[ovrControllerType_Remote] =
+          state->primary_input_pressed;
+
+      input_states.push_back(std::move(state));
+    } else {
+      primary_input_pressed[ovrControllerType_Remote] = false;
+    }
+  }
+
+  return input_states;
+}
+
+device::mojom::XRInputSourceStatePtr OculusRenderLoop::GetTouchData(
+    ovrControllerType type,
+    const ovrPoseStatef& pose,
+    const ovrInputState& input_state,
+    ovrHandType hand) {
+  device::mojom::XRInputSourceStatePtr state =
+      device::mojom::XRInputSourceState::New();
+
+  state->source_id = type;
+  state->primary_input_pressed =
+      (input_state.IndexTrigger[hand] > kTriggerPressedThreshold);
+
+  // If the input has gone from pressed to not pressed since the last poll
+  // report it as clicked.
+  if (!state->primary_input_pressed && primary_input_pressed[type])
+    state->primary_input_clicked = true;
+
+  primary_input_pressed[type] = state->primary_input_pressed;
+
+  device::mojom::XRInputSourceDescriptionPtr desc =
+      device::mojom::XRInputSourceDescription::New();
+
+  // It's a handheld pointing device.
+  desc->pointer_origin = device::mojom::XRPointerOrigin::HAND;
+
+  // Set handedness.
+  switch (hand) {
+    case ovrHand_Left:
+      desc->handedness = device::mojom::XRHandedness::LEFT;
+      break;
+    case ovrHand_Right:
+      desc->handedness = device::mojom::XRHandedness::RIGHT;
+      break;
+    default:
+      desc->handedness = device::mojom::XRHandedness::NONE;
+      break;
+  }
+
+  // Touch controller are fully 6DoF.
+  desc->emulated_position = false;
+
+  // The grip pose will be rotated and translated back a bit from the pointer
+  // pose, which is what the Oculus API returns.
+  state->grip = PoseToTransform(pose.ThePose);
+  state->grip->RotateAboutXAxis(kGripRotationXDelta);
+  state->grip->Translate3d(0, 0, kGripOffsetZMeters);
+
+  // Need to apply the inverse transform from above to put the pointer back in
+  // the right orientation relative to the grip.
+  desc->pointer_offset = gfx::Transform();
+  desc->pointer_offset->Translate3d(0, 0, -kGripOffsetZMeters);
+  desc->pointer_offset->RotateAboutXAxis(-kGripRotationXDelta);
+
+  state->description = std::move(desc);
+
+  return state;
+}
+
 }  // namespace device
diff --git a/device/vr/oculus/oculus_render_loop.h b/device/vr/oculus/oculus_render_loop.h
index 93ff7422..47af6a69 100644
--- a/device/vr/oculus/oculus_render_loop.h
+++ b/device/vr/oculus/oculus_render_loop.h
@@ -20,6 +20,8 @@
 
 namespace device {
 
+const int kMaxOculusRenderLoopInputId = (ovrControllerType_Remote + 1);
+
 class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider {
  public:
   OculusRenderLoop(ovrSession session, ovrGraphicsLuid luid);
@@ -52,6 +54,15 @@
 
   mojom::VRPosePtr GetPose();
 
+  std::vector<mojom::XRInputSourceStatePtr> GetInputState(
+      const ovrTrackingState& tracking_state);
+
+  device::mojom::XRInputSourceStatePtr GetTouchData(
+      ovrControllerType type,
+      const ovrPoseStatef& pose,
+      const ovrInputState& input_state,
+      ovrHandType hand);
+
 #if defined(OS_WIN)
   D3D11TextureHelper texture_helper_;
 #endif
@@ -70,6 +81,9 @@
   ovrTextureSwapChain texture_swap_chain_ = 0;
   double sensor_time_;
   mojo::Binding<mojom::VRPresentationProvider> binding_;
+  bool report_webxr_input_ = false;
+  bool primary_input_pressed[kMaxOculusRenderLoopInputId];
+
   base::WeakPtrFactory<OculusRenderLoop> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(OculusRenderLoop);
diff --git a/device/vr/openvr/openvr_render_loop.cc b/device/vr/openvr/openvr_render_loop.cc
index f379ca73f..337eac3 100644
--- a/device/vr/openvr/openvr_render_loop.cc
+++ b/device/vr/openvr/openvr_render_loop.cc
@@ -169,8 +169,12 @@
 
 void OpenVRRenderLoop::GetVSync(
     mojom::VRPresentationProvider::GetVSyncCallback callback) {
-  int16_t frame = next_frame_id_++;
   DCHECK(is_presenting_);
+  int16_t frame = next_frame_id_;
+  next_frame_id_ += 1;
+  if (next_frame_id_ < 0) {
+    next_frame_id_ = 0;
+  }
 
   mojom::VRPosePtr pose = GetPose();
 
diff --git a/device/vr/orientation/orientation_device.cc b/device/vr/orientation/orientation_device.cc
index 67f93f9..5c826c2 100644
--- a/device/vr/orientation/orientation_device.cc
+++ b/device/vr/orientation/orientation_device.cc
@@ -54,8 +54,11 @@
     mojom::SensorProviderPtr* sensor_provider,
     base::OnceClosure ready_callback)
     : ready_callback_(std::move(ready_callback)), binding_(this) {
+  // Use RELATIVE_ORIENTATION_QUATERNION rather than
+  // ABSOLUTE_ORIENTATION_QUATERNION because compass readings can be innacurate
+  // when used indoors.
   (*sensor_provider)
-      ->GetSensor(SensorType::ABSOLUTE_ORIENTATION_QUATERNION,
+      ->GetSensor(SensorType::RELATIVE_ORIENTATION_QUATERNION,
                   base::BindOnce(&VROrientationDevice::SensorReady,
                                  base::Unretained(this)));
 
diff --git a/device/vr/orientation/orientation_device_provider_unittest.cc b/device/vr/orientation/orientation_device_provider_unittest.cc
index e4db0d5..14757b5 100644
--- a/device/vr/orientation/orientation_device_provider_unittest.cc
+++ b/device/vr/orientation/orientation_device_provider_unittest.cc
@@ -70,7 +70,7 @@
     auto init_params = mojom::SensorInitParams::New();
     init_params->sensor = std::move(sensor_ptr_);
     init_params->default_configuration = PlatformSensorConfiguration(
-        SensorTraits<mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION>::
+        SensorTraits<mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION>::
             kDefaultFrequency);
 
     init_params->client_request = mojo::MakeRequest(&sensor_client_ptr_);
@@ -79,7 +79,7 @@
         mojo::SharedBufferHandle::AccessMode::READ_ONLY);
 
     init_params->buffer_offset = SensorReadingSharedBuffer::GetOffset(
-        mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION);
+        mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION);
 
     return init_params;
   }
diff --git a/device/vr/orientation/orientation_device_unittest.cc b/device/vr/orientation/orientation_device_unittest.cc
index 1643bb7..d360d89d 100644
--- a/device/vr/orientation/orientation_device_unittest.cc
+++ b/device/vr/orientation/orientation_device_unittest.cc
@@ -97,7 +97,7 @@
 
   double GetBufferOffset() {
     return SensorReadingSharedBuffer::GetOffset(
-        mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION);
+        mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION);
   }
 
   void InitializeDevice(mojom::SensorInitParamsPtr params) {
@@ -150,7 +150,7 @@
     auto init_params = mojom::SensorInitParams::New();
     init_params->sensor = std::move(sensor_ptr_);
     init_params->default_configuration = PlatformSensorConfiguration(
-        SensorTraits<mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION>::
+        SensorTraits<mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION>::
             kDefaultFrequency);
 
     init_params->client_request = mojo::MakeRequest(&sensor_client_ptr_);
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn
index a071e57..bf9ba62 100644
--- a/extensions/BUILD.gn
+++ b/extensions/BUILD.gn
@@ -223,7 +223,6 @@
     "//extensions/common:unit_tests",
     "//extensions/renderer:unit_tests",
     "//extensions/shell:unit_tests",
-    "//extensions/utility:unit_tests",
     "//services/data_decoder:lib",
     "//services/service_manager/public/cpp/test:test_support",
     "//ui/gl:test_support",
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 28da43b..8dc8564 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -615,6 +615,8 @@
     "//components/pref_registry:pref_registry",
     "//components/prefs:test_support",
     "//components/sync_preferences:test_support",
+    "//components/unzip_service:lib",
+    "//components/unzip_service/public/cpp:test_support",
     "//components/update_client",
     "//components/url_matcher",
     "//components/user_prefs",
@@ -629,8 +631,10 @@
     "//extensions/strings",
     "//ipc:test_support",
     "//net:test_support",
+    "//services/data_decoder:lib",
     "//services/data_decoder/public/cpp:test_support",
     "//services/device/public/mojom",
+    "//services/service_manager/public/cpp/test:test_support",
     "//storage/browser:test_support",
     "//third_party/leveldatabase",
     "//third_party/zlib/google:zip",
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS
index 3d5d48d..5d4816a 100644
--- a/extensions/browser/DEPS
+++ b/extensions/browser/DEPS
@@ -8,6 +8,7 @@
   "+components/sync",
   "+components/sync_preferences",
   "+components/update_client",
+  "+components/unzip_service/public",
   "+components/variations",
   "+components/version_info",
   "+components/web_cache",
@@ -56,4 +57,8 @@
     "+chrome/test/base/testing_profile.h",
     "+chrome/test/base/ui_test_utils.h",
   ],
+  "sandboxed_unpacker_unittest.cc": [
+    "+components/unzip_service",
+    "+services/data_decoder",
+  ],
 }
diff --git a/extensions/browser/sandboxed_unpacker.cc b/extensions/browser/sandboxed_unpacker.cc
index afec71d..54ee0ebc 100644
--- a/extensions/browser/sandboxed_unpacker.cc
+++ b/extensions/browser/sandboxed_unpacker.cc
@@ -25,15 +25,16 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "components/crx_file/crx_verifier.h"
+#include "components/unzip_service/public/cpp/unzip.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/api/declarative_net_request/utils.h"
 #include "extensions/browser/extension_file_task_runner.h"
+#include "extensions/browser/zipfile_installer.h"
 #include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_l10n_util.h"
 #include "extensions/common/extension_resource_path_normalizer.h"
-#include "extensions/common/extension_unpacker.mojom.h"
 #include "extensions/common/extension_utility_types.h"
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/features/feature_channel.h"
@@ -233,6 +234,9 @@
   CHECK_GT(location, Manifest::INVALID_LOCATION);
   CHECK_LT(location, Manifest::NUM_LOCATIONS);
 
+  // The connector should not be bound to any thread yet.
+  DCHECK(!connector_->IsBound());
+
   // Use a random instance ID to guarantee the connection is to a new data
   // decoder service (running in its own process).
   data_decoder_identity_ = service_manager::Identity(
@@ -323,9 +327,20 @@
   PATH_LENGTH_HISTOGRAM("Extensions.SandboxUnpackLinkFreeCrxPathLength",
                         link_free_crx_path);
 
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::BindOnce(&SandboxedUnpacker::Unzip, this, link_free_crx_path));
+  // Make sure to create the directory where the extension will be unzipped, as
+  // the unzipper service requires it.
+  base::FilePath unzipped_dir =
+      link_free_crx_path.DirName().AppendASCII(kTempExtensionName);
+  base::File::Error error;
+  if (!base::CreateDirectoryAndGetError(unzipped_dir, &error)) {
+    LOG(ERROR) << "Failed to created directory " << unzipped_dir.value()
+               << " with error " << error;
+    ReportFailure(UNZIP_FAILED,
+                  l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
+    return;
+  }
+
+  Unzip(link_free_crx_path, unzipped_dir);
 }
 
 void SandboxedUnpacker::StartWithDirectory(const std::string& extension_id,
@@ -348,8 +363,8 @@
     return;
   }
 
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
+  unpacker_io_task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&SandboxedUnpacker::Unpack, this, extension_root_));
 }
 
@@ -373,63 +388,23 @@
     unpacker_io_task_runner_->DeleteSoon(FROM_HERE, std::move(connector_));
 }
 
-void SandboxedUnpacker::StartUtilityProcessIfNeeded() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-
-  if (utility_process_mojo_client_)
-    return;
-
-  utility_process_mojo_client_ = std::make_unique<
-      content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>(
-      l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME));
-  utility_process_mojo_client_->set_error_callback(
-      base::Bind(&SandboxedUnpacker::UtilityProcessCrashed, this));
-
-  utility_process_mojo_client_->set_exposed_directory(temp_dir_.GetPath());
-
-  utility_process_mojo_client_->Start();
-}
-
-void SandboxedUnpacker::UtilityProcessCrashed() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-
-  utility_process_mojo_client_.reset();
-
-  unpacker_io_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &SandboxedUnpacker::ReportFailure, this,
-          UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL,
-          l10n_util::GetStringFUTF16(
-              IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
-              ASCIIToUTF16("UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL")) +
-              ASCIIToUTF16(". ") +
-              l10n_util::GetStringUTF16(
-                  IDS_EXTENSION_INSTALL_PROCESS_CRASHED)));
-}
-
-void SandboxedUnpacker::Unzip(const base::FilePath& crx_path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  StartUtilityProcessIfNeeded();
+void SandboxedUnpacker::Unzip(const base::FilePath& crx_path,
+                              const base::FilePath& unzipped_dir) {
+  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
 
   DCHECK(crx_path.DirName() == temp_dir_.GetPath());
-  base::FilePath unzipped_dir =
-      crx_path.DirName().AppendASCII(kTempExtensionName);
 
-  utility_process_mojo_client_->service()->Unzip(
-      crx_path, unzipped_dir,
-      base::BindOnce(&SandboxedUnpacker::UnzipDone, this, unzipped_dir));
+  ZipFileInstaller::Create(connector_.get(),
+                           base::BindOnce(&SandboxedUnpacker::UnzipDone, this))
+      ->LoadFromZipFileInDir(crx_path, unzipped_dir);
 }
 
-void SandboxedUnpacker::UnzipDone(const base::FilePath& directory,
-                                  bool success) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+void SandboxedUnpacker::UnzipDone(const base::FilePath& zip_file,
+                                  const base::FilePath& unzip_dir,
+                                  const std::string& error) {
+  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
 
-  utility_process_mojo_client_.reset();
-
-  if (!success) {
-    utility_process_mojo_client_.reset();
+  if (!error.empty()) {
     unpacker_io_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
@@ -438,22 +413,18 @@
     return;
   }
 
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::BindOnce(&SandboxedUnpacker::Unpack, this, directory));
+  Unpack(unzip_dir);
 }
 
 void SandboxedUnpacker::Unpack(const base::FilePath& directory) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
 
   DCHECK(directory.DirName() == temp_dir_.GetPath());
 
   base::FilePath manifest_path = extension_root_.Append(kManifestFilename);
-  unpacker_io_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &SandboxedUnpacker::ParseJsonFile, this, manifest_path,
-          base::BindOnce(&SandboxedUnpacker::ReadManifestDone, this)));
+
+  ParseJsonFile(manifest_path,
+                base::BindOnce(&SandboxedUnpacker::ReadManifestDone, this));
 }
 
 void SandboxedUnpacker::ReadManifestDone(
@@ -607,6 +578,12 @@
           l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                      ASCIIToUTF16("ERROR_SAVING_THEME_IMAGE"));
       break;
+    case ImageSanitizer::Status::kServiceError:
+      failure_reason = UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL;
+      error = l10n_util::GetStringFUTF16(
+          IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
+          ASCIIToUTF16("ERROR_UTILITY_PROCESS_CRASH"));
+      break;
     default:
       NOTREACHED();
       break;
@@ -786,7 +763,6 @@
 
 void SandboxedUnpacker::UnpackExtensionFailed(const base::string16& error) {
   DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
-
   ReportFailure(
       UNPACKER_CLIENT_FAILED,
       l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, error));
diff --git a/extensions/browser/sandboxed_unpacker.h b/extensions/browser/sandboxed_unpacker.h
index 9ec05ce..24ea37f 100644
--- a/extensions/browser/sandboxed_unpacker.h
+++ b/extensions/browser/sandboxed_unpacker.h
@@ -16,7 +16,6 @@
 #include "base/optional.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
-#include "content/public/browser/utility_process_mojo_client.h"
 #include "extensions/browser/crx_file_info.h"
 #include "extensions/browser/image_sanitizer.h"
 #include "extensions/browser/install/crx_install_error.h"
@@ -40,10 +39,6 @@
 namespace extensions {
 class Extension;
 
-namespace mojom {
-class ExtensionUnpacker;
-}
-
 class SandboxedUnpackerClient
     : public base::RefCountedDeleteOnSequence<SandboxedUnpackerClient> {
  public:
@@ -85,9 +80,8 @@
 
 // SandboxedUnpacker does work to optionally unpack and then validate/sanitize
 // an extension, either starting from a crx file, or else an already unzipped
-// directory (eg., from a differential update). This is done in a sandboxed
-// subprocess to protect the browser process from parsing complex data formats
-// like JPEG or JSON from untrusted sources.
+// directory (eg., from a differential update). The parsing of complex data
+// formats like JPEG or JSON is performed in specific, sandboxed services.
 //
 // Unpacking an extension using this class makes changes to its source, such as
 // transcoding all images to PNG, parsing all message catalogs, and rewriting
@@ -96,8 +90,7 @@
 //
 // Lifetime management:
 //
-// This class is ref-counted by each call it makes to itself on another thread,
-// and by UtilityProcessMojoClient.
+// This class is ref-counted by each call it makes to itself on another thread.
 //
 // Additionally, we hold a reference to our own client so that the client lives
 // long enough to receive the result of unpacking.
@@ -229,15 +222,12 @@
   bool ValidateSignature(const base::FilePath& crx_path,
                          const std::string& expected_hash);
 
-  // Ensures the utility process is created.
-  void StartUtilityProcessIfNeeded();
-
-  // Utility process crashed or failed while trying to install.
-  void UtilityProcessCrashed();
-
   // Unzips the extension into directory.
-  void Unzip(const base::FilePath& crx_path);
-  void UnzipDone(const base::FilePath& directory, bool success);
+  void Unzip(const base::FilePath& crx_path,
+             const base::FilePath& unzipped_dir);
+  void UnzipDone(const base::FilePath& zip_file,
+                 const base::FilePath& unzip_dir,
+                 const std::string& error);
 
   // Unpacks the extension in directory and returns the manifest.
   void Unpack(const base::FilePath& directory);
@@ -342,10 +332,6 @@
   // Sequenced task runner where file I/O operations will be performed.
   scoped_refptr<base::SequencedTaskRunner> unpacker_io_task_runner_;
 
-  // Utility client used for sending tasks to the utility process.
-  std::unique_ptr<content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>
-      utility_process_mojo_client_;
-
   // The normalized path of the install icon path, retrieved from the manifest.
   base::FilePath install_icon_path_;
 
diff --git a/extensions/browser/sandboxed_unpacker_unittest.cc b/extensions/browser/sandboxed_unpacker_unittest.cc
index a61be79..a378d30 100644
--- a/extensions/browser/sandboxed_unpacker_unittest.cc
+++ b/extensions/browser/sandboxed_unpacker_unittest.cc
@@ -17,6 +17,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "components/crx_file/id_util.h"
+#include "components/unzip_service/public/cpp/test_unzip_service.h"
+#include "components/unzip_service/unzip_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
@@ -28,8 +30,10 @@
 #include "extensions/common/switches.h"
 #include "extensions/strings/grit/extensions_strings.h"
 #include "extensions/test/test_extensions_client.h"
+#include "services/data_decoder/data_decoder_service.h"
 #include "services/data_decoder/public/cpp/test_data_decoder_service.h"
 #include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/zlib/google/zip.h"
@@ -128,10 +132,30 @@
     // It will delete itself.
     client_ = new MockSandboxedUnpackerClient;
 
-    sandboxed_unpacker_ = new SandboxedUnpacker(
-        test_data_decoder_service_.connector()->Clone(), Manifest::INTERNAL,
-        Extension::NO_FLAGS, extensions_dir_.GetPath(),
-        base::ThreadTaskRunnerHandle::Get(), client_);
+    InitSanboxedUnpacker(/*data_decode_service=*/nullptr,
+                         /*unzip_service=*/nullptr);
+  }
+
+  void InitSanboxedUnpacker(
+      std::unique_ptr<service_manager::Service> data_decode_service,
+      std::unique_ptr<service_manager::Service> unzip_service) {
+    service_manager::TestConnectorFactory::NameToServiceMap services;
+    if (!data_decode_service)
+      data_decode_service = data_decoder::DataDecoderService::Create();
+    if (!unzip_service)
+      unzip_service = unzip::UnzipService::CreateService();
+    services.insert(
+        std::make_pair("data_decoder", std::move(data_decode_service)));
+    services.insert(std::make_pair("unzip_service", std::move(unzip_service)));
+    test_connector_factory_ =
+        service_manager::TestConnectorFactory::CreateForServices(
+            std::move(services));
+    connector_ = test_connector_factory_->CreateConnector();
+
+    sandboxed_unpacker_ =
+        new SandboxedUnpacker(connector_->Clone(), Manifest::INTERNAL,
+                              Extension::NO_FLAGS, extensions_dir_.GetPath(),
+                              base::ThreadTaskRunnerHandle::Get(), client_);
   }
 
   void TearDown() override {
@@ -178,20 +202,6 @@
     client_->WaitForUnpack();
   }
 
-  void SimulateUtilityProcessCrash() {
-    sandboxed_unpacker_->CreateTempDirectory();
-
-    content::BrowserThread::PostTask(
-        content::BrowserThread::IO, FROM_HERE,
-        base::Bind(&SandboxedUnpacker::StartUtilityProcessIfNeeded,
-                   sandboxed_unpacker_));
-
-    content::BrowserThread::PostTask(
-        content::BrowserThread::IO, FROM_HERE,
-        base::Bind(&SandboxedUnpacker::UtilityProcessCrashed,
-                   sandboxed_unpacker_));
-  }
-
   bool InstallSucceeded() const { return !client_->temp_dir().empty(); }
 
   base::FilePath GetInstallPath() const {
@@ -222,12 +232,14 @@
   }
 
  protected:
-  data_decoder::TestDataDecoderService test_data_decoder_service_;
   base::ScopedTempDir extensions_dir_;
   MockSandboxedUnpackerClient* client_;
   scoped_refptr<SandboxedUnpacker> sandboxed_unpacker_;
   std::unique_ptr<content::InProcessUtilityThreadHelper>
       in_process_utility_thread_helper_;
+  std::unique_ptr<service_manager::TestConnectorFactory>
+      test_connector_factory_;
+  std::unique_ptr<service_manager::Connector> connector_;
 };
 
 TEST_F(SandboxedUnpackerTest, EmptyDefaultLocale) {
@@ -353,6 +365,34 @@
   EXPECT_EQ(base::string16(), GetInstallError());
 }
 
+// The following tests simulate the utility services failling.
+TEST_F(SandboxedUnpackerTest, UnzipperServiceFails) {
+  InitSanboxedUnpacker(
+      /*data_decoder_service=*/nullptr,
+      std::make_unique<unzip::CrashyUnzipService>());
+  SetupUnpacker("good_package.crx", "");
+  EXPECT_FALSE(InstallSucceeded());
+  EXPECT_FALSE(GetInstallError().empty());
+}
+
+TEST_F(SandboxedUnpackerTest, JsonParserFails) {
+  InitSanboxedUnpacker(std::make_unique<data_decoder::CrashyDataDecoderService>(
+                           /*crash_json=*/true, /*crash_image=*/false),
+                       /*unzip_service=*/nullptr);
+  SetupUnpacker("good_package.crx", "");
+  EXPECT_FALSE(InstallSucceeded());
+  EXPECT_FALSE(GetInstallError().empty());
+}
+
+TEST_F(SandboxedUnpackerTest, ImageDecoderFails) {
+  InitSanboxedUnpacker(std::make_unique<data_decoder::CrashyDataDecoderService>(
+                           /*crash_json=*/false, /*crash_image=*/true),
+                       /*unzip_service=*/nullptr);
+  SetupUnpacker("good_package.crx", "");
+  EXPECT_FALSE(InstallSucceeded());
+  EXPECT_FALSE(GetInstallError().empty());
+}
+
 // SandboxedUnpacker is ref counted and is reference by callbacks and
 // InterfacePtrs. This tests that it gets deleted as expected (so that no extra
 // refs are left).
@@ -364,25 +404,4 @@
   TestSandboxedUnpackerDeleted("bad_image.crx", /*expect_success=*/false);
 }
 
-class SandboxedUnpackerTestWithRealIOThread : public SandboxedUnpackerTest {
- public:
-  SandboxedUnpackerTestWithRealIOThread()
-      : SandboxedUnpackerTest(
-            content::TestBrowserThreadBundle::REAL_IO_THREAD) {}
-
-  void TearDown() override {
-    // The utility process task could still be running.  Ensure it is fully
-    // finished before ending the test.
-    content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
-    SandboxedUnpackerTest::TearDown();
-  }
-};
-
-TEST_F(SandboxedUnpackerTestWithRealIOThread, UtilityProcessCrash) {
-  SimulateUtilityProcessCrash();
-  client_->WaitForUnpack();
-  // Check that there is an error message.
-  EXPECT_NE(base::string16(), GetInstallError());
-}
-
 }  // namespace extensions
diff --git a/extensions/browser/zipfile_installer.cc b/extensions/browser/zipfile_installer.cc
index b24bbab..b18e9588 100644
--- a/extensions/browser/zipfile_installer.cc
+++ b/extensions/browser/zipfile_installer.cc
@@ -6,19 +6,33 @@
 
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/task_runner_util.h"
 #include "base/task_scheduler/post_task.h"
+#include "components/unzip_service/public/cpp/unzip.h"
+#include "components/unzip_service/public/interfaces/unzipper.mojom.h"
 #include "extensions/browser/extension_file_task_runner.h"
-#include "extensions/common/extension_unpacker.mojom.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/manifest.h"
 #include "extensions/strings/grit/extensions_strings.h"
+#include "services/data_decoder/public/cpp/safe_json_parser.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/l10n/l10n_util.h"
 
+namespace extensions {
+
 namespace {
 
-const char kExtensionHandlerTempDirError[] =
+constexpr char kExtensionHandlerTempDirError[] =
     "Could not create temporary directory for zipped extension.";
-const char kExtensionHandlerFileUnzipError[] =
+constexpr char kExtensionHandlerFileUnzipError[] =
     "Could not unzip extension for install.";
 
+constexpr const base::FilePath::CharType* kAllowedThemeFiletypes[] = {
+    FILE_PATH_LITERAL(".bmp"),  FILE_PATH_LITERAL(".gif"),
+    FILE_PATH_LITERAL(".jpeg"), FILE_PATH_LITERAL(".jpg"),
+    FILE_PATH_LITERAL(".json"), FILE_PATH_LITERAL(".png"),
+    FILE_PATH_LITERAL(".webp")};
+
 base::Optional<base::FilePath> PrepareAndGetUnzipDir(
     const base::FilePath& zip_file) {
   base::AssertBlockingAllowed();
@@ -36,31 +50,59 @@
   return unzip_dir;
 }
 
-}  // namespace
+base::Optional<std::string> ReadFileContent(const base::FilePath& path) {
+  base::AssertBlockingAllowed();
 
-namespace extensions {
+  std::string content;
+  return base::ReadFileToString(path, &content) ? content
+                                                : base::Optional<std::string>();
+}
+
+}  // namespace
 
 // static
 scoped_refptr<ZipFileInstaller> ZipFileInstaller::Create(
+    service_manager::Connector* connector,
     DoneCallback done_callback) {
+  DCHECK(connector);
   DCHECK(done_callback);
-  return base::WrapRefCounted(new ZipFileInstaller(std::move(done_callback)));
+  return base::WrapRefCounted(
+      new ZipFileInstaller(connector, std::move(done_callback)));
 }
 
 void ZipFileInstaller::LoadFromZipFile(const base::FilePath& zip_file) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  LoadFromZipFileImpl(zip_file, base::FilePath());
+}
+
+void ZipFileInstaller::LoadFromZipFileInDir(const base::FilePath& zip_file,
+                                            const base::FilePath& unzip_dir) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!unzip_dir.empty());
+  LoadFromZipFileImpl(zip_file, unzip_dir);
+}
+
+void ZipFileInstaller::LoadFromZipFileImpl(const base::FilePath& zip_file,
+                                           const base::FilePath& unzip_dir) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!zip_file.empty());
 
   zip_file_ = zip_file;
 
+  if (!unzip_dir.empty()) {
+    Unzip(unzip_dir);
+    return;
+  }
+
   base::PostTaskAndReplyWithResult(
       GetExtensionFileTaskRunner().get(), FROM_HERE,
       base::BindOnce(&PrepareAndGetUnzipDir, zip_file),
       base::BindOnce(&ZipFileInstaller::Unzip, this));
 }
 
-ZipFileInstaller::ZipFileInstaller(DoneCallback done_callback)
-    : done_callback_(std::move(done_callback)) {}
+ZipFileInstaller::ZipFileInstaller(service_manager::Connector* connector,
+                                   DoneCallback done_callback)
+    : done_callback_(std::move(done_callback)), connector_(connector) {}
 
 ZipFileInstaller::~ZipFileInstaller() = default;
 
@@ -71,29 +113,76 @@
     ReportFailure(std::string(kExtensionHandlerTempDirError));
     return;
   }
-  DCHECK(!utility_process_mojo_client_);
 
-  utility_process_mojo_client_ = std::make_unique<
-      content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>(
-      l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_ZIP_FILE_INSTALLER_NAME));
-  utility_process_mojo_client_->set_error_callback(
-      base::Bind(&ZipFileInstaller::UnzipDone, this, *unzip_dir, false));
+  unzip::UnzipWithFilter(
+      connector_->Clone(), zip_file_, *unzip_dir,
+      base::BindRepeating(&ZipFileInstaller::IsManifestFile),
+      base::BindOnce(&ZipFileInstaller::ManifestUnzipped, this, *unzip_dir));
+}
 
-  utility_process_mojo_client_->set_exposed_directory(*unzip_dir);
+void ZipFileInstaller::ManifestUnzipped(const base::FilePath& unzip_dir,
+                                        bool success) {
+  if (!success) {
+    ReportFailure(kExtensionHandlerFileUnzipError);
+    return;
+  }
 
-  utility_process_mojo_client_->Start();
+  base::PostTaskAndReplyWithResult(
+      GetExtensionFileTaskRunner().get(), FROM_HERE,
+      base::BindOnce(&ReadFileContent, unzip_dir.Append(kManifestFilename)),
+      base::BindOnce(&ZipFileInstaller::ManifestRead, this, unzip_dir));
+}
 
-  utility_process_mojo_client_->service()->Unzip(
-      zip_file_, *unzip_dir,
-      base::BindOnce(&ZipFileInstaller::UnzipDone, this, *unzip_dir));
+void ZipFileInstaller::ManifestRead(
+    const base::FilePath& unzip_dir,
+    base::Optional<std::string> manifest_content) {
+  if (!manifest_content) {
+    ReportFailure(std::string(kExtensionHandlerFileUnzipError));
+    return;
+  }
+
+  data_decoder::SafeJsonParser::Parse(
+      connector_, *manifest_content,
+      base::Bind(&ZipFileInstaller::ManifestParsed, this, unzip_dir),
+      base::Bind(&ZipFileInstaller::ManifestParsingFailed, this));
+}
+
+void ZipFileInstaller::ManifestParsingFailed(const std::string& error) {
+  ReportFailure(std::string(kExtensionHandlerFileUnzipError));
+}
+
+void ZipFileInstaller::ManifestParsed(
+    const base::FilePath& unzip_dir,
+    std::unique_ptr<base::Value> manifest_value) {
+  std::unique_ptr<base::DictionaryValue> manifest_dictionary =
+      base::DictionaryValue::From(std::move(manifest_value));
+  if (!manifest_dictionary) {
+    ReportFailure(std::string(kExtensionHandlerFileUnzipError));
+    return;
+  }
+
+  Manifest manifest(Manifest::INTERNAL, std::move(manifest_dictionary));
+
+  unzip::UnzipFilterCallback filter = base::BindRepeating(
+      [](bool is_theme, const base::FilePath& file_path) -> bool {
+        // Note that we ignore the manifest as it has already been extracted and
+        // would cause the unzipping to fail.
+        return ZipFileInstaller::ShouldExtractFile(is_theme, file_path) &&
+               !ZipFileInstaller::IsManifestFile(file_path);
+      },
+      manifest.is_theme());
+
+  // TODO(crbug.com/645263): This silently ignores blocked file types.
+  //                         Add install warnings.
+  unzip::UnzipWithFilter(
+      connector_->Clone(), zip_file_, unzip_dir, filter,
+      base::BindOnce(&ZipFileInstaller::UnzipDone, this, unzip_dir));
 }
 
 void ZipFileInstaller::UnzipDone(const base::FilePath& unzip_dir,
                                  bool success) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  utility_process_mojo_client_.reset();
-
   if (!success) {
     ReportFailure(kExtensionHandlerFileUnzipError);
     return;
@@ -108,4 +197,26 @@
   std::move(done_callback_).Run(zip_file_, base::FilePath(), error);
 }
 
+// static
+bool ZipFileInstaller::ShouldExtractFile(bool is_theme,
+                                         const base::FilePath& file_path) {
+  if (is_theme) {
+    const base::FilePath::StringType extension =
+        base::ToLowerASCII(file_path.FinalExtension());
+    // Allow filenames with no extension.
+    if (extension.empty())
+      return true;
+    return base::ContainsValue(kAllowedThemeFiletypes, extension);
+  }
+  return !base::FilePath::CompareEqualIgnoreCase(file_path.FinalExtension(),
+                                                 FILE_PATH_LITERAL(".exe"));
+}
+
+// static
+bool ZipFileInstaller::IsManifestFile(const base::FilePath& file_path) {
+  CHECK(!file_path.IsAbsolute());
+  return base::FilePath::CompareEqualIgnoreCase(file_path.value(),
+                                                kManifestFilename);
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/zipfile_installer.h b/extensions/browser/zipfile_installer.h
index 944e99a..9d44b5f 100644
--- a/extensions/browser/zipfile_installer.h
+++ b/extensions/browser/zipfile_installer.h
@@ -8,20 +8,24 @@
 #include <memory>
 #include <string>
 
+#include "base/callback.h"
 #include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
-#include "content/public/browser/utility_process_mojo_client.h"
+#include "base/values.h"
+
+namespace service_manager {
+class Connector;
+}
 
 namespace extensions {
 
-namespace mojom {
-class ExtensionUnpacker;
-}
-
-// ZipFileInstaller unzips an extension in a utility process.
+// ZipFileInstaller unzips an extension safely using the Unzipper and
+// SafeJSONParser services.
 // This class is not thread-safe: it is bound to the sequence it is created on.
 class ZipFileInstaller : public base::RefCountedThreadSafe<ZipFileInstaller> {
  public:
@@ -34,18 +38,38 @@
                                                const std::string& error)>;
 
   // Creates a ZipFileInstaller that invokes |done_callback| when done.
-  static scoped_refptr<ZipFileInstaller> Create(DoneCallback done_callback);
+  static scoped_refptr<ZipFileInstaller> Create(
+      service_manager::Connector* connector,
+      DoneCallback done_callback);
 
+  // Creates a temporary directory and unzips the extension in it.
   void LoadFromZipFile(const base::FilePath& zip_file);
 
+  // Unzips the extension in |unzip_dir|.
+  void LoadFromZipFileInDir(const base::FilePath& zip_file,
+                            const base::FilePath& unzip_dir);
+
  private:
   friend class base::RefCountedThreadSafe<ZipFileInstaller>;
+  FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, NonTheme_FileExtractionFilter);
+  FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, Theme_FileExtractionFilter);
+  FRIEND_TEST_ALL_PREFIXES(ZipFileInstallerTest, ManifestExtractionFilter);
 
-  explicit ZipFileInstaller(DoneCallback done_callback);
+  ZipFileInstaller(service_manager::Connector* connector,
+                   DoneCallback done_callback);
   ~ZipFileInstaller();
 
+  void LoadFromZipFileImpl(const base::FilePath& zip_file,
+                           const base::FilePath& unzip_dir);
+
   // Unzip an extension into |unzip_dir| and load it with an UnpackedInstaller.
   void Unzip(base::Optional<base::FilePath> unzip_dir);
+  void ManifestUnzipped(const base::FilePath& unzip_dir, bool success);
+  void ManifestRead(const base::FilePath& unzip_dir,
+                    base::Optional<std::string> manifest_content);
+  void ManifestParsingFailed(const std::string& error);
+  void ManifestParsed(const base::FilePath& unzip_dir,
+                      std::unique_ptr<base::Value> manifest);
   void UnzipDone(const base::FilePath& unzip_dir, bool success);
 
   // On failure, report the |error| reason.
@@ -54,12 +78,18 @@
   // Callback invoked when unzipping has finished.
   DoneCallback done_callback_;
 
+  // Whether a file should be extracted as part of installing an
+  // extension/theme. Protects against unused or potentially hamrful files.
+  static bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path);
+
+  // Returns true if |file_path| points to an extension manifest.
+  static bool IsManifestFile(const base::FilePath& file_path);
+
   // File containing the extension to unzip.
   base::FilePath zip_file_;
 
-  // Utility process used to perform the unzip.
-  std::unique_ptr<content::UtilityProcessMojoClient<mojom::ExtensionUnpacker>>
-      utility_process_mojo_client_;
+  // Connector to the ServiceManager. Bound to the UI thread.
+  service_manager::Connector* connector_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 0450e04..eb30402e 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -23,7 +23,6 @@
 if (enable_extensions) {
   mojom("mojo") {
     sources = [
-      "extension_unpacker.mojom",
       "mojo/app_window.mojom",
       "mojo/guest_view.mojom",
       "mojo/keep_alive.mojom",
@@ -35,7 +34,6 @@
 
     public_deps = [
       "//content/public/common:interfaces",
-      "//mojo/common:common_custom_types",
       "//ui/gfx/geometry/mojo",
       "//url/mojom:url_mojom_gurl",
     ]
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json
index 54301371..8b0d4c7 100644
--- a/extensions/common/api/_manifest_features.json
+++ b/extensions/common/api/_manifest_features.json
@@ -231,6 +231,7 @@
     "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ],
     "whitelist": [
       "787000072C6FBB934AF5A42275CDE73FC977D995",  // browser_tests
+      "2FC374607C2DF285634B67C64A2E356C607091C3",  // QuickOffice
       "CBCC42ABED43A4B58FE3810E62AFFA010EB0349F"   // PDF
     ]
   },
diff --git a/extensions/common/extension_unpacker.mojom b/extensions/common/extension_unpacker.mojom
deleted file mode 100644
index f29ce63..0000000
--- a/extensions/common/extension_unpacker.mojom
+++ /dev/null
@@ -1,16 +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.
-
-// Safe chrome extension unpacker service provided by the utility process
-// and exposed by mojo policy to the chrome browser process.
-
-module extensions.mojom;
-
-import "mojo/common/file_path.mojom";
-
-interface ExtensionUnpacker {
-  // Unzip |file| into the directory |path|.
-  Unzip(mojo.common.mojom.FilePath file,
-        mojo.common.mojom.FilePath path) => (bool success);
-};
diff --git a/extensions/shell/BUILD.gn b/extensions/shell/BUILD.gn
index b5ddbfab..5b493a18 100644
--- a/extensions/shell/BUILD.gn
+++ b/extensions/shell/BUILD.gn
@@ -65,7 +65,6 @@
     "//extensions/shell/common/api",
     "//extensions/shell/common/api:api_registration",
     "//extensions/shell/common/api:extensions_features",
-    "//extensions/utility",
     "//skia",
     "//third_party/WebKit/public:blink",
     "//third_party/cld_3/src/src:cld_3",
@@ -95,6 +94,8 @@
     "browser/api/feedback_private/shell_feedback_private_delegate.h",
     "browser/api/identity/identity_api.cc",
     "browser/api/identity/identity_api.h",
+    "browser/api/runtime/shell_runtime_api_delegate.cc",
+    "browser/api/runtime/shell_runtime_api_delegate.h",
     "browser/default_shell_browser_main_delegate.cc",
     "browser/default_shell_browser_main_delegate.h",
     "browser/delegates/shell_kiosk_delegate.cc",
@@ -161,8 +162,6 @@
     "browser/shell_oauth2_token_service_delegate.h",
     "browser/shell_prefs.cc",
     "browser/shell_prefs.h",
-    "browser/shell_runtime_api_delegate.cc",
-    "browser/shell_runtime_api_delegate.h",
     "browser/shell_special_storage_policy.cc",
     "browser/shell_special_storage_policy.h",
     "browser/shell_speech_recognition_manager_delegate.cc",
@@ -183,8 +182,6 @@
     "renderer/shell_content_renderer_client.h",
     "renderer/shell_extensions_renderer_client.cc",
     "renderer/shell_extensions_renderer_client.h",
-    "utility/shell_content_utility_client.cc",
-    "utility/shell_content_utility_client.h",
   ]
 
   if (use_aura) {
@@ -456,6 +453,7 @@
 source_set("browser_tests") {
   testonly = true
   sources = [
+    "browser/api/runtime/runtime_apitest.cc",
     "browser/geolocation/geolocation_apitest.cc",
     "browser/shell_browsertest.cc",
     "test/shell_apitest.cc",
@@ -473,15 +471,18 @@
     ":app_shell_lib",
     "//base",
     "//base/test:test_support",
+    "//components/keep_alive_registry",
     "//components/version_info",
     "//content/shell:content_shell_lib",
     "//content/test:test_support",
     "//extensions:test_support",
     "//extensions/browser",
+    "//extensions/browser:test_support",
     "//extensions/common",
   ]
 
   if (use_aura) {
+    sources += [ "browser/shell_desktop_controller_aura_browsertest.cc" ]
     deps += [ "//ui/aura" ]
   }
 }
diff --git a/extensions/shell/app/DEPS b/extensions/shell/app/DEPS
index 5b7cd97..a92aad2 100644
--- a/extensions/shell/app/DEPS
+++ b/extensions/shell/app/DEPS
@@ -4,5 +4,6 @@
   "+components/nacl",
   "+content/public/app",
   "+content/public/browser",
+  "+content/public/utility",
   "+sandbox",
 ]
diff --git a/extensions/shell/app/shell_main_delegate.cc b/extensions/shell/app/shell_main_delegate.cc
index 823dbfc5..42c0f22 100644
--- a/extensions/shell/app/shell_main_delegate.cc
+++ b/extensions/shell/app/shell_main_delegate.cc
@@ -12,13 +12,13 @@
 #include "components/nacl/common/buildflags.h"
 #include "content/public/browser/browser_main_runner.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/utility/content_utility_client.h"
 #include "content/shell/common/shell_switches.h"
 #include "extensions/common/extension_paths.h"
 #include "extensions/shell/browser/default_shell_browser_main_delegate.h"
 #include "extensions/shell/browser/shell_content_browser_client.h"
 #include "extensions/shell/common/shell_content_client.h"
 #include "extensions/shell/renderer/shell_content_renderer_client.h"
-#include "extensions/shell/utility/shell_content_utility_client.h"
 #include "ui/base/resource/resource_bundle.h"
 
 #if defined(OS_CHROMEOS)
@@ -168,11 +168,6 @@
   return renderer_client_.get();
 }
 
-content::ContentUtilityClient* ShellMainDelegate::CreateContentUtilityClient() {
-  utility_client_.reset(CreateShellContentUtilityClient());
-  return utility_client_.get();
-}
-
 void ShellMainDelegate::ProcessExiting(const std::string& process_type) {
   logging::CloseLogFile();
 }
@@ -202,7 +197,7 @@
 
 content::ContentUtilityClient*
 ShellMainDelegate::CreateShellContentUtilityClient() {
-  return new ShellContentUtilityClient();
+  return new content::ContentUtilityClient();
 }
 
 void ShellMainDelegate::InitializeResourceBundle() {
diff --git a/extensions/shell/app/shell_main_delegate.h b/extensions/shell/app/shell_main_delegate.h
index 1f005c6..c43f620 100644
--- a/extensions/shell/app/shell_main_delegate.h
+++ b/extensions/shell/app/shell_main_delegate.h
@@ -31,7 +31,6 @@
   void PreSandboxStartup() override;
   content::ContentBrowserClient* CreateContentBrowserClient() override;
   content::ContentRendererClient* CreateContentRendererClient() override;
-  content::ContentUtilityClient* CreateContentUtilityClient() override;
   void ProcessExiting(const std::string& process_type) override;
 #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
   void ZygoteStarting(std::vector<std::unique_ptr<content::ZygoteForkDelegate>>*
diff --git a/extensions/shell/browser/api/runtime/runtime_apitest.cc b/extensions/shell/browser/api/runtime/runtime_apitest.cc
new file mode 100644
index 0000000..c1f0507
--- /dev/null
+++ b/extensions/shell/browser/api/runtime/runtime_apitest.cc
@@ -0,0 +1,80 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/browsertest_util.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/test_extension_registry_observer.h"
+#include "extensions/common/extension_id.h"
+#include "extensions/shell/browser/shell_extension_system.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/result_catcher.h"
+
+namespace extensions {
+
+using ShellRuntimeApiTest = ShellApiTest;
+
+IN_PROC_BROWSER_TEST_F(ShellRuntimeApiTest, RuntimeReload) {
+  const Extension* extension = nullptr;
+
+  // Load the extension and wait for it to be ready.
+  {
+    ResultCatcher catcher;
+    ASSERT_TRUE(extension = LoadExtension("extension"));
+    ASSERT_TRUE(catcher.GetNextResult());
+  }
+
+  const ExtensionId extension_id = extension->id();
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+
+  // Reload the extension and wait for a pair of
+  // ExtensionRegistry::OnExtensionUnloaded()/Loaded() calls.
+  TestExtensionRegistryObserver registry_observer(registry, extension_id);
+  ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+      browser_context(), extension_id, "chrome.runtime.reload();"));
+  ASSERT_EQ(extension, registry_observer.WaitForExtensionUnloaded());
+  EXPECT_TRUE(registry->disabled_extensions().Contains(extension_id));
+  ASSERT_TRUE(extension = registry_observer.WaitForExtensionLoaded());
+  ASSERT_EQ(extension->id(), extension_id);
+  EXPECT_TRUE(registry->enabled_extensions().Contains(extension_id));
+
+  // Wait for the background page to load.
+  {
+    ResultCatcher catcher;
+    ASSERT_TRUE(catcher.GetNextResult());
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(ShellRuntimeApiTest, RuntimeReloadApp) {
+  const Extension* extension = nullptr;
+
+  // Load and launch the app and wait for it to create a window.
+  {
+    ResultCatcher catcher;
+    extension = LoadApp("platform_app");
+    ASSERT_TRUE(catcher.GetNextResult());
+  }
+
+  const ExtensionId extension_id = extension->id();
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+
+  // Reload the extension and wait for a pair of
+  // ExtensionRegistry::OnExtensionUnloaded()/Loaded() calls.
+  TestExtensionRegistryObserver registry_observer(registry, extension_id);
+  ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+      browser_context(), extension_id, "chrome.runtime.reload();"));
+  ASSERT_EQ(extension, registry_observer.WaitForExtensionUnloaded());
+  EXPECT_TRUE(registry->disabled_extensions().Contains(extension_id));
+  ASSERT_TRUE(extension = registry_observer.WaitForExtensionLoaded());
+  ASSERT_EQ(extension->id(), extension_id);
+  EXPECT_TRUE(registry->enabled_extensions().Contains(extension_id));
+
+  // Reloading the app should launch it again automatically.
+  // Wait for the app to create a new window.
+  {
+    ResultCatcher catcher;
+    ASSERT_TRUE(catcher.GetNextResult());
+  }
+}
+
+}  // namespace extensions
diff --git a/extensions/shell/browser/shell_runtime_api_delegate.cc b/extensions/shell/browser/api/runtime/shell_runtime_api_delegate.cc
similarity index 75%
rename from extensions/shell/browser/shell_runtime_api_delegate.cc
rename to extensions/shell/browser/api/runtime/shell_runtime_api_delegate.cc
index 9824e0f..d6e61de 100644
--- a/extensions/shell/browser/shell_runtime_api_delegate.cc
+++ b/extensions/shell/browser/api/runtime/shell_runtime_api_delegate.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "extensions/shell/browser/shell_runtime_api_delegate.h"
+#include "extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h"
 
 #include "build/build_config.h"
 #include "extensions/common/api/runtime.h"
+#include "extensions/shell/browser/shell_extension_system.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -17,19 +18,21 @@
 
 namespace extensions {
 
-ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate() {
+ShellRuntimeAPIDelegate::ShellRuntimeAPIDelegate(
+    content::BrowserContext* browser_context)
+    : browser_context_(browser_context) {
+  DCHECK(browser_context_);
 }
 
-ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() {
-}
+ShellRuntimeAPIDelegate::~ShellRuntimeAPIDelegate() = default;
 
-void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {
-}
+void ShellRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) {}
 
-void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {
-}
+void ShellRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) {}
 
 void ShellRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) {
+  static_cast<ShellExtensionSystem*>(ExtensionSystem::Get(browser_context_))
+      ->ReloadExtension(extension_id);
 }
 
 bool ShellRuntimeAPIDelegate::CheckForUpdates(
@@ -38,8 +41,7 @@
   return false;
 }
 
-void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {
-}
+void ShellRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) {}
 
 bool ShellRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) {
 #if defined(OS_CHROMEOS)
diff --git a/extensions/shell/browser/shell_runtime_api_delegate.h b/extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h
similarity index 70%
rename from extensions/shell/browser/shell_runtime_api_delegate.h
rename to extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h
index 92a5c3cd..1691ce34 100644
--- a/extensions/shell/browser/shell_runtime_api_delegate.h
+++ b/extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h
@@ -2,17 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
-#define EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
+#ifndef EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
+#define EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
 
 #include "base/macros.h"
 #include "extensions/browser/api/runtime/runtime_api_delegate.h"
 
+namespace content {
+class BrowserContext;
+}  // namespace content
+
 namespace extensions {
 
 class ShellRuntimeAPIDelegate : public RuntimeAPIDelegate {
  public:
-  ShellRuntimeAPIDelegate();
+  explicit ShellRuntimeAPIDelegate(content::BrowserContext* browser_context);
   ~ShellRuntimeAPIDelegate() override;
 
   // RuntimeAPIDelegate implementation.
@@ -26,9 +30,11 @@
   bool RestartDevice(std::string* error_message) override;
 
  private:
+  content::BrowserContext* browser_context_;
+
   DISALLOW_COPY_AND_ASSIGN(ShellRuntimeAPIDelegate);
 };
 
 }  // namespace extensions
 
-#endif  // EXTENSIONS_SHELL_BROWSER_SHELL_RUNTIME_API_DELEGATE_H_
+#endif  // EXTENSIONS_SHELL_BROWSER_API_RUNTIME_SHELL_RUNTIME_API_DELEGATE_H_
diff --git a/extensions/shell/browser/shell_browsertest.cc b/extensions/shell/browser/shell_browsertest.cc
index 2a82867..cbd127e 100644
--- a/extensions/shell/browser/shell_browsertest.cc
+++ b/extensions/shell/browser/shell_browsertest.cc
@@ -2,10 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/logging.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/test/test_utils.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/notification_types.h"
diff --git a/extensions/shell/browser/shell_desktop_controller_aura.cc b/extensions/shell/browser/shell_desktop_controller_aura.cc
index 21fb9f3..934ae5e8 100644
--- a/extensions/shell/browser/shell_desktop_controller_aura.cc
+++ b/extensions/shell/browser/shell_desktop_controller_aura.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "components/keep_alive_registry/keep_alive_registry.h"
 #include "extensions/shell/browser/shell_app_window_client.h"
 #include "ui/aura/client/cursor_client.h"
 #include "ui/aura/window.h"
@@ -161,10 +162,15 @@
 }
 
 void ShellDesktopControllerAura::Run() {
+  KeepAliveRegistry::GetInstance()->AddObserver(this);
+
   base::RunLoop run_loop;
   run_loop_ = &run_loop;
   run_loop.Run();
   run_loop_ = nullptr;
+
+  KeepAliveRegistry::GetInstance()->SetIsShuttingDown(true);
+  KeepAliveRegistry::GetInstance()->RemoveObserver(this);
 }
 
 void ShellDesktopControllerAura::AddAppWindow(AppWindow* app_window,
@@ -198,9 +204,7 @@
   TearDownRootWindowController(it->second.get());
   root_window_controllers_.erase(it);
 
-  // run_loop_ may be null in tests.
-  if (run_loop_ && root_window_controllers_.empty())
-    run_loop_->QuitWhenIdle();
+  MaybeQuit();
 }
 
 #if defined(OS_CHROMEOS)
@@ -234,6 +238,15 @@
   return GetPrimaryHost()->DispatchKeyEventPostIME(key_event);
 }
 
+void ShellDesktopControllerAura::OnKeepAliveStateChanged(
+    bool is_keeping_alive) {
+  if (!is_keeping_alive)
+    MaybeQuit();
+}
+
+void ShellDesktopControllerAura::OnKeepAliveRestartStateChanged(
+    bool can_restart) {}
+
 aura::WindowTreeHost* ShellDesktopControllerAura::GetPrimaryHost() {
   if (root_window_controllers_.empty())
     return nullptr;
@@ -337,6 +350,19 @@
   root->host()->window()->RemovePreTargetHandler(focus_controller_.get());
 }
 
+void ShellDesktopControllerAura::MaybeQuit() {
+  // run_loop_ may be null in tests.
+  if (!run_loop_)
+    return;
+
+  // Quit if there are no app windows open and no keep-alives waiting for apps
+  // to relaunch.
+  if (root_window_controllers_.empty() &&
+      !KeepAliveRegistry::GetInstance()->IsKeepingAlive()) {
+    run_loop_->QuitWhenIdle();
+  }
+}
+
 #if defined(OS_CHROMEOS)
 gfx::Size ShellDesktopControllerAura::GetStartingWindowSize() {
   gfx::Size size;
diff --git a/extensions/shell/browser/shell_desktop_controller_aura.h b/extensions/shell/browser/shell_desktop_controller_aura.h
index bc6abf7b..34718f20 100644
--- a/extensions/shell/browser/shell_desktop_controller_aura.h
+++ b/extensions/shell/browser/shell_desktop_controller_aura.h
@@ -11,6 +11,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "components/keep_alive_registry/keep_alive_state_observer.h"
 #include "extensions/shell/browser/desktop_controller.h"
 #include "extensions/shell/browser/root_window_controller.h"
 #include "ui/aura/window.h"
@@ -69,7 +70,8 @@
       public chromeos::PowerManagerClient::Observer,
       public display::DisplayConfigurator::Observer,
 #endif
-      public ui::internal::InputMethodDelegate {
+      public ui::internal::InputMethodDelegate,
+      public KeepAliveStateObserver {
  public:
   explicit ShellDesktopControllerAura(content::BrowserContext* browser_context);
   ~ShellDesktopControllerAura() override;
@@ -84,19 +86,23 @@
       RootWindowController* root_window_controller) override;
 
 #if defined(OS_CHROMEOS)
-  // chromeos::PowerManagerClient::Observer overrides:
+  // chromeos::PowerManagerClient::Observer:
   void PowerButtonEventReceived(bool down,
                                 const base::TimeTicks& timestamp) override;
 
-  // display::DisplayConfigurator::Observer overrides.
+  // display::DisplayConfigurator::Observer:
   void OnDisplayModeChanged(
       const display::DisplayConfigurator::DisplayStateList& displays) override;
 #endif
 
-  // ui::internal::InputMethodDelegate overrides:
+  // ui::internal::InputMethodDelegate:
   ui::EventDispatchDetails DispatchKeyEventPostIME(
       ui::KeyEvent* key_event) override;
 
+  // KeepAliveStateObserver:
+  void OnKeepAliveStateChanged(bool is_keeping_alive) override;
+  void OnKeepAliveRestartStateChanged(bool can_restart) override;
+
   // Returns the WindowTreeHost for the primary display.
   aura::WindowTreeHost* GetPrimaryHost();
 
@@ -119,6 +125,10 @@
   // Removes handlers from the RootWindowController so it can be destroyed.
   void TearDownRootWindowController(RootWindowController* root);
 
+  // Quits if there are no app windows, and no keep-alives waiting for apps to
+  // relaunch.
+  void MaybeQuit();
+
 #if defined(OS_CHROMEOS)
   // Returns the desired dimensions of the RootWindowController from the command
   // line, or falls back to a default size.
diff --git a/extensions/shell/browser/shell_desktop_controller_aura_browsertest.cc b/extensions/shell/browser/shell_desktop_controller_aura_browsertest.cc
new file mode 100644
index 0000000..96637946
--- /dev/null
+++ b/extensions/shell/browser/shell_desktop_controller_aura_browsertest.cc
@@ -0,0 +1,178 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/shell/browser/shell_desktop_controller_aura.h"
+
+#include "base/macros.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/time/time.h"
+#include "components/keep_alive_registry/keep_alive_registry.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/browsertest_util.h"
+#include "extensions/shell/browser/desktop_controller.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/result_catcher.h"
+
+namespace extensions {
+
+// Tests that spin up the ShellDesktopControllerAura and run async tasks like
+// launching and reloading apps.
+class ShellDesktopControllerAuraBrowserTest : public ShellApiTest {
+ public:
+  ShellDesktopControllerAuraBrowserTest() = default;
+  ~ShellDesktopControllerAuraBrowserTest() override = default;
+
+  // Loads and launches a platform app that opens an app window.
+  void LoadAndLaunchApp() {
+    ASSERT_FALSE(app_);
+    app_ = LoadApp("platform_app");
+    ASSERT_TRUE(app_);
+
+    // Wait for app window to load.
+    ResultCatcher catcher;
+    EXPECT_TRUE(catcher.GetNextResult());
+
+    // A window was created.
+    EXPECT_EQ(1u,
+              AppWindowRegistry::Get(browser_context())->app_windows().size());
+  }
+
+ protected:
+  // Returns an open app window.
+  AppWindow* GetAppWindow() {
+    EXPECT_GT(AppWindowRegistry::Get(browser_context())->app_windows().size(),
+              0u);
+    return AppWindowRegistry::Get(browser_context())->app_windows().front();
+  }
+
+  // ShellApiTest:
+  void SetUpOnMainThread() override {
+    ShellApiTest::SetUpOnMainThread();
+    desktop_controller_ =
+        static_cast<ShellDesktopControllerAura*>(DesktopController::instance());
+    ASSERT_TRUE(desktop_controller_);
+  }
+
+  void TearDownOnMainThread() override {
+    EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
+    ShellApiTest::TearDownOnMainThread();
+  }
+
+  ShellDesktopControllerAura* desktop_controller_ = nullptr;
+  scoped_refptr<const Extension> app_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ShellDesktopControllerAuraBrowserTest);
+};
+
+// Test that closing the app window stops the DesktopController.
+IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, CloseAppWindow) {
+  bool test_succeeded = false;
+
+  // Post a task so everything runs after the DesktopController starts.
+  base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
+      FROM_HERE,
+      // Asynchronously launch the app.
+      base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
+                     base::Unretained(this)),
+
+      // Once the app launches, run the test.
+      base::BindLambdaForTesting([this, &test_succeeded]() {
+        // Close the app window so DesktopController quits.
+        GetAppWindow()->OnNativeClose();
+        test_succeeded = true;
+      }));
+
+  // Start DesktopController. It should run until the last app window closes.
+  desktop_controller_->Run();
+  EXPECT_TRUE(test_succeeded)
+      << "DesktopController quit before test completed.";
+}
+
+// Test that the DesktopController runs until all app windows close.
+IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, TwoAppWindows) {
+  bool test_succeeded = false;
+
+  // Post a task so everything runs after the DesktopController starts.
+  base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
+      FROM_HERE,
+      // Asynchronously launch the app.
+      base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
+                     base::Unretained(this)),
+
+      // Once the app launches, run the test.
+      base::BindLambdaForTesting([this, &test_succeeded]() {
+        // Create a second app window.
+        ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+            browser_context(), app_->id(),
+            "chrome.app.window.create('/hello.html');"));
+        ResultCatcher catcher;
+        catcher.GetNextResult();
+
+        // Close the first app window.
+        GetAppWindow()->OnNativeClose();
+
+        // One window is still open, so the DesktopController should still be
+        // running. Post a task to close the last window.
+        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+            FROM_HERE, base::BindLambdaForTesting([this, &test_succeeded]() {
+              GetAppWindow()->OnNativeClose();
+              test_succeeded = true;
+            }),
+            // A regression might cause DesktopController to quit before the
+            // last window closes. To ensure we catch this, wait a while before
+            // closing the last window. If DesktopController::Run() finishes
+            // before we close the last window and update |test_succeeded|, the
+            // test fails.
+            base::TimeDelta::FromMilliseconds(500));
+      }));
+
+  desktop_controller_->Run();
+  EXPECT_TRUE(test_succeeded)
+      << "DesktopController quit before test completed.";
+}
+
+// Test that the DesktopController stays open while an app reloads, even though
+// the app window closes.
+IN_PROC_BROWSER_TEST_F(ShellDesktopControllerAuraBrowserTest, ReloadApp) {
+  bool test_succeeded = false;
+
+  // Post a task so everything runs after the DesktopController starts.
+  base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
+      FROM_HERE,
+      // Asynchronously launch the app.
+      base::BindOnce(&ShellDesktopControllerAuraBrowserTest::LoadAndLaunchApp,
+                     base::Unretained(this)),
+
+      // Once the app launches, run the test.
+      base::BindLambdaForTesting([this, &test_succeeded]() {
+        // Reload the app.
+        ASSERT_TRUE(browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+            browser_context(), app_->id(), "chrome.runtime.reload();"));
+
+        // Wait for the app window to re-open.
+        ResultCatcher catcher;
+        ASSERT_TRUE(catcher.GetNextResult());
+
+        // Close the new window after a delay. DesktopController should remain
+        // open until the window closes.
+        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+            FROM_HERE, base::BindLambdaForTesting([this, &test_succeeded]() {
+              AppWindow* app_window = AppWindowRegistry::Get(browser_context())
+                                          ->app_windows()
+                                          .front();
+              app_window->OnNativeClose();
+              test_succeeded = true;
+            }),
+            base::TimeDelta::FromMilliseconds(500));
+      }));
+
+  desktop_controller_->Run();
+  EXPECT_TRUE(test_succeeded)
+      << "DesktopController quit before test completed.";
+}
+
+}  // namespace extensions
diff --git a/extensions/shell/browser/shell_extension_loader_unittest.cc b/extensions/shell/browser/shell_extension_loader_unittest.cc
index a28a6eb..92f1fe1 100644
--- a/extensions/shell/browser/shell_extension_loader_unittest.cc
+++ b/extensions/shell/browser/shell_extension_loader_unittest.cc
@@ -12,7 +12,6 @@
 #include "base/path_service.h"
 #include "components/crx_file/id_util.h"
 #include "components/keep_alive_registry/keep_alive_registry.h"
-#include "components/prefs/testing_pref_service.h"
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/test/test_utils.h"
@@ -95,7 +94,9 @@
 
     ExtensionsTest::SetUp();
     extensions_browser_client()->set_extension_system_factory(&factory_);
-    user_prefs::UserPrefs::Set(browser_context(), &testing_pref_service_);
+    // ExtensionsTest sets up the ExtensionPrefs, but we still need to attach
+    // the PrefService to the browser context.
+    user_prefs::UserPrefs::Set(browser_context(), pref_service());
     event_router_ = CreateAndUseTestEventRouter(browser_context());
   }
 
@@ -135,7 +136,6 @@
 
  private:
   MockExtensionSystemFactory<TestExtensionSystem> factory_;
-  TestingPrefServiceSimple testing_pref_service_;
 
   TestEventRouter* event_router_ = nullptr;  // Created in SetUp().
 
diff --git a/extensions/shell/browser/shell_extension_system.cc b/extensions/shell/browser/shell_extension_system.cc
index 3bdb3b3..5a176319a 100644
--- a/extensions/shell/browser/shell_extension_system.cc
+++ b/extensions/shell/browser/shell_extension_system.cc
@@ -70,6 +70,10 @@
   apps::LaunchPlatformApp(browser_context_, extension, SOURCE_UNTRACKED);
 }
 
+void ShellExtensionSystem::ReloadExtension(const ExtensionId& extension_id) {
+  extension_loader_->ReloadExtension(extension_id);
+}
+
 void ShellExtensionSystem::Shutdown() {
   extension_loader_.reset();
 }
diff --git a/extensions/shell/browser/shell_extension_system.h b/extensions/shell/browser/shell_extension_system.h
index b25e58b..b69a1a3c 100644
--- a/extensions/shell/browser/shell_extension_system.h
+++ b/extensions/shell/browser/shell_extension_system.h
@@ -52,6 +52,9 @@
   // Launch the app with id |extension_id|.
   void LaunchApp(const ExtensionId& extension_id);
 
+  // Reloads the extension with id |extension_id|.
+  void ReloadExtension(const ExtensionId& extension_id);
+
   // KeyedService implementation:
   void Shutdown() override;
 
diff --git a/extensions/shell/browser/shell_extensions_browser_client.cc b/extensions/shell/browser/shell_extensions_browser_client.cc
index f21f331..587f57b 100644
--- a/extensions/shell/browser/shell_extensions_browser_client.cc
+++ b/extensions/shell/browser/shell_extensions_browser_client.cc
@@ -23,13 +23,13 @@
 #include "extensions/browser/url_request_util.h"
 #include "extensions/common/features/feature_channel.h"
 #include "extensions/shell/browser/api/generated_api_registration.h"
+#include "extensions/shell/browser/api/runtime/shell_runtime_api_delegate.h"
 #include "extensions/shell/browser/delegates/shell_kiosk_delegate.h"
 #include "extensions/shell/browser/shell_extension_host_delegate.h"
 #include "extensions/shell/browser/shell_extension_system_factory.h"
 #include "extensions/shell/browser/shell_extension_web_contents_observer.h"
 #include "extensions/shell/browser/shell_extensions_api_client.h"
 #include "extensions/shell/browser/shell_navigation_ui_data.h"
-#include "extensions/shell/browser/shell_runtime_api_delegate.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/login/login_state.h"
@@ -229,7 +229,7 @@
 std::unique_ptr<RuntimeAPIDelegate>
 ShellExtensionsBrowserClient::CreateRuntimeAPIDelegate(
     content::BrowserContext* context) const {
-  return std::make_unique<ShellRuntimeAPIDelegate>();
+  return std::make_unique<ShellRuntimeAPIDelegate>(context);
 }
 
 const ComponentExtensionResourceManager*
diff --git a/extensions/shell/utility/DEPS b/extensions/shell/utility/DEPS
deleted file mode 100644
index 8ad521e..0000000
--- a/extensions/shell/utility/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "+content/public/utility",
-]
diff --git a/extensions/shell/utility/shell_content_utility_client.cc b/extensions/shell/utility/shell_content_utility_client.cc
deleted file mode 100644
index 75d72622..0000000
--- a/extensions/shell/utility/shell_content_utility_client.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "extensions/shell/utility/shell_content_utility_client.h"
-
-namespace extensions {
-
-ShellContentUtilityClient::ShellContentUtilityClient() = default;
-
-ShellContentUtilityClient::~ShellContentUtilityClient() = default;
-
-void ShellContentUtilityClient::UtilityThreadStarted() {
-  utility_handler::UtilityThreadStarted();
-}
-
-}  // namespace extensions
diff --git a/extensions/shell/utility/shell_content_utility_client.h b/extensions/shell/utility/shell_content_utility_client.h
deleted file mode 100644
index 4baaf17..0000000
--- a/extensions/shell/utility/shell_content_utility_client.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
-#define EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
-
-#include "content/public/utility/content_utility_client.h"
-#include "extensions/utility/utility_handler.h"
-
-namespace extensions {
-
-class ShellContentUtilityClient : public content::ContentUtilityClient {
- public:
-  ShellContentUtilityClient();
-  ~ShellContentUtilityClient() override;
-
-  // content::ContentUtilityClient:
-  void UtilityThreadStarted() override;
-};
-
-}  // namespace extensions
-
-#endif  // EXTENSIONS_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
diff --git a/extensions/strings/extensions_strings.grd b/extensions/strings/extensions_strings.grd
index f3ea305..0fe77a51d 100644
--- a/extensions/strings/extensions_strings.grd
+++ b/extensions/strings/extensions_strings.grd
@@ -385,17 +385,6 @@
           Shaped windows are not supported.
         </message>
       </if>
-
-      <!-- Utility process names. Please keep alphabetized. -->
-      <message name="IDS_UTILITY_PROCESS_EXTENSION_UNPACKER_NAME" desc="The name of the utility process used for unpacking extensions.">
-        Extension Unpacker
-      </message>
-      <message name="IDS_UTILITY_PROCESS_MANIFEST_PARSER_NAME" desc="The name of the utility process used for parsing extension manifests.">
-        Extension Manifest Parser
-      </message>
-      <message name="IDS_UTILITY_PROCESS_ZIP_FILE_INSTALLER_NAME" desc="The name of the utility process used for unpacking zip files.">
-        Zip File Installer
-      </message>
     </messages>
   </release>
 </grit>
diff --git a/extensions/test/test_content_utility_client.cc b/extensions/test/test_content_utility_client.cc
index 10c2c16..69895ce 100644
--- a/extensions/test/test_content_utility_client.cc
+++ b/extensions/test/test_content_utility_client.cc
@@ -17,10 +17,7 @@
 TestContentUtilityClient::~TestContentUtilityClient() = default;
 
 void TestContentUtilityClient::UtilityThreadStarted() {
-  utility_handler::UtilityThreadStarted();
-
   auto registry = std::make_unique<service_manager::BinderRegistry>();
-  utility_handler::ExposeInterfacesToBrowser(registry.get(), false);
   content::ChildThread::Get()
       ->GetServiceManagerConnection()
       ->AddConnectionFilter(std::make_unique<content::SimpleConnectionFilter>(
diff --git a/extensions/test/test_content_utility_client.h b/extensions/test/test_content_utility_client.h
index 18531eb..b2edbd6b 100644
--- a/extensions/test/test_content_utility_client.h
+++ b/extensions/test/test_content_utility_client.h
@@ -6,7 +6,6 @@
 #define EXTENSIONS_TEST_TEST_CONTENT_UTILITY_CLIENT_H_
 
 #include "content/public/utility/content_utility_client.h"
-#include "extensions/utility/utility_handler.h"
 
 namespace extensions {
 
diff --git a/extensions/utility/BUILD.gn b/extensions/utility/BUILD.gn
deleted file mode 100644
index de71ac1..0000000
--- a/extensions/utility/BUILD.gn
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/features.gni")
-import("//extensions/buildflags/buildflags.gni")
-
-assert(enable_extensions)
-
-source_set("utility") {
-  sources = [
-    "utility_handler.cc",
-    "utility_handler.h",
-  ]
-
-  # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
-  configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
-
-  deps = [
-    "//content/public/common",
-    "//content/public/utility",
-    "//extensions/common",
-    "//skia",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "utility_handler_unittest.cc",
-  ]
-  deps = [
-    ":utility",
-    "//base",
-    "//extensions:test_support",
-    "//extensions/common",
-    "//extensions/strings",
-    "//testing/gtest",
-    "//third_party/zlib/google:zip",
-    "//ui/base",
-  ]
-}
diff --git a/extensions/utility/DEPS b/extensions/utility/DEPS
deleted file mode 100644
index 48d2f8d..0000000
--- a/extensions/utility/DEPS
+++ /dev/null
@@ -1,6 +0,0 @@
-include_rules = [
-  "+content/public/utility",
-  "+content/public/child",
-  "+net",
-  "+third_party/zlib/google",
-]
diff --git a/extensions/utility/utility_handler.cc b/extensions/utility/utility_handler.cc
deleted file mode 100644
index 40d7116e..0000000
--- a/extensions/utility/utility_handler.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "extensions/utility/utility_handler.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/json/json_file_value_serializer.h"
-#include "base/location.h"
-#include "base/task_scheduler/post_task.h"
-#include "content/public/utility/utility_thread.h"
-#include "extensions/common/constants.h"
-#include "extensions/common/extension_l10n_util.h"
-#include "extensions/common/extension_unpacker.mojom.h"
-#include "extensions/common/extensions_client.h"
-#include "extensions/common/features/feature_channel.h"
-#include "extensions/common/features/feature_session_type.h"
-#include "extensions/common/manifest.h"
-#include "extensions/common/manifest_constants.h"
-#include "extensions/strings/grit/extensions_strings.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-#include "third_party/zlib/google/zip.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/ui_base_switches.h"
-
-namespace extensions {
-
-namespace {
-
-constexpr const base::FilePath::CharType* kAllowedThemeFiletypes[] = {
-    FILE_PATH_LITERAL(".bmp"),  FILE_PATH_LITERAL(".gif"),
-    FILE_PATH_LITERAL(".jpeg"), FILE_PATH_LITERAL(".jpg"),
-    FILE_PATH_LITERAL(".json"), FILE_PATH_LITERAL(".png"),
-    FILE_PATH_LITERAL(".webp")};
-
-std::unique_ptr<base::DictionaryValue> ReadManifest(
-    const base::FilePath& extension_dir,
-    std::string* error) {
-  DCHECK(error);
-  base::FilePath manifest_path = extension_dir.Append(kManifestFilename);
-  if (!base::PathExists(manifest_path)) {
-    *error = manifest_errors::kInvalidManifest;
-    return nullptr;
-  }
-
-  JSONFileValueDeserializer deserializer(manifest_path);
-  std::unique_ptr<base::Value> root = deserializer.Deserialize(NULL, error);
-  if (!root) {
-    return nullptr;
-  }
-
-  if (!root->is_dict()) {
-    *error = manifest_errors::kInvalidManifest;
-    return nullptr;
-  }
-
-  return base::DictionaryValue::From(std::move(root));
-}
-
-class ExtensionUnpackerImpl : public extensions::mojom::ExtensionUnpacker {
- public:
-  ExtensionUnpackerImpl() = default;
-  ~ExtensionUnpackerImpl() override = default;
-
-  static void Create(extensions::mojom::ExtensionUnpackerRequest request) {
-    mojo::MakeStrongBinding(std::make_unique<ExtensionUnpackerImpl>(),
-                            std::move(request));
-  }
-
- private:
-  // extensions::mojom::ExtensionUnpacker:
-  void Unzip(const base::FilePath& file,
-             const base::FilePath& path,
-             UnzipCallback callback) override {
-    // Move unzip operation to background thread to avoid blocking the main
-    // utility thread for extended amont of time. For example, this prevents
-    // extension unzipping block receipt of the connection complete
-    // notification for the utility process channel to the browser process,
-    // which could cause the utility process to terminate itself due to browser
-    // process being considered unreachable.
-    base::PostTaskWithTraitsAndReplyWithResult(
-        FROM_HERE,
-        {base::TaskPriority::USER_BLOCKING, base::MayBlock(),
-         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
-        base::BindOnce(&ExtensionUnpackerImpl::UnzipOnBackgroundTaskRunner,
-                       file, path),
-        std::move(callback));
-  }
-
-  static bool UnzipFileManifestIntoPath(
-      const base::FilePath& file,
-      const base::FilePath& path,
-      std::unique_ptr<base::DictionaryValue>* manifest) {
-    if (zip::UnzipWithFilterCallback(
-            file, path, base::BindRepeating(&utility_handler::IsManifestFile),
-            false)) {
-      std::string error;
-      *manifest = ReadManifest(path, &error);
-      return error.empty() && manifest->get();
-    }
-
-    return false;
-  }
-
-  static bool UnzipFileIntoPath(
-      const base::FilePath& file,
-      const base::FilePath& path,
-      std::unique_ptr<base::DictionaryValue> manifest) {
-    Manifest internal(Manifest::INTERNAL, std::move(manifest));
-    // TODO(crbug.com/645263): This silently ignores blocked file types.
-    //                         Add install warnings.
-    return zip::UnzipWithFilterCallback(
-        file, path,
-        base::BindRepeating(&utility_handler::ShouldExtractFile,
-                            internal.is_theme()),
-        true /* log_skipped_files */);
-  }
-
-  // Unzips the extension from |file| to |path|.
-  // Returns whether the unzip operation succeeded.
-  static bool UnzipOnBackgroundTaskRunner(const base::FilePath& file,
-                                          const base::FilePath& path) {
-    std::unique_ptr<base::DictionaryValue> manifest;
-    if (!UnzipFileManifestIntoPath(file, path, &manifest))
-      return false;
-
-    return UnzipFileIntoPath(file, path, std::move(manifest));
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(ExtensionUnpackerImpl);
-};
-
-}  // namespace
-
-namespace utility_handler {
-
-void UtilityThreadStarted() {
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  std::string lang = command_line->GetSwitchValueASCII(switches::kLang);
-  if (!lang.empty())
-    extension_l10n_util::SetProcessLocale(lang);
-}
-
-void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
-                               bool running_elevated) {
-  // If our process runs with elevated privileges, only add elevated Mojo
-  // interfaces to the interface registry.
-  if (running_elevated)
-    return;
-
-  registry->AddInterface(base::Bind(&ExtensionUnpackerImpl::Create),
-                         base::ThreadTaskRunnerHandle::Get());
-}
-
-bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path) {
-  if (is_theme) {
-    const base::FilePath::StringType extension =
-        base::ToLowerASCII(file_path.FinalExtension());
-    // Allow filenames with no extension.
-    if (extension.empty())
-      return true;
-    return base::ContainsValue(kAllowedThemeFiletypes, extension);
-  }
-  return !base::FilePath::CompareEqualIgnoreCase(file_path.FinalExtension(),
-                                                 FILE_PATH_LITERAL(".exe"));
-}
-
-bool IsManifestFile(const base::FilePath& file_path) {
-  CHECK(!file_path.IsAbsolute());
-  return base::FilePath::CompareEqualIgnoreCase(file_path.value(),
-                                                kManifestFilename);
-}
-
-}  // namespace utility_handler
-
-}  // namespace extensions
diff --git a/extensions/utility/utility_handler.h b/extensions/utility/utility_handler.h
deleted file mode 100644
index 7a361d9..0000000
--- a/extensions/utility/utility_handler.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
-#define EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
-
-#include "services/service_manager/public/cpp/binder_registry.h"
-
-namespace base {
-class FilePath;
-}
-
-namespace extensions {
-
-namespace utility_handler {
-
-void UtilityThreadStarted();
-
-void ExposeInterfacesToBrowser(service_manager::BinderRegistry* registry,
-                               bool running_elevated);
-
-bool ShouldExtractFile(bool is_theme, const base::FilePath& file_path);
-
-bool IsManifestFile(const base::FilePath& file_path);
-
-}  // namespace utility_handler
-
-}  // namespace extensions
-
-#endif  // EXTENSIONS_UTILITY_UTILITY_HANDLER_H_
diff --git a/extensions/utility/utility_handler_unittest.cc b/extensions/utility/utility_handler_unittest.cc
deleted file mode 100644
index db2c0293..0000000
--- a/extensions/utility/utility_handler_unittest.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <vector>
-
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "extensions/utility/utility_handler.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace extensions {
-
-using UtilityHandlerTest = testing::Test;
-
-struct UnzipFileFilterTestCase {
-  const base::FilePath::CharType* input;
-  const bool should_unzip;
-};
-
-void RunZipFileFilterTest(
-    const std::vector<UnzipFileFilterTestCase>& cases,
-    base::RepeatingCallback<bool(const base::FilePath&)>& filter) {
-  for (size_t i = 0; i < cases.size(); ++i) {
-    base::FilePath input(cases[i].input);
-    bool observed = filter.Run(input);
-    EXPECT_EQ(cases[i].should_unzip, observed)
-        << "i: " << i << ", input: " << input.value();
-  }
-}
-
-TEST_F(UtilityHandlerTest, NonTheme_FileExtractionFilter) {
-  const std::vector<UnzipFileFilterTestCase> cases = {
-      {FILE_PATH_LITERAL("foo"), true},
-      {FILE_PATH_LITERAL("foo.nexe"), true},
-      {FILE_PATH_LITERAL("foo.dll"), true},
-      {FILE_PATH_LITERAL("foo.jpg.exe"), false},
-      {FILE_PATH_LITERAL("foo.exe"), false},
-      {FILE_PATH_LITERAL("foo.EXE"), false},
-      {FILE_PATH_LITERAL("file_without_extension"), true},
-  };
-  base::RepeatingCallback<bool(const base::FilePath&)> filter =
-      base::BindRepeating(&utility_handler::ShouldExtractFile, false);
-  RunZipFileFilterTest(cases, filter);
-}
-
-TEST_F(UtilityHandlerTest, Theme_FileExtractionFilter) {
-  const std::vector<UnzipFileFilterTestCase> cases = {
-      {FILE_PATH_LITERAL("image.jpg"), true},
-      {FILE_PATH_LITERAL("IMAGE.JPEG"), true},
-      {FILE_PATH_LITERAL("test/image.bmp"), true},
-      {FILE_PATH_LITERAL("test/IMAGE.gif"), true},
-      {FILE_PATH_LITERAL("test/image.WEBP"), true},
-      {FILE_PATH_LITERAL("test/dir/file.image.png"), true},
-      {FILE_PATH_LITERAL("manifest.json"), true},
-      {FILE_PATH_LITERAL("other.html"), false},
-      {FILE_PATH_LITERAL("file_without_extension"), true},
-  };
-  base::RepeatingCallback<bool(const base::FilePath&)> filter =
-      base::BindRepeating(&utility_handler::ShouldExtractFile, true);
-  RunZipFileFilterTest(cases, filter);
-}
-
-TEST_F(UtilityHandlerTest, ManifestExtractionFilter) {
-  const std::vector<UnzipFileFilterTestCase> cases = {
-      {FILE_PATH_LITERAL("manifest.json"), true},
-      {FILE_PATH_LITERAL("MANIFEST.JSON"), true},
-      {FILE_PATH_LITERAL("test/manifest.json"), false},
-      {FILE_PATH_LITERAL("manifest.json/test"), false},
-      {FILE_PATH_LITERAL("other.file"), false},
-  };
-  base::RepeatingCallback<bool(const base::FilePath&)> filter =
-      base::BindRepeating(&utility_handler::IsManifestFile);
-  RunZipFileFilterTest(cases, filter);
-}
-
-}  // namespace extensions
diff --git a/gpu/command_buffer/service/buffer_manager.cc b/gpu/command_buffer/service/buffer_manager.cc
index bbe2c33a..5908c49e 100644
--- a/gpu/command_buffer/service/buffer_manager.cc
+++ b/gpu/command_buffer/service/buffer_manager.cc
@@ -129,6 +129,8 @@
       size_(0),
       deleted_(false),
       is_client_side_array_(false),
+      binding_count_(0),
+      transform_feedback_binding_count_(0),
       service_id_(service_id),
       initial_target_(0),
       usage_(GL_STATIC_DRAW) {
@@ -427,12 +429,19 @@
     return;
   }
 
+  if (buffer->IsBoundForTransformFeedbackAndOther()) {
+    ERRORSTATE_SET_GL_ERROR(
+        error_state, GL_INVALID_OPERATION, "glBufferData",
+        "buffer is bound for transform feedback and other use simultaneously");
+    return;
+  }
+
   DoBufferData(error_state, buffer, target, size, usage, data);
 
   if (context_state->bound_transform_feedback.get()) {
     // buffer size might have changed, and on Desktop GL lower than 4.2,
     // we might need to reset transform feedback buffer range.
-    context_state->bound_transform_feedback->OnBufferData(target, buffer);
+    context_state->bound_transform_feedback->OnBufferData(buffer);
   }
 }
 
@@ -478,8 +487,8 @@
 void BufferManager::ValidateAndDoBufferSubData(
   ContextState* context_state, GLenum target, GLintptr offset, GLsizeiptr size,
   const GLvoid * data) {
-  Buffer* buffer = RequestBufferAccess(
-      context_state, target, offset, size, "glBufferSubData");
+  Buffer* buffer = RequestBufferAccess(context_state, target, offset, size,
+                                       "glBufferSubData");
   if (!buffer) {
     return;
   }
@@ -500,12 +509,12 @@
     ContextState* context_state, GLenum readtarget, GLenum writetarget,
     GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size) {
   const char* func_name = "glCopyBufferSubData";
-  Buffer* readbuffer = RequestBufferAccess(
-      context_state, readtarget, readoffset, size, func_name);
+  Buffer* readbuffer = RequestBufferAccess(context_state, readtarget,
+                                           readoffset, size, func_name);
   if (!readbuffer)
     return;
-  Buffer* writebuffer = RequestBufferAccess(
-      context_state, writetarget, writeoffset, size, func_name);
+  Buffer* writebuffer = RequestBufferAccess(context_state, writetarget,
+                                            writeoffset, size, func_name);
   if (!writebuffer)
     return;
 
@@ -619,10 +628,6 @@
     // After being bound to non ELEMENT_ARRAY_BUFFER target, a buffer cannot
     // be bound to ELEMENT_ARRAY_BUFFER target.
 
-    // Note that we don't force the WebGL 2 rule that a buffer bound to
-    // TRANSFORM_FEEDBACK_BUFFER target should not be bound to any other
-    // targets, because that is not a security threat, so we only enforce it
-    // in the WebGL2RenderingContextBase.
     switch (buffer->initial_target()) {
       case GL_ELEMENT_ARRAY_BUFFER:
         switch (target) {
@@ -771,8 +776,8 @@
   if (!buffer->CheckRange(offset, size)) {
     std::string msg = base::StringPrintf(
         "bound to target 0x%04x : offset/size out of range", target);
-    ERRORSTATE_SET_GL_ERROR(
-        error_state, GL_INVALID_VALUE, func_name, msg.c_str());
+    ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, func_name,
+                            msg.c_str());
     return nullptr;
   }
   return buffer;
@@ -785,15 +790,17 @@
   ErrorState* error_state = context_state->GetErrorState();
 
   Buffer* buffer = GetBufferInfoForTarget(context_state, target);
-  return RequestBufferAccess(
-      error_state, buffer, func_name,
-      "bound to target 0x%04x", target) ? buffer : nullptr;
+  return RequestBufferAccess(error_state, buffer, func_name,
+                             "bound to target 0x%04x", target)
+             ? buffer
+             : nullptr;
 }
 
 bool BufferManager::RequestBufferAccess(ErrorState* error_state,
                                         Buffer* buffer,
                                         const char* func_name,
-                                        const char* error_message_format, ...) {
+                                        const char* error_message_format,
+                                        ...) {
   DCHECK(error_state);
 
   va_list varargs;
@@ -832,6 +839,7 @@
     const char* message_tag) {
   DCHECK(error_state);
   DCHECK(bindings);
+
   for (size_t ii = 0; ii < variable_sizes.size(); ++ii) {
     if (variable_sizes[ii] == 0)
       continue;
@@ -850,13 +858,22 @@
           error_state, GL_INVALID_OPERATION, func_name, msg.c_str());
       return false;
     }
+    if (buffer->IsBoundForTransformFeedbackAndOther()) {
+      std::string msg = base::StringPrintf(
+          "%s : buffer at index %zu is bound for transform feedback and other "
+          "use simultaneously",
+          message_tag, ii);
+      ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, func_name,
+                              msg.c_str());
+      return false;
+    }
     GLsizeiptr size = bindings->GetEffectiveBufferSize(ii);
     base::CheckedNumeric<GLsizeiptr> required_size = variable_sizes[ii];
     required_size *= count;
     if (size < required_size.ValueOrDefault(
             std::numeric_limits<GLsizeiptr>::max())) {
       std::string msg = base::StringPrintf(
-          "%s : buffer or buffer range not large enough at index %zu",
+          "%s : buffer or buffer range at index %zu not large enough",
           message_tag, ii);
       ERRORSTATE_SET_GL_ERROR(
           error_state, GL_INVALID_OPERATION, func_name, msg.c_str());
@@ -888,6 +905,16 @@
                             msg.c_str());
     return false;
   }
+  if (buffer->IsBoundForTransformFeedbackAndOther()) {
+    std::string message_tag = base::StringPrintV(error_message_format, varargs);
+    std::string msg = base::StringPrintf(
+        "%s : buffer is bound for transform feedback and other use "
+        "simultaneously",
+        message_tag.c_str());
+    ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, func_name,
+                            msg.c_str());
+    return false;
+  }
   return true;
 }
 
diff --git a/gpu/command_buffer/service/buffer_manager.h b/gpu/command_buffer/service/buffer_manager.h
index 7b754cf..6efb10f3 100644
--- a/gpu/command_buffer/service/buffer_manager.h
+++ b/gpu/command_buffer/service/buffer_manager.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "base/containers/hash_tables.h"
+#include "base/debug/stack_trace.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -103,6 +104,27 @@
     return mapped_range_.get();
   }
 
+  void OnBind(GLenum target) {
+    ++binding_count_;
+    if (target == GL_TRANSFORM_FEEDBACK_BUFFER) {
+      ++transform_feedback_binding_count_;
+    }
+  }
+
+  void OnUnbind(GLenum target) {
+    --binding_count_;
+    if (target == GL_TRANSFORM_FEEDBACK_BUFFER) {
+      --transform_feedback_binding_count_;
+    }
+    DCHECK(binding_count_ >= 0);
+    DCHECK(transform_feedback_binding_count_ >= 0);
+  }
+
+  bool IsBoundForTransformFeedbackAndOther() const {
+    return transform_feedback_binding_count_ > 0 &&
+           transform_feedback_binding_count_ != binding_count_;
+  }
+
  private:
   friend class BufferManager;
   friend class BufferManagerTestBase;
@@ -192,6 +214,13 @@
   // sitting in local memory.
   bool is_client_side_array_;
 
+  // Keeps track of whether this buffer is currently bound for transform
+  // feedback in a WebGL context. Used as an optimization when validating WebGL
+  // draw calls for compliance with binding restrictions.
+  // http://crbug.com/696345
+  int binding_count_;
+  int transform_feedback_binding_count_;
+
   // Service side buffer id.
   GLuint service_id_;
 
@@ -320,7 +349,8 @@
   bool RequestBufferAccess(ErrorState* error_state,
                            Buffer* buffer,
                            const char* func_name,
-                           const char* error_message_format, ...);
+                           const char* error_message_format,
+                           ...);
   // Generates INVALID_OPERATION if offset + size is out of range.
   bool RequestBufferAccess(ErrorState* error_state,
                            Buffer* buffer,
@@ -331,13 +361,12 @@
   // Returns false and generates INVALID_OPERATION if buffer at binding |ii|
   // doesn't exist, is mapped, or smaller than |variable_sizes[ii]| * |count|.
   // Return true otherwise.
-  bool RequestBuffersAccess(
-      ErrorState* error_state,
-      const IndexedBufferBindingHost* bindings,
-      const std::vector<GLsizeiptr>& variable_sizes,
-      GLsizei count,
-      const char* func_name,
-      const char* message_tag);
+  bool RequestBuffersAccess(ErrorState* error_state,
+                            const IndexedBufferBindingHost* bindings,
+                            const std::vector<GLsizeiptr>& variable_sizes,
+                            GLsizei count,
+                            const char* func_name,
+                            const char* message_tag);
 
  private:
   friend class Buffer;
diff --git a/gpu/command_buffer/service/context_state.cc b/gpu/command_buffer/service/context_state.cc
index 324f461..d022fb9 100644
--- a/gpu/command_buffer/service/context_state.cc
+++ b/gpu/command_buffer/service/context_state.cc
@@ -401,7 +401,8 @@
                                               : 0);
   if (flag) {
     if (bound_transform_feedback.get()) {
-      bound_transform_feedback->DoBindTransformFeedback(GL_TRANSFORM_FEEDBACK);
+      bound_transform_feedback->DoBindTransformFeedback(
+          GL_TRANSFORM_FEEDBACK, bound_transform_feedback.get());
     } else {
       api()->glBindTransformFeedbackFn(GL_TRANSFORM_FEEDBACK, 0);
     }
@@ -645,32 +646,61 @@
 }
 
 void ContextState::SetBoundBuffer(GLenum target, Buffer* buffer) {
+  bool do_refcounting = feature_info_->IsWebGL2OrES3Context();
   switch (target) {
     case GL_ARRAY_BUFFER:
+      if (do_refcounting && bound_array_buffer)
+        bound_array_buffer->OnUnbind(target);
       bound_array_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       break;
     case GL_ELEMENT_ARRAY_BUFFER:
       vertex_attrib_manager->SetElementArrayBuffer(buffer);
       break;
     case GL_COPY_READ_BUFFER:
+      if (do_refcounting && bound_copy_read_buffer)
+        bound_copy_read_buffer->OnUnbind(target);
       bound_copy_read_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       break;
     case GL_COPY_WRITE_BUFFER:
+      if (do_refcounting && bound_copy_write_buffer)
+        bound_copy_write_buffer->OnUnbind(target);
       bound_copy_write_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       break;
     case GL_PIXEL_PACK_BUFFER:
+      if (do_refcounting && bound_pixel_pack_buffer)
+        bound_pixel_pack_buffer->OnUnbind(target);
       bound_pixel_pack_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       UpdatePackParameters();
       break;
     case GL_PIXEL_UNPACK_BUFFER:
+      if (do_refcounting && bound_pixel_unpack_buffer)
+        bound_pixel_unpack_buffer->OnUnbind(target);
       bound_pixel_unpack_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       UpdateUnpackParameters();
       break;
     case GL_TRANSFORM_FEEDBACK_BUFFER:
+      if (do_refcounting && bound_transform_feedback_buffer)
+        bound_transform_feedback_buffer->OnUnbind(target);
       bound_transform_feedback_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       break;
     case GL_UNIFORM_BUFFER:
+      if (do_refcounting && bound_uniform_buffer)
+        bound_uniform_buffer->OnUnbind(target);
       bound_uniform_buffer = buffer;
+      if (do_refcounting && buffer)
+        buffer->OnBind(target);
       break;
     default:
       NOTREACHED();
@@ -680,32 +710,47 @@
 
 void ContextState::RemoveBoundBuffer(Buffer* buffer) {
   DCHECK(buffer);
+  bool do_refcounting = feature_info_->IsWebGL2OrES3Context();
   vertex_attrib_manager->Unbind(buffer);
   if (bound_array_buffer.get() == buffer) {
     bound_array_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_ARRAY_BUFFER);
   }
   if (bound_copy_read_buffer.get() == buffer) {
     bound_copy_read_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_COPY_READ_BUFFER);
   }
   if (bound_copy_write_buffer.get() == buffer) {
     bound_copy_write_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_COPY_WRITE_BUFFER);
   }
   if (bound_pixel_pack_buffer.get() == buffer) {
     bound_pixel_pack_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_PIXEL_PACK_BUFFER);
     UpdatePackParameters();
   }
   if (bound_pixel_unpack_buffer.get() == buffer) {
     bound_pixel_unpack_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_PIXEL_UNPACK_BUFFER);
     UpdateUnpackParameters();
   }
   if (bound_transform_feedback_buffer.get() == buffer) {
     bound_transform_feedback_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_TRANSFORM_FEEDBACK_BUFFER);
   }
   if (bound_transform_feedback.get()) {
     bound_transform_feedback->RemoveBoundBuffer(buffer);
   }
   if (bound_uniform_buffer.get() == buffer) {
     bound_uniform_buffer = nullptr;
+    if (do_refcounting && buffer)
+      buffer->OnUnbind(GL_UNIFORM_BUFFER);
   }
 }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 81b51b8..c17eb07f 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -1280,7 +1280,8 @@
       GLuint service_id,
       bool client_visible) {
     return vertex_array_manager()->CreateVertexAttribManager(
-        client_id, service_id, group_->max_vertex_attribs(), client_visible);
+        client_id, service_id, group_->max_vertex_attribs(), client_visible,
+        feature_info_->IsWebGL2OrES3Context());
   }
 
   void DoBindAttribLocation(GLuint client_id,
@@ -3416,9 +3417,12 @@
     api()->glBindTransformFeedbackFn(GL_TRANSFORM_FEEDBACK,
                                      default_transform_feedback);
     state_.bound_transform_feedback = state_.default_transform_feedback.get();
+    state_.bound_transform_feedback->SetIsBound(true);
   }
-  state_.indexed_uniform_buffer_bindings = new IndexedBufferBindingHost(
-      group_->max_uniform_buffer_bindings(), needs_emulation);
+  state_.indexed_uniform_buffer_bindings =
+      new IndexedBufferBindingHost(group_->max_uniform_buffer_bindings(),
+                                   GL_UNIFORM_BUFFER, needs_emulation);
+  state_.indexed_uniform_buffer_bindings->SetIsBound(true);
 
   state_.InitGenericAttribs(group_->max_vertex_attribs());
   vertex_array_manager_.reset(new VertexArrayManager());
@@ -4517,7 +4521,7 @@
         // Bind to the default transform feedback.
         DCHECK(state_.default_transform_feedback.get());
         state_.default_transform_feedback->DoBindTransformFeedback(
-            GL_TRANSFORM_FEEDBACK);
+            GL_TRANSFORM_FEEDBACK, state_.bound_transform_feedback.get());
         state_.bound_transform_feedback =
             state_.default_transform_feedback.get();
       }
@@ -5817,10 +5821,10 @@
   DCHECK(bindings);
   switch (function_type) {
     case kBindBufferBase:
-      bindings->DoBindBufferBase(target, index, buffer);
+      bindings->DoBindBufferBase(index, buffer);
       break;
     case kBindBufferRange:
-      bindings->DoBindBufferRange(target, index, buffer, offset, size);
+      bindings->DoBindBufferRange(index, buffer, offset, size);
       break;
     default:
       NOTREACHED();
@@ -6255,7 +6259,8 @@
     return;
   }
   LogClientServiceForInfo(transform_feedback, client_id, function_name);
-  transform_feedback->DoBindTransformFeedback(target);
+  transform_feedback->DoBindTransformFeedback(
+      target, state_.bound_transform_feedback.get());
   state_.bound_transform_feedback = transform_feedback;
 }
 
@@ -16926,7 +16931,11 @@
 
   // Only set the VAO state if it's changed
   if (state_.vertex_attrib_manager.get() != vao) {
+    if (state_.vertex_attrib_manager)
+      state_.vertex_attrib_manager->SetIsBound(false);
     state_.vertex_attrib_manager = vao;
+    if (vao)
+      vao->SetIsBound(true);
     if (!features().native_vertex_array_object) {
       EmulateVertexArrayState();
     } else {
diff --git a/gpu/command_buffer/service/indexed_buffer_binding_host.cc b/gpu/command_buffer/service/indexed_buffer_binding_host.cc
index 967cb1cf..144f5c2 100644
--- a/gpu/command_buffer/service/indexed_buffer_binding_host.cc
+++ b/gpu/command_buffer/service/indexed_buffer_binding_host.cc
@@ -75,40 +75,56 @@
   effective_full_buffer_size = 0;
 }
 
-
-IndexedBufferBindingHost::IndexedBufferBindingHost(
-    uint32_t max_bindings, bool needs_emulation)
+IndexedBufferBindingHost::IndexedBufferBindingHost(uint32_t max_bindings,
+                                                   GLenum target,
+                                                   bool needs_emulation)
     : needs_emulation_(needs_emulation),
-      max_non_null_binding_index_plus_one_(0u) {
+      is_bound_(false),
+      max_non_null_binding_index_plus_one_(0u),
+      target_(target) {
   DCHECK(needs_emulation);
   buffer_bindings_.resize(max_bindings);
 }
 
-IndexedBufferBindingHost::~IndexedBufferBindingHost() = default;
+IndexedBufferBindingHost::~IndexedBufferBindingHost() {
+  SetIsBound(false);
+}
 
-void IndexedBufferBindingHost::DoBindBufferBase(
-    GLenum target, GLuint index, Buffer* buffer) {
+void IndexedBufferBindingHost::DoBindBufferBase(GLuint index, Buffer* buffer) {
   DCHECK_LT(index, buffer_bindings_.size());
   GLuint service_id = buffer ? buffer->service_id() : 0;
-  glBindBufferBase(target, index, service_id);
+  glBindBufferBase(target_, index, service_id);
 
+  if (buffer_bindings_[index].buffer && is_bound_) {
+    buffer_bindings_[index].buffer->OnUnbind(target_);
+  }
   buffer_bindings_[index].SetBindBufferBase(buffer);
+  if (buffer && is_bound_) {
+    buffer->OnBind(target_);
+  }
   UpdateMaxNonNullBindingIndex(index);
 }
 
-void IndexedBufferBindingHost::DoBindBufferRange(
-    GLenum target, GLuint index, Buffer* buffer, GLintptr offset,
-    GLsizeiptr size) {
+void IndexedBufferBindingHost::DoBindBufferRange(GLuint index,
+                                                 Buffer* buffer,
+                                                 GLintptr offset,
+                                                 GLsizeiptr size) {
   DCHECK_LT(index, buffer_bindings_.size());
   GLuint service_id = buffer ? buffer->service_id() : 0;
   if (buffer && needs_emulation_) {
-    DoAdjustedBindBufferRange(
-        target, index, service_id, offset, size, buffer->size());
+    DoAdjustedBindBufferRange(target_, index, service_id, offset, size,
+                              buffer->size());
   } else {
-    glBindBufferRange(target, index, service_id, offset, size);
+    glBindBufferRange(target_, index, service_id, offset, size);
   }
 
+  if (buffer_bindings_[index].buffer && is_bound_) {
+    buffer_bindings_[index].buffer->OnUnbind(target_);
+  }
   buffer_bindings_[index].SetBindBufferRange(buffer, offset, size);
+  if (buffer && is_bound_) {
+    buffer->OnBind(target_);
+  }
   UpdateMaxNonNullBindingIndex(index);
 }
 
@@ -140,24 +156,7 @@
   glBindBufferRange(target, index, service_id, offset, adjusted_size);
 }
 
-void IndexedBufferBindingHost::OnBindHost(GLenum target) {
-  if (needs_emulation_) {
-    // If some bound buffers change size since last time the transformfeedback
-    // is bound, we might need to reset the ranges.
-    for (size_t ii = 0; ii < buffer_bindings_.size(); ++ii) {
-      Buffer* buffer = buffer_bindings_[ii].buffer.get();
-      if (buffer && buffer_bindings_[ii].type == kBindBufferRange &&
-          buffer_bindings_[ii].effective_full_buffer_size != buffer->size()) {
-        DoAdjustedBindBufferRange(
-            target, ii, buffer->service_id(), buffer_bindings_[ii].offset,
-            buffer_bindings_[ii].size, buffer->size());
-        buffer_bindings_[ii].effective_full_buffer_size = buffer->size();
-      }
-    }
-  }
-}
-
-void IndexedBufferBindingHost::OnBufferData(GLenum target, Buffer* buffer) {
+void IndexedBufferBindingHost::OnBufferData(Buffer* buffer) {
   DCHECK(buffer);
   if (needs_emulation_) {
     // If some bound buffers change size since last time the transformfeedback
@@ -167,9 +166,9 @@
         continue;
       if (buffer_bindings_[ii].type == kBindBufferRange &&
           buffer_bindings_[ii].effective_full_buffer_size != buffer->size()) {
-        DoAdjustedBindBufferRange(
-            target, ii, buffer->service_id(), buffer_bindings_[ii].offset,
-            buffer_bindings_[ii].size, buffer->size());
+        DoAdjustedBindBufferRange(target_, ii, buffer->service_id(),
+                                  buffer_bindings_[ii].offset,
+                                  buffer_bindings_[ii].size, buffer->size());
         buffer_bindings_[ii].effective_full_buffer_size = buffer->size();
       }
     }
@@ -185,6 +184,36 @@
   }
 }
 
+void IndexedBufferBindingHost::SetIsBound(bool is_bound) {
+  if (is_bound && needs_emulation_) {
+    // If some bound buffers change size since last time the transformfeedback
+    // is bound, we might need to reset the ranges.
+    for (size_t ii = 0; ii < buffer_bindings_.size(); ++ii) {
+      Buffer* buffer = buffer_bindings_[ii].buffer.get();
+      if (buffer && buffer_bindings_[ii].type == kBindBufferRange &&
+          buffer_bindings_[ii].effective_full_buffer_size != buffer->size()) {
+        DoAdjustedBindBufferRange(target_, ii, buffer->service_id(),
+                                  buffer_bindings_[ii].offset,
+                                  buffer_bindings_[ii].size, buffer->size());
+        buffer_bindings_[ii].effective_full_buffer_size = buffer->size();
+      }
+    }
+  }
+
+  if (is_bound != is_bound_) {
+    is_bound_ = is_bound;
+    for (auto& bb : buffer_bindings_) {
+      if (bb.buffer) {
+        if (is_bound_) {
+          bb.buffer->OnBind(target_);
+        } else {
+          bb.buffer->OnUnbind(target_);
+        }
+      }
+    }
+  }
+}
+
 Buffer* IndexedBufferBindingHost::GetBufferBinding(GLuint index) const {
   DCHECK_LT(index, buffer_bindings_.size());
   return buffer_bindings_[index].buffer.get();
@@ -222,6 +251,8 @@
 
 void IndexedBufferBindingHost::RestoreBindings(
     IndexedBufferBindingHost* prev) {
+  // This is used only for UNIFORM_BUFFER bindings in context switching.
+  DCHECK(target_ == GL_UNIFORM_BUFFER && (!prev || prev->target_ == target_));
   size_t limit = max_non_null_binding_index_plus_one_;
   if (prev && prev->max_non_null_binding_index_plus_one_ > limit) {
     limit = prev->max_non_null_binding_index_plus_one_;
@@ -233,13 +264,12 @@
     switch (buffer_bindings_[ii].type) {
       case kBindBufferBase:
       case kBindBufferNone:
-        DoBindBufferBase(
-            GL_UNIFORM_BUFFER, ii, buffer_bindings_[ii].buffer.get());
+        DoBindBufferBase(ii, buffer_bindings_[ii].buffer.get());
         break;
       case kBindBufferRange:
-        DoBindBufferRange(
-            GL_UNIFORM_BUFFER, ii, buffer_bindings_[ii].buffer.get(),
-            buffer_bindings_[ii].offset, buffer_bindings_[ii].size);
+        DoBindBufferRange(ii, buffer_bindings_[ii].buffer.get(),
+                          buffer_bindings_[ii].offset,
+                          buffer_bindings_[ii].size);
         break;
     }
   }
diff --git a/gpu/command_buffer/service/indexed_buffer_binding_host.h b/gpu/command_buffer/service/indexed_buffer_binding_host.h
index 86c78ffd..4e9993b 100644
--- a/gpu/command_buffer/service/indexed_buffer_binding_host.h
+++ b/gpu/command_buffer/service/indexed_buffer_binding_host.h
@@ -25,25 +25,27 @@
   // In theory |needs_emulation| needs to be true on Desktop GL 4.1 or lower.
   // However, we set it to true everywhere, not to trust drivers to handle
   // out-of-bounds buffer accesses.
-  IndexedBufferBindingHost(uint32_t max_bindings, bool needs_emulation);
+  IndexedBufferBindingHost(uint32_t max_bindings,
+                           GLenum target,
+                           bool needs_emulation);
 
   // The following two functions do state update and call the underlying GL
   // function.  All validations have been done already and the GL function is
   // guaranteed to succeed.
-  void DoBindBufferBase(GLenum target, GLuint index, Buffer* buffer);
-  void DoBindBufferRange(
-      GLenum target, GLuint index, Buffer* buffer, GLintptr offset,
-      GLsizeiptr size);
+  void DoBindBufferBase(GLuint index, Buffer* buffer);
+  void DoBindBufferRange(GLuint index,
+                         Buffer* buffer,
+                         GLintptr offset,
+                         GLsizeiptr size);
 
   // This is called on the active host when glBufferData is called and buffer
   // size might change.
-  void OnBufferData(GLenum target, Buffer* buffer);
-
-  // This is called when the host become active.
-  void OnBindHost(GLenum target);
+  void OnBufferData(Buffer* buffer);
 
   void RemoveBoundBuffer(Buffer* buffer);
 
+  void SetIsBound(bool bound);
+
   Buffer* GetBufferBinding(GLuint index) const;
   // Returns |size| set by glBindBufferRange; 0 if set by glBindBufferBase.
   GLsizeiptr GetBufferSize(GLuint index) const;
@@ -107,8 +109,20 @@
 
   bool needs_emulation_;
 
+  // Whether this object is currently bound into the context.
+  bool is_bound_;
+
+  // Whether or not to call Buffer::OnBind/OnUnbind whenever bindings change.
+  // This is only necessary for WebGL contexts to implement
+  // https://crbug.com/696345
+  bool do_buffer_refcounting_;
+
   // This is used for optimization purpose in context switching.
   size_t max_non_null_binding_index_plus_one_;
+
+  // The GL binding point that this host manages
+  // (e.g. GL_TRANSFORM_FEEDBACK_BUFFER).
+  GLenum target_;
 };
 
 }  // namespace gles2
diff --git a/gpu/command_buffer/service/indexed_buffer_binding_host_unittest.cc b/gpu/command_buffer/service/indexed_buffer_binding_host_unittest.cc
index 6d126f48..7e5237b 100644
--- a/gpu/command_buffer/service/indexed_buffer_binding_host_unittest.cc
+++ b/gpu/command_buffer/service/indexed_buffer_binding_host_unittest.cc
@@ -19,7 +19,12 @@
 class IndexedBufferBindingHostTest : public GpuServiceTest {
  public:
   IndexedBufferBindingHostTest()
-      : host_(new IndexedBufferBindingHost(kMaxBindings, true)),
+      : uniform_host_(new IndexedBufferBindingHost(kMaxBindings,
+                                                   GL_UNIFORM_BUFFER,
+                                                   true)),
+        tf_host_(new IndexedBufferBindingHost(kMaxBindings,
+                                              GL_TRANSFORM_FEEDBACK_BUFFER,
+                                              true)),
         buffer_manager_(new BufferManager(nullptr, nullptr)) {
     buffer_manager_->CreateBuffer(kBufferClientId, kBufferServiceId);
     buffer_ = buffer_manager_->GetBuffer(kBufferClientId);
@@ -34,7 +39,8 @@
   }
 
   void TearDown() override {
-    host_->RemoveBoundBuffer(buffer_.get());
+    uniform_host_->RemoveBoundBuffer(buffer_.get());
+    tf_host_->RemoveBoundBuffer(buffer_.get());
     buffer_ = nullptr;
     buffer_manager_->MarkContextLost();
     buffer_manager_->Destroy();
@@ -47,7 +53,8 @@
         buffer_.get(), target, size, GL_STATIC_DRAW, false);
   }
 
-  scoped_refptr<IndexedBufferBindingHost> host_;
+  scoped_refptr<IndexedBufferBindingHost> uniform_host_;
+  scoped_refptr<IndexedBufferBindingHost> tf_host_;
   std::unique_ptr<BufferManager> buffer_manager_;
   scoped_refptr<Buffer> buffer_;
 };
@@ -62,15 +69,15 @@
       .Times(1)
       .RetiresOnSaturation();
 
-  host_->DoBindBufferRange(kTarget, kIndex, buffer_.get(), kOffset, kSize);
+  tf_host_->DoBindBufferRange(kIndex, buffer_.get(), kOffset, kSize);
 
   for (uint32_t index = 0; index < kMaxBindings; ++index) {
     if (index != kIndex) {
-      EXPECT_EQ(nullptr, host_->GetBufferBinding(index));
+      EXPECT_EQ(nullptr, tf_host_->GetBufferBinding(index));
     } else {
-      EXPECT_EQ(buffer_.get(), host_->GetBufferBinding(index));
-      EXPECT_EQ(kSize, host_->GetBufferSize(index));
-      EXPECT_EQ(kOffset, host_->GetBufferStart(index));
+      EXPECT_EQ(buffer_.get(), tf_host_->GetBufferBinding(index));
+      EXPECT_EQ(kSize, tf_host_->GetBufferSize(index));
+      EXPECT_EQ(kOffset, tf_host_->GetBufferStart(index));
     }
   }
 }
@@ -91,15 +98,15 @@
       .Times(1)
       .RetiresOnSaturation();
 
-  host_->DoBindBufferRange(kTarget, kIndex, buffer_.get(), kOffset, kSize);
+  tf_host_->DoBindBufferRange(kIndex, buffer_.get(), kOffset, kSize);
 
   for (uint32_t index = 0; index < kMaxBindings; ++index) {
     if (index != kIndex) {
-      EXPECT_EQ(nullptr, host_->GetBufferBinding(index));
+      EXPECT_EQ(nullptr, tf_host_->GetBufferBinding(index));
     } else {
-      EXPECT_EQ(buffer_.get(), host_->GetBufferBinding(index));
-      EXPECT_EQ(kSize, host_->GetBufferSize(index));
-      EXPECT_EQ(kOffset, host_->GetBufferStart(index));
+      EXPECT_EQ(buffer_.get(), tf_host_->GetBufferBinding(index));
+      EXPECT_EQ(kSize, tf_host_->GetBufferSize(index));
+      EXPECT_EQ(kOffset, tf_host_->GetBufferStart(index));
     }
   }
 
@@ -110,7 +117,7 @@
       .RetiresOnSaturation();
 
   SetBufferSize(kTarget, kOffset + kSize);
-  host_->OnBufferData(kTarget, buffer_.get());
+  tf_host_->OnBufferData(buffer_.get());
 }
 
 TEST_F(IndexedBufferBindingHostTest, RestoreBindings) {
@@ -128,29 +135,29 @@
   EXPECT_CALL(*gl_, BindBufferBase(kTarget, kIndex, kBufferServiceId))
       .Times(1)
       .RetiresOnSaturation();
-  host_->DoBindBufferBase(kTarget, kIndex, buffer_.get());
+  uniform_host_->DoBindBufferBase(kIndex, buffer_.get());
   // Set up the second host
   scoped_refptr<IndexedBufferBindingHost> other =
-      new IndexedBufferBindingHost(kMaxBindings, true);
+      new IndexedBufferBindingHost(kMaxBindings, GL_UNIFORM_BUFFER, true);
   EXPECT_CALL(*gl_, BindBufferRange(kTarget, kOtherIndex, kBufferServiceId,
                                     kOffset, clamped_size))
       .Times(1)
       .RetiresOnSaturation();
-  other->DoBindBufferRange(kTarget, kOtherIndex, buffer_.get(), kOffset, kSize);
+  other->DoBindBufferRange(kOtherIndex, buffer_.get(), kOffset, kSize);
 
   {
-    // Switching from |other| to |host_|.
+    // Switching from |other| to |uniform_host_|.
     EXPECT_CALL(*gl_, BindBufferBase(kTarget, kIndex, kBufferServiceId))
         .Times(1)
         .RetiresOnSaturation();
     EXPECT_CALL(*gl_, BindBufferBase(kTarget, kOtherIndex, 0))
         .Times(1)
         .RetiresOnSaturation();
-    host_->RestoreBindings(other.get());
+    uniform_host_->RestoreBindings(other.get());
   }
 
   {
-    // Switching from |host_| to |other|.
+    // Switching from |uniform_host_| to |other|.
     EXPECT_CALL(*gl_, BindBufferBase(kTarget, kIndex, 0))
         .Times(1)
         .RetiresOnSaturation();
@@ -158,7 +165,7 @@
                                       kOffset, clamped_size))
         .Times(1)
         .RetiresOnSaturation();
-    other->RestoreBindings(host_.get());
+    other->RestoreBindings(uniform_host_.get());
   }
 }
 
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index 36fccf08..cfa7f8d 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -2616,6 +2616,12 @@
           "pixel unpack buffer should not be mapped to client memory");
       return false;
     }
+    if (buffer->IsBoundForTransformFeedbackAndOther()) {
+      ERRORSTATE_SET_GL_ERROR(
+          error_state, GL_INVALID_OPERATION, function_name,
+          "pixel unpack buffer is simultaneously bound for transform feedback");
+      return error::kNoError;
+    }
     base::CheckedNumeric<uint32_t> size = args.pixels_size;
     GLuint offset = ToGLuint(args.pixels);
     size += offset;
@@ -2912,6 +2918,12 @@
           "pixel unpack buffer should not be mapped to client memory");
       return false;
     }
+    if (buffer->IsBoundForTransformFeedbackAndOther()) {
+      ERRORSTATE_SET_GL_ERROR(
+          error_state, GL_INVALID_OPERATION, function_name,
+          "pixel unpack buffer is simultaneously bound for transform feedback");
+      return error::kNoError;
+    }
     base::CheckedNumeric<uint32_t> size = args.pixels_size;
     GLuint offset = ToGLuint(args.pixels);
     size += offset;
diff --git a/gpu/command_buffer/service/transform_feedback_manager.cc b/gpu/command_buffer/service/transform_feedback_manager.cc
index 7f20e973..1cde26b 100644
--- a/gpu/command_buffer/service/transform_feedback_manager.cc
+++ b/gpu/command_buffer/service/transform_feedback_manager.cc
@@ -15,6 +15,7 @@
                                      GLuint service_id)
     : IndexedBufferBindingHost(
           manager->max_transform_feedback_separate_attribs(),
+          GL_TRANSFORM_FEEDBACK_BUFFER,
           manager->needs_emulation()),
       manager_(manager),
       client_id_(client_id),
@@ -35,17 +36,24 @@
   }
 }
 
-void TransformFeedback::DoBindTransformFeedback(GLenum target) {
+void TransformFeedback::DoBindTransformFeedback(
+    GLenum target,
+    TransformFeedback* last_bound_transform_feedback) {
   DCHECK_LT(0u, service_id_);
   glBindTransformFeedback(target, service_id_);
   has_been_bound_ = true;
-  OnBindHost(target);
   if (active_ && !paused_) {
     // This could only happen during virtual context switching.
     // Otherwise the validation should generate a GL error without calling
     // into this function.
     glResumeTransformFeedback();
   }
+  if (last_bound_transform_feedback != this) {
+    if (last_bound_transform_feedback) {
+      last_bound_transform_feedback->SetIsBound(false);
+    }
+    SetIsBound(true);
+  }
 }
 
 void TransformFeedback::DoBeginTransformFeedback(GLenum primitive_mode) {
@@ -77,9 +85,9 @@
   paused_ = false;
 }
 
-
 TransformFeedbackManager::TransformFeedbackManager(
-    GLuint max_transform_feedback_separate_attribs, bool needs_emulation)
+    GLuint max_transform_feedback_separate_attribs,
+    bool needs_emulation)
     : max_transform_feedback_separate_attribs_(
           max_transform_feedback_separate_attribs),
       needs_emulation_(needs_emulation),
diff --git a/gpu/command_buffer/service/transform_feedback_manager.h b/gpu/command_buffer/service/transform_feedback_manager.h
index 09c370e4..d589fa7 100644
--- a/gpu/command_buffer/service/transform_feedback_manager.h
+++ b/gpu/command_buffer/service/transform_feedback_manager.h
@@ -22,13 +22,16 @@
 // Info about TransformFeedbacks currently in the system.
 class GPU_GLES2_EXPORT TransformFeedback : public IndexedBufferBindingHost {
  public:
-  TransformFeedback(
-      TransformFeedbackManager* manager, GLuint client_id, GLuint service_id);
+  TransformFeedback(TransformFeedbackManager* manager,
+                    GLuint client_id,
+                    GLuint service_id);
 
   // All the following functions do state update and call the underlying GL
   // function.  All validations have been done already and the GL function is
   // guaranteed to succeed.
-  void DoBindTransformFeedback(GLenum target);
+  void DoBindTransformFeedback(
+      GLenum target,
+      TransformFeedback* last_bound_transform_feedback);
   void DoBeginTransformFeedback(GLenum primitive_mode);
   void DoEndTransformFeedback();
   void DoPauseTransformFeedback();
diff --git a/gpu/command_buffer/service/vertex_array_manager.cc b/gpu/command_buffer/service/vertex_array_manager.cc
index a2f2e1ae..c3f5400 100644
--- a/gpu/command_buffer/service/vertex_array_manager.cc
+++ b/gpu/command_buffer/service/vertex_array_manager.cc
@@ -37,9 +37,11 @@
 VertexArrayManager::CreateVertexAttribManager(GLuint client_id,
                                               GLuint service_id,
                                               uint32_t num_vertex_attribs,
-                                              bool client_visible) {
+                                              bool client_visible,
+                                              bool do_buffer_refcounting) {
   scoped_refptr<VertexAttribManager> vertex_attrib_manager(
-    new VertexAttribManager(this, service_id, num_vertex_attribs));
+      new VertexAttribManager(this, service_id, num_vertex_attribs,
+                              do_buffer_refcounting));
 
   if (client_visible) {
     std::pair<VertexAttribManagerMap::iterator, bool> result =
diff --git a/gpu/command_buffer/service/vertex_array_manager.h b/gpu/command_buffer/service/vertex_array_manager.h
index 5ff5649..a3ab420f 100644
--- a/gpu/command_buffer/service/vertex_array_manager.h
+++ b/gpu/command_buffer/service/vertex_array_manager.h
@@ -35,7 +35,8 @@
       GLuint client_id,
       GLuint service_id,
       uint32_t num_vertex_attribs,
-      bool client_visible);
+      bool client_visible,
+      bool do_buffer_refcounting);
 
   // Gets the vertex attrib manager for the given vertex array.
   VertexAttribManager* GetVertexAttribManager(GLuint client_id);
diff --git a/gpu/command_buffer/service/vertex_array_manager_unittest.cc b/gpu/command_buffer/service/vertex_array_manager_unittest.cc
index ac645c8..daa37434 100644
--- a/gpu/command_buffer/service/vertex_array_manager_unittest.cc
+++ b/gpu/command_buffer/service/vertex_array_manager_unittest.cc
@@ -54,8 +54,8 @@
   const GLuint kClient2Id = 2;
 
   // Check we can create
-  manager_->CreateVertexAttribManager(
-      kClient1Id, kService1Id, kNumVertexAttribs, true);
+  manager_->CreateVertexAttribManager(kClient1Id, kService1Id,
+                                      kNumVertexAttribs, true, false);
   // Check creation success
   VertexAttribManager* info1 = manager_->GetVertexAttribManager(kClient1Id);
   ASSERT_TRUE(info1 != NULL);
@@ -81,8 +81,8 @@
   const GLuint kService1Id = 11;
   VertexArrayManager manager;
   // Check we can create
-  manager.CreateVertexAttribManager(
-      kClient1Id, kService1Id, kNumVertexAttribs, true);
+  manager.CreateVertexAttribManager(kClient1Id, kService1Id, kNumVertexAttribs,
+                                    true, false);
   // Check creation success
   VertexAttribManager* info1 = manager.GetVertexAttribManager(kClient1Id);
   ASSERT_TRUE(info1 != NULL);
diff --git a/gpu/command_buffer/service/vertex_attrib_manager.cc b/gpu/command_buffer/service/vertex_attrib_manager.cc
index bc61abd..673f36b3 100644
--- a/gpu/command_buffer/service/vertex_attrib_manager.cc
+++ b/gpu/command_buffer/service/vertex_attrib_manager.cc
@@ -65,12 +65,6 @@
   integer_ = integer;
 }
 
-void VertexAttrib::Unbind(Buffer* buffer) {
-  if (buffer_.get() == buffer) {
-    buffer_ = NULL;
-  }
-}
-
 bool VertexAttrib::CanAccess(GLuint index) const {
   if (!enabled_) {
     return true;
@@ -90,21 +84,25 @@
   return index < num_elements;
 }
 
-VertexAttribManager::VertexAttribManager()
+VertexAttribManager::VertexAttribManager(bool do_buffer_refcounting)
     : num_fixed_attribs_(0),
       element_array_buffer_(NULL),
       manager_(NULL),
       deleted_(false),
-      service_id_(0) {
-}
+      is_bound_(false),
+      do_buffer_refcounting_(do_buffer_refcounting),
+      service_id_(0) {}
 
 VertexAttribManager::VertexAttribManager(VertexArrayManager* manager,
                                          GLuint service_id,
-                                         uint32_t num_vertex_attribs)
+                                         uint32_t num_vertex_attribs,
+                                         bool do_buffer_refcounting)
     : num_fixed_attribs_(0),
       element_array_buffer_(NULL),
       manager_(manager),
       deleted_(false),
+      is_bound_(false),
+      do_buffer_refcounting_(do_buffer_refcounting),
       service_id_(service_id) {
   manager_->StartTracking(this);
   Initialize(num_vertex_attribs, false);
@@ -144,7 +142,11 @@
 }
 
 void VertexAttribManager::SetElementArrayBuffer(Buffer* buffer) {
+  if (do_buffer_refcounting_ && is_bound_ && element_array_buffer_)
+    element_array_buffer_->OnUnbind(GL_ELEMENT_ARRAY_BUFFER);
   element_array_buffer_ = buffer;
+  if (do_buffer_refcounting_ && is_bound_ && buffer)
+    buffer->OnBind(GL_ELEMENT_ARRAY_BUFFER);
 }
 
 bool VertexAttribManager::Enable(GLuint index, bool enable) {
@@ -167,11 +169,42 @@
 }
 
 void VertexAttribManager::Unbind(Buffer* buffer) {
+  if (!buffer)
+    return;
   if (element_array_buffer_.get() == buffer) {
-    element_array_buffer_ = NULL;
+    if (do_buffer_refcounting_ && is_bound_)
+      buffer->OnUnbind(GL_ELEMENT_ARRAY_BUFFER);
+    element_array_buffer_ = nullptr;
   }
   for (uint32_t vv = 0; vv < vertex_attribs_.size(); ++vv) {
-    vertex_attribs_[vv].Unbind(buffer);
+    if (vertex_attribs_[vv].buffer_ == buffer) {
+      if (do_buffer_refcounting_ && is_bound_)
+        buffer->OnUnbind(GL_ARRAY_BUFFER);
+      vertex_attribs_[vv].buffer_ = nullptr;
+    }
+  }
+}
+
+void VertexAttribManager::SetIsBound(bool is_bound) {
+  if (is_bound == is_bound_)
+    return;
+  is_bound_ = is_bound;
+  if (do_buffer_refcounting_) {
+    if (element_array_buffer_) {
+      if (is_bound)
+        element_array_buffer_->OnBind(GL_ELEMENT_ARRAY_BUFFER);
+      else
+        element_array_buffer_->OnUnbind(GL_ELEMENT_ARRAY_BUFFER);
+    }
+    for (const auto& va : vertex_attribs_) {
+      if (va.buffer_) {
+        if (is_bound) {
+          va.buffer_->OnBind(GL_ARRAY_BUFFER);
+        } else {
+          va.buffer_->OnUnbind(GL_ARRAY_BUFFER);
+        }
+      }
+    }
   }
 }
 
@@ -201,10 +234,9 @@
        it != enabled_vertex_attribs_.end(); ++it) {
     VertexAttrib* attrib = *it;
     Buffer* buffer = attrib->buffer();
-    if (!buffer_manager->RequestBufferAccess(
-            error_state, buffer, function_name,
-            "attached to enabled attrib %u",
-            attrib->index())) {
+    if (!buffer_manager->RequestBufferAccess(error_state, buffer, function_name,
+                                             "attached to enabled attrib %u",
+                                             attrib->index())) {
       return false;
     }
     const Program::VertexAttrib* attrib_info =
diff --git a/gpu/command_buffer/service/vertex_attrib_manager.h b/gpu/command_buffer/service/vertex_attrib_manager.h
index ff54986..f60539f 100644
--- a/gpu/command_buffer/service/vertex_attrib_manager.h
+++ b/gpu/command_buffer/service/vertex_attrib_manager.h
@@ -130,8 +130,6 @@
     divisor_ = divisor;
   }
 
-  void Unbind(Buffer* buffer);
-
   // The index of this attrib.
   GLuint index_;
 
@@ -185,7 +183,7 @@
  public:
   typedef std::list<VertexAttrib*> VertexAttribList;
 
-  VertexAttribManager();
+  explicit VertexAttribManager(bool do_buffer_refcounting);
 
   void Initialize(uint32_t num_vertex_attribs, bool init_attribs);
 
@@ -256,8 +254,12 @@
       if (type == GL_FIXED) {
         ++num_fixed_attribs_;
       }
+      if (do_buffer_refcounting_ && is_bound_ && attrib->buffer_)
+        attrib->buffer_->OnUnbind(GL_ARRAY_BUFFER);
       attrib->SetInfo(buffer, size, type, normalized, gl_stride, real_stride,
                       offset, integer);
+      if (do_buffer_refcounting_ && is_bound_ && buffer)
+        buffer->OnBind(GL_ARRAY_BUFFER);
     }
   }
 
@@ -300,6 +302,8 @@
       bool instanced,
       GLsizei primcount);
 
+  void SetIsBound(bool is_bound);
+
  private:
   friend class VertexArrayManager;
   friend class VertexArrayManagerTest;
@@ -308,7 +312,8 @@
   // Used when creating from a VertexArrayManager
   VertexAttribManager(VertexArrayManager* manager,
                       GLuint service_id,
-                      uint32_t num_vertex_attribs);
+                      uint32_t num_vertex_attribs,
+                      bool do_buffer_refcounting);
 
   ~VertexAttribManager();
 
@@ -346,6 +351,14 @@
   // True if deleted.
   bool deleted_;
 
+  // True if this is the currently bound VAO.
+  bool is_bound_;
+
+  // Whether or not to call Buffer::OnBind/OnUnbind whenever bindings change.
+  // This is only necessary for WebGL contexts to implement
+  // https://crbug.com/696345
+  bool do_buffer_refcounting_;
+
   // Service side vertex array object id.
   GLuint service_id_;
 };
diff --git a/gpu/command_buffer/service/vertex_attrib_manager_unittest.cc b/gpu/command_buffer/service/vertex_attrib_manager_unittest.cc
index 83dd567..c894c6a 100644
--- a/gpu/command_buffer/service/vertex_attrib_manager_unittest.cc
+++ b/gpu/command_buffer/service/vertex_attrib_manager_unittest.cc
@@ -38,7 +38,7 @@
           .RetiresOnSaturation();
     }
 
-    manager_ = new VertexAttribManager();
+    manager_ = new VertexAttribManager(false);
     manager_->Initialize(kNumVertexAttribs, true);
   }
 
diff --git a/gpu/command_buffer/tests/gl_map_buffer_range_unittest.cc b/gpu/command_buffer/tests/gl_map_buffer_range_unittest.cc
index 9b74792..be58ee3 100644
--- a/gpu/command_buffer/tests/gl_map_buffer_range_unittest.cc
+++ b/gpu/command_buffer/tests/gl_map_buffer_range_unittest.cc
@@ -459,7 +459,6 @@
   glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, 6, GL_MAP_READ_BIT);
   EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
 
-  glDrawArrays(GL_TRIANGLES, 0, 6);
   glEndTransformFeedback();
   GLTestHelper::CheckGLError("no errors", __LINE__);
 
diff --git a/gpu/config/gpu_test_config.cc b/gpu/config/gpu_test_config.cc
index fb7a835..6d4fd89 100644
--- a/gpu/config/gpu_test_config.cc
+++ b/gpu/config/gpu_test_config.cc
@@ -147,6 +147,8 @@
       build_type_ != kBuildTypeUnknown &&
       (build_type_ & config.build_type_) == 0)
     return false;
+  if (config.api() != kAPIUnknown && api_ != kAPIUnknown && api_ != config.api_)
+    return false;
   return true;
 }
 
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 4a10929..1a1fc28 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -115,6 +115,31 @@
 }
 
 builder_mixins {
+  name: "android-angle-try"
+  dimensions: "cores:8"
+  dimensions: "cpu:x86-64"
+  dimensions: "os:Ubuntu-14.04"
+  mixins: "angle-try"
+}
+
+builder_mixins {
+  name: "android-gpu-fyi-ci"
+  dimensions: "cores:8"
+  dimensions: "cpu:x86-64"
+  dimensions: "os:Ubuntu-14.04"
+  mixins: "gpu-fyi-ci"
+}
+
+builder_mixins {
+  name: "android-optional-gpu-try"
+  dimensions: "cores:8"
+  dimensions: "cpu:x86-64"
+  dimensions: "os:Ubuntu-14.04"
+  mixins: "gpu-optional-try"
+  mixins: "android-try"
+}
+
+builder_mixins {
   name: "angle-try"
   service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
   recipe {
@@ -329,6 +354,51 @@
     }
 
     builders {
+      name: "Android FYI 32 Vk Release (Nexus 5X)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI 64 Vk Release (Nexus 5X)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI dEQP Release (Nexus 5X)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (Nexus 5)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (Nexus 5X)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (Nexus 6)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (Nexus 6P)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (Nexus 9)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
+      name: "Android FYI Release (NVIDIA Shield TV)"
+      mixins: "android-gpu-fyi-ci"
+    }
+
+    builders {
       name: "Android x64 Builder (dbg)"
       mixins: "android-ci"
       dimensions: "os:Ubuntu-14.04"
@@ -802,6 +872,8 @@
     }
 
     # Keep builders sorted by OS, then name.
+    builders { mixins: "android-angle-try" name: "android_angle_rel_ng" }
+    builders { mixins: "android-angle-try" name: "android_angle_deqp_rel_ng" }
     builders {
       mixins: "android-try"
       name: "android_arm64_dbg_recipe"
@@ -814,6 +886,7 @@
       dimensions: "os:Ubuntu-14.04"
       dimensions: "cores:8"
     }
+    builders { mixins: "android-optional-gpu-try" name: "android_optional_gpu_tests_rel" }
 
     builders { mixins: "linux-try" name: "cast_shell_audio_linux" }
     builders { mixins: "linux-try" name: "cast_shell_linux" }
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 80995c6..54a1798 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -1647,6 +1647,98 @@
     short_name: "(ಥ_ಥ)"
   }
 
+  # GPU FYI Android bots, prefixed with GPU FYI|Android
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 5)"
+    category: "GPU FYI|Android|L32|N5"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 5)"
+    category: "GPU FYI|Android|L32|N5"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 6)"
+    category: "GPU FYI|Android|L32|N6"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 6)"
+    category: "GPU FYI|Android|L32|N6"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 5X)"
+    category: "GPU FYI|Android|M64|QCOM|N5X"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 5X)"
+    category: "GPU FYI|Android|M64|QCOM|N5X"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 6P)"
+    category: "GPU FYI|Android|M64|QCOM|N6P"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 6P)"
+    category: "GPU FYI|Android|M64|QCOM|N6P"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 9)"
+    category: "GPU FYI|Android|M64|NVDA|N9"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 9)"
+    category: "GPU FYI|Android|M64|NVDA|N9"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (NVIDIA Shield TV)"
+    category: "GPU FYI|Android|N64|NVDA|STV"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI Release (NVIDIA Shield TV)"
+    category: "GPU FYI|Android|N64|NVDA|STV"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI dEQP Release (Nexus 5X)"
+    category: "GPU FYI|Android|dqp|M64|N5X"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI dEQP Release (Nexus 5X)"
+    category: "GPU FYI|Android|dqp|M64|N5X"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI 32 Vk Release (Nexus 5X)"
+    category: "GPU FYI|Android|vk|O32|N5X"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI 32 Vk Release (Nexus 5X)"
+    category: "GPU FYI|Android|vk|O32|N5X"
+    short_name: "bb"
+  }
+  builders: {
+    name: "buildbucket/luci.chromium.ci/Android FYI 64 Vk Release (Nexus 5X)"
+    category: "GPU FYI|Android|vk|O64|N5X"
+    short_name: "ci"
+  }
+  builders: {
+    name: "buildbot/chromium.gpu.fyi/Android FYI 64 Vk Release (Nexus 5X)"
+    category: "GPU FYI|Android|vk|O64|N5X"
+    short_name: "bb"
+  }
+
   # GPU FYI Linux bots, prefixed with GPU FYI|Linux
   builders: {
     name: "buildbucket/luci.chromium.ci/GPU FYI Linux Builder"
@@ -3464,46 +3556,55 @@
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 5)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 5)"
     category: "Android|L32"
     short_name: "N5"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 6)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 6)"
     category: "Android|L32"
     short_name: "N6"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 5X)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 5X)"
     category: "Android|M64|QCOM"
     short_name: "N5X"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 6P)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 6P)"
     category: "Android|M64|QCOM"
     short_name: "N6P"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (Nexus 9)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (Nexus 9)"
     category: "Android|M64|NVDA"
     short_name: "N9"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI Release (NVIDIA Shield TV)"
+    name: "buildbucket/luci.chromium.ci/Android FYI Release (NVIDIA Shield TV)"
     category: "Android|N64|NVDA"
     short_name: "STV"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI dEQP Release (Nexus 5X)"
+    name: "buildbucket/luci.chromium.ci/Android FYI dEQP Release (Nexus 5X)"
     category: "Android|dqp|M64"
     short_name: "N5X"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI 32 Vk Release (Nexus 5X)"
+    name: "buildbucket/luci.chromium.ci/Android FYI 32 Vk Release (Nexus 5X)"
     category: "Android|vk|O32"
     short_name: "N5X"
   }
   builders: {
     name: "buildbot/chromium.gpu.fyi/Android FYI 64 Vk Release (Nexus 5X)"
+    name: "buildbucket/luci.chromium.ci/Android FYI 64 Vk Release (Nexus 5X)"
     category: "Android|vk|O64"
     short_name: "N5X"
   }
@@ -4963,6 +5064,9 @@
     name: "buildbucket/luci.chromium.try/android_n5x_swarming_dbg"
   }
   builders: {
+    name: "buildbucket/luci.chromium.try/android_optional_gpu_tests_rel"
+  }
+  builders: {
     name: "buildbucket/luci.chromium.try/cast_shell_audio_linux"
   }
   builders: {
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index 8eef5b5b..2d7f510 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -53,8 +53,18 @@
 
   # Android. Sorted alphabetically.
   triggers: "Android arm64 Builder (dbg)"
+  triggers: "Android FYI 32 Vk Release (Nexus 5X)"
+  triggers: "Android FYI 64 Vk Release (Nexus 5X)"
+  triggers: "Android FYI dEQP Release (Nexus 5X)"
+  triggers: "Android FYI Release (Nexus 5)"
+  triggers: "Android FYI Release (Nexus 5X)"
+  triggers: "Android FYI Release (Nexus 6)"
+  triggers: "Android FYI Release (Nexus 6P)"
+  triggers: "Android FYI Release (Nexus 9)"
+  triggers: "Android FYI Release (NVIDIA Shield TV)"
   triggers: "Android x64 Builder (dbg)"
   triggers: "Android x86 Builder (dbg)"
+  triggers: "Optional Android Release (Nexus 5X)"
 
   # Linux. Sorted alphabetically.
   triggers: "Cast Linux"
@@ -110,6 +120,96 @@
 }
 
 job {
+  id: "Android FYI 32 Vk Release (Nexus 5X)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI 32 Vk Release (Nexus 5X)"
+  }
+}
+
+job {
+  id: "Android FYI 64 Vk Release (Nexus 5X)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI 64 Vk Release (Nexus 5X)"
+  }
+}
+
+job {
+  id: "Android FYI dEQP Release (Nexus 5X)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI dEQP Release (Nexus 5X)"
+  }
+}
+
+job {
+  id: "Android FYI Release (Nexus 5)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (Nexus 5)"
+  }
+}
+
+job {
+  id: "Android FYI Release (Nexus 5X)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (Nexus 5X)"
+  }
+}
+
+job {
+  id: "Android FYI Release (Nexus 6)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (Nexus 6)"
+  }
+}
+
+job {
+  id: "Android FYI Release (Nexus 6P)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (Nexus 6P)"
+  }
+}
+
+job {
+  id: "Android FYI Release (Nexus 9)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (Nexus 9)"
+  }
+}
+
+job {
+  id: "Android FYI Release (NVIDIA Shield TV)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI Release (NVIDIA Shield TV)"
+  }
+}
+
+job {
   id: "Android WebView N (dbg)"
   # triggered by "Android arm64 Builder (dbg)"
   acl_sets: "triggered-by-parent-builders"
@@ -193,6 +293,13 @@
 }
 
 job {
+  id: "Optional Android Release (Nexus 5X)"
+  acl_sets: "default"
+  # This bot doesn't actually exist, so it's noop'ed out. crbug.com/819899
+  noop: {}
+}
+
+job {
   id: "Oreo Phone Tester"
   # triggered by "Android arm64 Builder (dbg)"
   acl_sets: "triggered-by-parent-builders"
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn b/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
index 303fc94..3650a8b 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
@@ -33,6 +33,7 @@
     "//base",
     "//components/browser_sync",
     "//components/sessions",
+    "//components/strings",
     "//components/sync",
     "//ios/chrome/app/strings",
     "//ios/chrome/app/theme",
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
index 0b58127..3d66bae 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.mm
@@ -9,10 +9,11 @@
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
 #include "components/browser_sync/profile_sync_service.h"
+#include "components/sessions/core/tab_restore_service.h"
+#include "components/strings/grit/components_strings.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync_sessions/open_tabs_ui_delegate.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
-#include "ios/chrome/browser/experimental_flags.h"
 #import "ios/chrome/browser/metrics/new_tab_page_uma.h"
 #include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
 #include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
@@ -28,24 +29,16 @@
 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_constants.h"
 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_view_controller.h"
 #include "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/generic_section_header_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/header_of_collapsable_section_protocol.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/session_section_header_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/session_tab_data_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/show_full_history_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_in_progress_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_off_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_on_no_sessions_view.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/views/spacers_view.h"
 #import "ios/chrome/browser/ui/settings/sync_utils/sync_presenter.h"
 #import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_url_item.h"
 #include "ios/chrome/browser/ui/ui_util.h"
 #import "ios/chrome/browser/ui/url_loader.h"
-#import "ios/chrome/browser/ui/util/constraints_ui_util.h"
 #import "ios/chrome/browser/ui/util/top_view_controller.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
 #include "ios/chrome/grit/ios_strings.h"
-#include "ios/web/public/referrer.h"
 #import "ios/web/public/web_state/context_menu_params.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -55,40 +48,34 @@
 
 namespace {
 
+typedef NS_ENUM(NSInteger, SectionIdentifier) {
+  SectionIdentifierRecentlyClosedTabs = kSectionIdentifierEnumZero,
+  SectionIdentifierOtherDevices,
+  // The first SessionsSectionIdentifier index.
+  kFirstSessionSectionIdentifier,
+};
+
+typedef NS_ENUM(NSInteger, ItemType) {
+  ItemTypeRecentlyClosedHeader = kItemTypeEnumZero,
+  ItemTypeRecentlyClosed,
+  ItemTypeOtherDevicesHeader,
+  ItemTypeOtherDevicesSyncOff,
+  ItemTypeOtherDevicesNoSessions,
+  ItemTypeOtherDevicesSigninPromo,
+  ItemTypeOtherDevicesSyncInProgress,
+  ItemTypeSessionHeader,
+  ItemTypeSessionTabData,
+  ItemTypeShowFullHistory,
+};
+
 // Key for saving whether the Other Device section is collapsed.
 NSString* const kOtherDeviceCollapsedKey = @"OtherDevicesCollapsed";
-
 // Key for saving whether the Recently Closed section is collapsed.
 NSString* const kRecentlyClosedCollapsedKey = @"RecentlyClosedCollapsed";
-
-// Tag to extract the section headers from the cells.
-enum { kSectionHeader = 1 };
-
-// Margin at the top of the sigin-in promo view.
-const CGFloat kSigninPromoViewTopMargin = 24;
-
-// Types of sections.
-enum SectionType {
-  SEPARATOR_SECTION,
-  CLOSED_TAB_SECTION,
-  OTHER_DEVICES_SECTION,
-  SESSION_SECTION,
-};
-
-// Types of cells.
-enum CellType {
-  CELL_CLOSED_TAB_SECTION_HEADER,
-  CELL_CLOSED_TAB_DATA,
-  CELL_SHOW_FULL_HISTORY,
-  CELL_SEPARATOR,
-  CELL_OTHER_DEVICES_SECTION_HEADER,
-  CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF,
-  CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS,
-  CELL_OTHER_DEVICES_SIGNIN_PROMO,
-  CELL_OTHER_DEVICES_SYNC_IN_PROGRESS,
-  CELL_SESSION_SECTION_HEADER,
-  CELL_SESSION_TAB_DATA,
-};
+// There are 2 static sections before the first SessionSection.
+int const kNumberOfSectionsBeforeSessions = 2;
+// Estimated Table Row height.
+const CGFloat kEstimatedRowHeight = 56;
 
 }  // namespace
 
@@ -120,14 +107,9 @@
 
 #pragma mark - Public Interface
 
-- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
-                              loader:(id<UrlLoader>)loader
-                          dispatcher:(id<ApplicationCommands>)dispatcher {
+- (instancetype)init {
   self = [super initWithStyle:UITableViewStylePlain];
   if (self) {
-    _dispatcher = dispatcher;
-    _loader = loader;
-    _browserState = browserState;
     _sessionState = SessionsSyncUserState::USER_SIGNED_OUT;
     _syncedSessions.reset(new synced_sessions::SyncedSessions());
   }
@@ -140,14 +122,14 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
+  [self loadModel];
   self.view.accessibilityIdentifier =
       kRecentTabsTableViewControllerAccessibilityIdentifier;
-  self.tableView.rowHeight = UITableViewAutomaticDimension;
-  self.tableView.estimatedRowHeight =
-      [SessionTabDataView desiredHeightInUITableViewCell];
-  [self.tableView setSeparatorColor:[UIColor clearColor]];
-  [self.tableView setDataSource:self];
   [self.tableView setDelegate:self];
+  self.tableView.estimatedRowHeight = kEstimatedRowHeight;
+  self.tableView.rowHeight = UITableViewAutomaticDimension;
+  self.tableView.estimatedSectionHeaderHeight = kEstimatedRowHeight;
+  self.tableView.sectionFooterHeight = 0.0;
   UILongPressGestureRecognizer* longPress =
       [[UILongPressGestureRecognizer alloc]
           initWithTarget:self
@@ -156,91 +138,360 @@
   [self.tableView addGestureRecognizer:longPress];
 }
 
-- (SectionType)sectionType:(NSInteger)section {
-  if (section == 0) {
-    return CLOSED_TAB_SECTION;
-  }
-  if (section == 1) {
-    return SEPARATOR_SECTION;
-  }
-  if (section < [self numberOfSectionsBeforeSessionOrOtherDevicesSections]) {
-    return CLOSED_TAB_SECTION;
-  }
+#pragma mark - TableViewModel
+
+- (void)loadModel {
+  [super loadModel];
+  [self addRecentlyClosedSection];
+
+  // The other Devices header section is always present regardless if it has any
+  // items or not.
+  [self addOtherDevicesSectionHeader];
   if (self.sessionState ==
       SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
-    return SESSION_SECTION;
-  }
-  // Other cases of recent_tabs::USER_SIGNED_IN_SYNC_OFF,
-  // recent_tabs::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS, and
-  // recent_tabs::USER_SIGNED_OUT falls through to here.
-  return OTHER_DEVICES_SECTION;
-}
-
-- (CellType)cellType:(NSIndexPath*)indexPath {
-  SectionType sectionType = [self sectionType:indexPath.section];
-  switch (sectionType) {
-    case CLOSED_TAB_SECTION:
-      if (indexPath.row == 0) {
-        return CELL_CLOSED_TAB_SECTION_HEADER;
-      }
-      // The last cell of the section is to access the history panel.
-      if (indexPath.row ==
-          [self numberOfCellsInRecentlyClosedTabsSection] - 1) {
-        return CELL_SHOW_FULL_HISTORY;
-      }
-      return CELL_CLOSED_TAB_DATA;
-    case SEPARATOR_SECTION:
-      return CELL_SEPARATOR;
-    case SESSION_SECTION:
-      if (indexPath.row == 0) {
-        return CELL_SESSION_SECTION_HEADER;
-      }
-      return CELL_SESSION_TAB_DATA;
-    case OTHER_DEVICES_SECTION:
-      if (self.sessionState ==
-          SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS) {
-        return CELL_OTHER_DEVICES_SYNC_IN_PROGRESS;
-      }
-      if (indexPath.row == 0) {
-        return CELL_OTHER_DEVICES_SECTION_HEADER;
-      }
-      switch (self.sessionState) {
-        case SessionsSyncUserState::USER_SIGNED_OUT:
-          return CELL_OTHER_DEVICES_SIGNIN_PROMO;
-        case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
-          return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF;
-        case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-          return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS;
-        case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
-        case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
-          NOTREACHED();
-          // These cases should never occur. Still, this method needs to
-          // return _something_, so it's returning the least wrong cell type.
-          return CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS;
-      }
+    [self addSessionSections];
+  } else {
+    [self addOtherDevicesItemForState:self.sessionState];
   }
 }
 
-- (NSInteger)sectionIndexForSectionType:(SectionType)sectionType {
-  NSUInteger sectionCount = [self numberOfSectionsInTableView:self.tableView];
-  for (NSUInteger sectionIndex = 0; sectionIndex < sectionCount;
-       ++sectionIndex) {
-    if ([self sectionType:sectionIndex] == sectionType)
-      return sectionIndex;
+#pragma mark Recently Closed Section
+
+- (void)addRecentlyClosedSection {
+  TableViewModel* model = self.tableViewModel;
+
+  // Recently Closed Section.
+  [model addSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs];
+  TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc]
+      initWithType:ItemTypeRecentlyClosedHeader];
+  header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_RECENTLY_CLOSED);
+  [model setHeader:header
+      forSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs];
+
+  // Add Recently Closed Tabs Cells.
+  [self addRecentlyClosedTabItems];
+
+  // Add show full history item last.
+  TableViewURLItem* historyItem =
+      [[TableViewURLItem alloc] initWithType:ItemTypeShowFullHistory];
+  historyItem.title = l10n_util::GetNSString(IDS_HISTORY_SHOWFULLHISTORY_LINK);
+  historyItem.favicon = [UIImage imageNamed:@"ntp_opentabs_clock"];
+  [model addItem:historyItem
+      toSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs];
+}
+
+- (void)addRecentlyClosedTabItems {
+  if (!self.tabRestoreService)
+    return;
+
+  // Note that std:list<> can only be accessed sequentially, which is
+  // suboptimal when using Cocoa table APIs. This list doesn't appear
+  // to get very long, so it probably won't matter for perf.
+  for (auto iter = self.tabRestoreService->entries().begin();
+       iter != self.tabRestoreService->entries().end(); ++iter) {
+    DCHECK(iter->get());
+    const sessions::TabRestoreService::Entry* entry = iter->get();
+    DCHECK(entry);
+    // Use the page's title for the label, or its URL if title is empty.
+    NSString* entryTitle;
+    NSString* entryURL = @"";
+    GURL entryGURL;
+    switch (entry->type) {
+      case sessions::TabRestoreService::TAB: {
+        const sessions::TabRestoreService::Tab* tab =
+            static_cast<const sessions::TabRestoreService::Tab*>(entry);
+        const sessions::SerializedNavigationEntry& entry =
+            tab->navigations[tab->current_navigation_index];
+        // Use the page's title for the label, or its URL if title is empty.
+        if (entry.title().size()) {
+          entryTitle = base::SysUTF16ToNSString(entry.title());
+          entryURL = base::SysUTF8ToNSString(entry.virtual_url().spec());
+        } else {
+          entryTitle = base::SysUTF8ToNSString(entry.virtual_url().spec());
+        }
+        entryGURL = entry.virtual_url();
+        break;
+      }
+      case sessions::TabRestoreService::WINDOW: {
+        // Only TAB type is handled.
+        entryTitle = @"Window type - NOTIMPLEMENTED";
+        break;
+      }
+    }
+
+    // Configure and add the Item.
+    TableViewURLItem* recentlyClosedTab =
+        [[TableViewURLItem alloc] initWithType:ItemTypeRecentlyClosed];
+    recentlyClosedTab.favicon = [UIImage imageNamed:@"default_favicon"];
+    recentlyClosedTab.title = entryTitle;
+    recentlyClosedTab.URL = entryURL;
+    [self.tableViewModel addItem:recentlyClosedTab
+         toSectionWithIdentifier:SectionIdentifierRecentlyClosedTabs];
   }
-  return NSNotFound;
 }
 
-- (NSInteger)numberOfSectionsBeforeSessionOrOtherDevicesSections {
-  // The 2 sections are CLOSED_TAB_SECTION and SEPARATOR_SECTION.
-  return 2;
+#pragma mark Sessions Section
+
+// Cleans up the model in order to update the Session sections. Needs to be
+// called inside a [UITableView beginUpdates] block on iOS10, or
+// performBatchUpdates on iOS11+.
+- (void)updateSessionSections {
+  SessionsSyncUserState previousState = self.sessionState;
+  if (previousState !=
+      SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+    // The previous state was one of the OtherDevices states, remove it to clean
+    // it up and re-add just the header.
+    [self.tableViewModel
+        removeSectionWithIdentifier:SectionIdentifierOtherDevices];
+    [self addOtherDevicesSectionHeader];
+    // Reload the TableViews section.
+    [self.tableView reloadSections:[self otherDevicesSectionIndexSet]
+                  withRowAnimation:UITableViewRowAnimationNone];
+  }
+
+  // Clean up any previously added SessionSections.
+  [self removeSessionSections];
+
+  // Re-Add the session sections to |self.tableViewModel| and insert them into
+  // |self.tableView|.
+  [self addSessionSections];
+  [self.tableView insertSections:[self sessionSectionIndexSet]
+                withRowAnimation:UITableViewRowAnimationNone];
 }
 
-- (void)dismissModals {
-  [self.contextMenuCoordinator stop];
+// Adds all the Remote Sessions sections with its respective items.
+- (void)addSessionSections {
+  TableViewModel* model = self.tableViewModel;
+  for (NSUInteger i = 0; i < [self numberOfSessions]; i++) {
+    synced_sessions::DistantSession const* session =
+        _syncedSessions->GetSession(i);
+    NSInteger sessionIdentifier = [self sectionIdentifierForSession:session];
+    [model addSectionWithIdentifier:sessionIdentifier];
+    TableViewTextHeaderFooterItem* header =
+        [[TableViewTextHeaderFooterItem alloc]
+            initWithType:ItemTypeSessionHeader];
+    header.text = base::SysUTF8ToNSString(session->name);
+    [model setHeader:header forSectionWithIdentifier:sessionIdentifier];
+    [self addItemsForSession:session];
+  }
 }
 
-#pragma mark - Recently closed tab helpers
+- (void)addItemsForSession:(synced_sessions::DistantSession const*)session {
+  TableViewModel* model = self.tableViewModel;
+  NSInteger numberOfTabs = static_cast<NSInteger>(session->tabs.size());
+  for (int i = 0; i < numberOfTabs; i++) {
+    synced_sessions::DistantTab const* sessionTab = session->tabs[i].get();
+    NSString* title = base::SysUTF16ToNSString(sessionTab->title);
+    GURL url = sessionTab->virtual_url;
+
+    TableViewURLItem* sessionTabItem =
+        [[TableViewURLItem alloc] initWithType:ItemTypeSessionTabData];
+    sessionTabItem.favicon = [UIImage imageNamed:@"default_favicon"];
+    sessionTabItem.title = title;
+    sessionTabItem.URL = base::SysUTF8ToNSString(url.host());
+    [model addItem:sessionTabItem
+        toSectionWithIdentifier:[self sectionIdentifierForSession:session]];
+  }
+}
+
+// Remove all SessionSections from |self.tableViewModel| and |self.tableView|
+// Needs to be called inside a [UITableView beginUpdates] block on iOS10, or
+// performBatchUpdates on iOS11+.
+- (void)removeSessionSections {
+  // Get the numberOfSessionSections from |self.tableViewModel|, since
+  // |_syncedSessions| has been updated by now.
+  int numberOfSessionSections =
+      [self.tableViewModel numberOfSections] - kNumberOfSectionsBeforeSessions;
+  for (int i = 0; i < numberOfSessionSections; i++) {
+    [self.tableView
+          deleteSections:[NSIndexSet
+                             indexSetWithIndex:i +
+                                               kNumberOfSectionsBeforeSessions]
+        withRowAnimation:UITableViewRowAnimationNone];
+    [self.tableViewModel
+        removeSectionWithIdentifier:i + kFirstSessionSectionIdentifier];
+  }
+}
+
+#pragma mark Other Devices Section
+
+// Cleans up the model in order to update the Other devices section. Needs to be
+// called inside a [UITableView beginUpdates] block on iOS10, or
+// performBatchUpdates on iOS11+.
+- (void)updateOtherDevicesSectionForState:(SessionsSyncUserState)newState {
+  TableViewModel* model = self.tableViewModel;
+  SessionsSyncUserState previousState = self.sessionState;
+  switch (previousState) {
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+    case SessionsSyncUserState::USER_SIGNED_OUT:
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
+      // The OtherDevices section will be updated, remove it in order to clean
+      // it up. After the clean up add just the header.
+      [model removeSectionWithIdentifier:SectionIdentifierOtherDevices];
+      [self addOtherDevicesSectionHeader];
+      break;
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
+      // The Sessions Section exists, remove it.
+      [self removeSessionSections];
+      break;
+  }
+  // Add updated OtherDevices Item.
+  [self addOtherDevicesItemForState:newState];
+
+  // Update OtherDevices TableView Section.
+  [self.tableView reloadSections:[self otherDevicesSectionIndexSet]
+                withRowAnimation:UITableViewRowAnimationNone];
+}
+
+// Adds Other Devices Section and its header.
+- (void)addOtherDevicesSectionHeader {
+  TableViewModel* model = self.tableViewModel;
+  [model addSectionWithIdentifier:SectionIdentifierOtherDevices];
+  TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc]
+      initWithType:ItemTypeRecentlyClosedHeader];
+  header.text = l10n_util::GetNSString(IDS_IOS_RECENT_TABS_OTHER_DEVICES);
+  [model setHeader:header
+      forSectionWithIdentifier:SectionIdentifierOtherDevices];
+}
+
+// Adds Other Devices item for |state|.
+- (void)addOtherDevicesItemForState:(SessionsSyncUserState)state {
+  // Add item depending on |state|.
+  TableViewTextItem* dummyCell = nil;
+  switch (state) {
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
+      NOTREACHED();
+      return;
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
+      dummyCell =
+          [[TableViewTextItem alloc] initWithType:ItemTypeOtherDevicesSyncOff];
+      dummyCell.text =
+          l10n_util::GetNSString(IDS_IOS_OPEN_TABS_ENABLE_SYNC_MOBILE);
+      break;
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
+      dummyCell = [[TableViewTextItem alloc]
+          initWithType:ItemTypeOtherDevicesNoSessions];
+      dummyCell.text =
+          l10n_util::GetNSString(IDS_IOS_OPEN_TABS_NO_SESSION_INSTRUCTIONS);
+      break;
+    case SessionsSyncUserState::USER_SIGNED_OUT:
+      dummyCell = [[TableViewTextItem alloc]
+          initWithType:ItemTypeOtherDevicesSigninPromo];
+      dummyCell.text = l10n_util::GetNSString(IDS_IOS_SIGNIN_PROMO_RECENT_TABS);
+      break;
+    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
+      dummyCell = [[TableViewTextItem alloc]
+          initWithType:ItemTypeOtherDevicesSyncInProgress];
+      dummyCell.text = @"Sync in progress";
+  }
+  [self.tableViewModel addItem:dummyCell
+       toSectionWithIdentifier:SectionIdentifierOtherDevices];
+}
+
+#pragma mark - TableViewModel Helpers
+
+// Ordered array of all section identifiers.
+- (NSArray*)allSessionSectionIdentifiers {
+  NSMutableArray* allSessionSectionIdentifiers = [[NSMutableArray alloc] init];
+  for (NSUInteger i = 0; i < [self numberOfSessions]; i++) {
+    [allSessionSectionIdentifiers
+        addObject:@(i + kFirstSessionSectionIdentifier)];
+  }
+  return allSessionSectionIdentifiers;
+}
+
+// Returns the TableViewModel SectionIdentifier for |distantSession|. Returns -1
+// if |distantSession| doesn't exists.
+- (NSInteger)sectionIdentifierForSession:
+    (synced_sessions::DistantSession const*)distantSession {
+  for (NSUInteger i = 0; i < [self numberOfSessions]; i++) {
+    synced_sessions::DistantSession const* session =
+        _syncedSessions->GetSession(i);
+    if (session->tag == distantSession->tag)
+      return i + kFirstSessionSectionIdentifier;
+  }
+  NOTREACHED();
+  return -1;
+}
+
+// Returns an IndexSet containing the Other Devices Section.
+- (NSIndexSet*)otherDevicesSectionIndexSet {
+  NSUInteger otherDevicesSection = [self.tableViewModel
+      sectionForSectionIdentifier:SectionIdentifierOtherDevices];
+  return [NSIndexSet indexSetWithIndex:otherDevicesSection];
+}
+
+// Returns an IndexSet containing the all the Session Sections.
+- (NSIndexSet*)sessionSectionIndexSet {
+  // Create a range of all Session Sections.
+  NSRange rangeOfSessionSections =
+      NSMakeRange(kNumberOfSectionsBeforeSessions, [self numberOfSessions]);
+  NSIndexSet* sessionSectionIndexes =
+      [NSIndexSet indexSetWithIndexesInRange:rangeOfSessionSections];
+  return sessionSectionIndexes;
+}
+
+// Returns YES if |sectionIdentifier| is a Sessions sectionIdentifier.
+- (BOOL)isSessionSectionIdentifier:(NSInteger)sectionIdentifier {
+  NSArray* sessionSectionIdentifiers = [self allSessionSectionIdentifiers];
+  NSNumber* sectionIdentifierObject = @(sectionIdentifier);
+  return [sessionSectionIdentifiers containsObject:sectionIdentifierObject];
+}
+
+#pragma mark - Consumer Protocol
+
+- (void)refreshUserState:(SessionsSyncUserState)newSessionState {
+  if ((newSessionState == self.sessionState &&
+       self.sessionState !=
+           SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) ||
+      self.signinPromoViewMediator.isSigninInProgress) {
+    // No need to refresh the sections since all states other than
+    // USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS only have static content. This means
+    // that if the previous State is the same as the new one the static content
+    // won't change.
+    return;
+  }
+  syncer::SyncService* syncService =
+      IOSChromeProfileSyncServiceFactory::GetForBrowserState(self.browserState);
+  _syncedSessions.reset(new synced_sessions::SyncedSessions(syncService));
+
+  // Update the TableView and TableViewModel sections to match the new
+  // sessionState.
+  // Turn Off animations since UITableViewRowAnimationNone still animates.
+  [UIView setAnimationsEnabled:NO];
+  // If iOS11+ use performBatchUpdates: instead of begin/endUpdates.
+  if (@available(iOS 11, *)) {
+    if (newSessionState ==
+        SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+      [self.tableView performBatchUpdates:^{
+        [self updateSessionSections];
+      }
+                               completion:nil];
+    } else {
+      [self.tableView performBatchUpdates:^{
+        [self updateOtherDevicesSectionForState:newSessionState];
+      }
+                               completion:nil];
+    }
+  } else {
+    [self.tableView beginUpdates];
+    if (newSessionState ==
+        SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+      [self updateSessionSections];
+    } else {
+      [self updateOtherDevicesSectionForState:newSessionState];
+    }
+    [self.tableView endUpdates];
+  }
+  [UIView setAnimationsEnabled:YES];
+
+  self.sessionState = newSessionState;
+  if (self.sessionState != SessionsSyncUserState::USER_SIGNED_OUT) {
+    [self.signinPromoViewMediator signinPromoViewRemoved];
+    self.signinPromoViewMediator = nil;
+  }
+}
 
 - (void)refreshRecentlyClosedTabs {
   [self.tableView reloadData];
@@ -250,22 +501,54 @@
   _tabRestoreService = tabRestoreService;
 }
 
-- (NSInteger)numberOfCellsInRecentlyClosedTabsSection {
-  // + 2 because of the section header, and the "Show full history" cell.
-  return [self numberOfRecentlyClosedTabs] + 2;
+- (void)dismissModals {
+  [self.contextMenuCoordinator stop];
 }
 
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView*)tableView
+    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
+  DCHECK_EQ(tableView, self.tableView);
+  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+  NSInteger itemTypeSelected =
+      [self.tableViewModel itemTypeForIndexPath:indexPath];
+  switch (itemTypeSelected) {
+    case ItemTypeRecentlyClosed:
+      [self
+          openTabWithTabRestoreEntry:[self
+                                         tabRestoreEntryAtIndexPath:indexPath]];
+      break;
+    case ItemTypeSessionTabData:
+      [self
+          openTabWithContentOfDistantTab:[self
+                                             distantTabAtIndexPath:indexPath]];
+      break;
+    case ItemTypeShowFullHistory:
+      [tableView deselectRowAtIndexPath:indexPath animated:NO];
+      [self showFullHistory];
+      break;
+    case ItemTypeOtherDevicesSyncOff:
+    case ItemTypeOtherDevicesNoSessions:
+    case ItemTypeOtherDevicesSigninPromo:
+    case ItemTypeOtherDevicesSyncInProgress:
+      break;
+  }
+}
+
+#pragma mark - Recently closed tab helpers
+
 - (NSInteger)numberOfRecentlyClosedTabs {
   if (!self.tabRestoreService)
     return 0;
   return static_cast<NSInteger>(self.tabRestoreService->entries().size());
 }
 
-- (const sessions::TabRestoreService::Entry*)tabRestoreEntryAtIndex:
+- (const sessions::TabRestoreService::Entry*)tabRestoreEntryAtIndexPath:
     (NSIndexPath*)indexPath {
-  DCHECK_EQ([self sectionType:indexPath.section], CLOSED_TAB_SECTION);
-  // "- 1" because of the section header.
-  NSInteger index = indexPath.row - 1;
+  DCHECK_EQ([self.tableViewModel sectionIdentifierForSection:indexPath.section],
+            SectionIdentifierRecentlyClosedTabs);
+  NSInteger index = indexPath.row;
   DCHECK_LE(index, [self numberOfRecentlyClosedTabs]);
   if (!self.tabRestoreService)
     return nullptr;
@@ -281,7 +564,52 @@
   return iter->get();
 }
 
-#pragma mark - Helpers to open tabs, or show the full history view.
+// TO IMPLEMENT. Remove this depending on which TableStyle we'll use.
+- (CGFloat)tableView:(UITableView*)tableView
+    heightForFooterInSection:(NSInteger)section {
+  return 1.0;
+}
+
+#pragma mark - Distant Sessions helpers
+
+- (NSUInteger)numberOfSessions {
+  if (!_syncedSessions)
+    return 0;
+  return _syncedSessions->GetSessionCount();
+}
+
+// Returns the Session Index for a given Session Tab |indexPath|.
+- (size_t)indexOfSessionForTabAtIndexPath:(NSIndexPath*)indexPath {
+  DCHECK_EQ([self.tableViewModel itemTypeForIndexPath:indexPath],
+            ItemTypeSessionTabData);
+  // Get the sectionIdentifier for |indexPath|,
+  NSNumber* sectionIdentifierForIndexPath =
+      @([self.tableViewModel sectionIdentifierForSection:indexPath.section]);
+  // Get the index of this sectionIdentifier.
+  size_t indexOfSession = [[self allSessionSectionIdentifiers]
+      indexOfObject:sectionIdentifierForIndexPath];
+  DCHECK_LT(indexOfSession, _syncedSessions->GetSessionCount());
+  return indexOfSession;
+}
+
+- (synced_sessions::DistantSession const*)sessionForTabAtIndexPath:
+    (NSIndexPath*)indexPath {
+  return _syncedSessions->GetSession(
+      [self indexOfSessionForTabAtIndexPath:indexPath]);
+}
+
+- (synced_sessions::DistantTab const*)distantTabAtIndexPath:
+    (NSIndexPath*)indexPath {
+  DCHECK_EQ([self.tableViewModel itemTypeForIndexPath:indexPath],
+            ItemTypeSessionTabData);
+  size_t indexOfDistantTab = indexPath.row;
+  synced_sessions::DistantSession const* session =
+      [self sessionForTabAtIndexPath:indexPath];
+  DCHECK_LT(indexOfDistantTab, session->tabs.size());
+  return session->tabs[indexOfDistantTab].get();
+}
+
+#pragma mark - Navigation helpers
 
 - (void)dismissRecentTabsModal {
   [self.handsetCommandHandler dismissRecentTabsWithCompletion:nil];
@@ -309,7 +637,7 @@
   DCHECK(entry);
   if (!entry)
     return;
-  // We only handle the TAB type.
+  // Only TAB type is handled.
   if (entry->type != sessions::TabRestoreService::TAB)
     return;
   TabRestoreServiceDelegateImplIOS* delegate =
@@ -324,16 +652,6 @@
                                            WindowOpenDisposition::CURRENT_TAB);
 }
 
-- (void)openTabWithURL:(const GURL&)url {
-  if (url.is_valid()) {
-    [self dismissRecentTabsModal];
-    [self.loader loadURL:url
-                 referrer:web::Referrer()
-               transition:ui::PAGE_TRANSITION_TYPED
-        rendererInitiated:NO];
-  }
-}
-
 - (void)showFullHistory {
   __weak RecentTabsTableViewController* weakSelf = self;
   ProceduralBlock openHistory = ^{
@@ -343,79 +661,10 @@
   [self.handsetCommandHandler dismissRecentTabsWithCompletion:openHistory];
 }
 
-#pragma mark - Handling of the collapsed sections.
+#pragma mark - Collapse sections
 
 - (void)toggleExpansionOfSection:(NSInteger)sectionIndex {
-  NSString* sectionCollapseKey = nil;
-  int cellCount = 0;
-
-  SectionType section = [self sectionType:sectionIndex];
-
-  switch (section) {
-    case CLOSED_TAB_SECTION:
-      sectionCollapseKey = kRecentlyClosedCollapsedKey;
-      // - 1 because the header does not count.
-      cellCount = [self numberOfCellsInRecentlyClosedTabsSection] - 1;
-      break;
-    case SEPARATOR_SECTION:
-      NOTREACHED();
-      return;
-    case OTHER_DEVICES_SECTION:
-      cellCount = 1;
-      sectionCollapseKey = kOtherDeviceCollapsedKey;
-      break;
-    case SESSION_SECTION: {
-      size_t indexOfSession =
-          sectionIndex -
-          [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
-      DCHECK_LT(indexOfSession, _syncedSessions->GetSessionCount());
-      synced_sessions::DistantSession const* distantSession =
-          _syncedSessions->GetSession(indexOfSession);
-      cellCount = distantSession->tabs.size();
-      sectionCollapseKey = [self keyForDistantSession:distantSession];
-      break;
-    }
-  }
-  DCHECK(sectionCollapseKey);
-  BOOL collapsed = ![self sectionIsCollapsed:sectionCollapseKey];
-  [self setSection:sectionCollapseKey collapsed:collapsed];
-
-  // Builds an array indexing all the cells needing to be removed or inserted to
-  // collapse/expand the section.
-  NSMutableArray* cellIndexPathsToDeleteOrInsert = [NSMutableArray array];
-  for (int i = 1; i <= cellCount; i++) {
-    NSIndexPath* tabIndexPath =
-        [NSIndexPath indexPathForRow:i inSection:sectionIndex];
-    [cellIndexPathsToDeleteOrInsert addObject:tabIndexPath];
-  }
-
-  // Update the table view.
-  [self.tableView beginUpdates];
-  if (collapsed) {
-    [self.tableView deleteRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
-                          withRowAnimation:UITableViewRowAnimationFade];
-  } else {
-    [self.tableView insertRowsAtIndexPaths:cellIndexPathsToDeleteOrInsert
-                          withRowAnimation:UITableViewRowAnimationFade];
-  }
-  [self.tableView endUpdates];
-
-  // Rotate disclosure icon.
-  NSIndexPath* sectionCellIndexPath =
-      [NSIndexPath indexPathForRow:0 inSection:sectionIndex];
-  UITableViewCell* sectionCell =
-      [self.tableView cellForRowAtIndexPath:sectionCellIndexPath];
-  UIView* subview = [sectionCell viewWithTag:kSectionHeader];
-  DCHECK([subview
-      conformsToProtocol:@protocol(HeaderOfCollapsableSectionProtocol)]);
-  id<HeaderOfCollapsableSectionProtocol> headerView =
-      static_cast<id<HeaderOfCollapsableSectionProtocol>>(subview);
-  [headerView setSectionIsCollapsed:collapsed animated:YES];
-}
-
-- (NSString*)keyForDistantSession:
-    (synced_sessions::DistantSession const*)distantSession {
-  return base::SysUTF8ToNSString(distantSession->tag);
+  // TO IMPLEMENT!
 }
 
 - (void)setSection:(NSString*)sectionKey collapsed:(BOOL)collapsed {
@@ -442,95 +691,6 @@
   return [value boolValue];
 }
 
-#pragma mark - Distant Sessions helpers
-
-- (void)refreshUserState:(SessionsSyncUserState)newSessionState {
-  if ((newSessionState == self.sessionState &&
-       self.sessionState !=
-           SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) ||
-      self.signinPromoViewMediator.isSigninInProgress) {
-    // No need to refresh the sections.
-    return;
-  }
-
-  [self.tableView beginUpdates];
-  NSIndexSet* indexesToBeDeleted = [self sessionOrOtherDevicesSectionsIndexes];
-  [self.tableView deleteSections:indexesToBeDeleted
-                withRowAnimation:UITableViewRowAnimationFade];
-  syncer::SyncService* syncService =
-      IOSChromeProfileSyncServiceFactory::GetForBrowserState(self.browserState);
-  _syncedSessions.reset(new synced_sessions::SyncedSessions(syncService));
-  self.sessionState = newSessionState;
-
-  if (self.sessionState ==
-      SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS) {
-    // Expand the "Other Device" section once sync is finished.
-    [self setSection:kOtherDeviceCollapsedKey collapsed:NO];
-  }
-
-  NSIndexSet* indexesToBeInserted = [self sessionOrOtherDevicesSectionsIndexes];
-  [self.tableView insertSections:indexesToBeInserted
-                withRowAnimation:UITableViewRowAnimationFade];
-  [self.tableView endUpdates];
-
-  if (self.sessionState != SessionsSyncUserState::USER_SIGNED_OUT) {
-    [self.signinPromoViewMediator signinPromoViewRemoved];
-    self.signinPromoViewMediator = nil;
-  }
-}
-
-- (NSInteger)numberOfSessionSections {
-  DCHECK(self.sessionState ==
-         SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS);
-  return _syncedSessions->GetSessionCount();
-}
-
-- (NSIndexSet*)sessionOrOtherDevicesSectionsIndexes {
-  NSInteger sectionCount = 0;
-  switch (self.sessionState) {
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
-      sectionCount = [self numberOfSessionSections];
-      break;
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-    case SessionsSyncUserState::USER_SIGNED_OUT:
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
-      sectionCount = 1;
-      break;
-  }
-  NSRange rangeOfSessionSections = NSMakeRange(
-      [self numberOfSectionsBeforeSessionOrOtherDevicesSections], sectionCount);
-  NSIndexSet* sessionSectionsIndexes =
-      [NSIndexSet indexSetWithIndexesInRange:rangeOfSessionSections];
-  return sessionSectionsIndexes;
-}
-
-- (size_t)indexOfSessionAtIndexPath:(NSIndexPath*)indexPath {
-  DCHECK_EQ([self sectionType:indexPath.section], SESSION_SECTION);
-  size_t indexOfSession =
-      indexPath.section -
-      [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
-  DCHECK_LT(indexOfSession, _syncedSessions->GetSessionCount());
-  return indexOfSession;
-}
-
-- (synced_sessions::DistantSession const*)sessionAtIndexPath:
-    (NSIndexPath*)indexPath {
-  return _syncedSessions->GetSession(
-      [self indexOfSessionAtIndexPath:indexPath]);
-}
-
-- (synced_sessions::DistantTab const*)distantTabAtIndex:
-    (NSIndexPath*)indexPath {
-  DCHECK_EQ([self sectionType:indexPath.section], SESSION_SECTION);
-  // "- 1" because of the section header.
-  size_t indexOfDistantTab = indexPath.row - 1;
-  synced_sessions::DistantSession const* session =
-      [self sessionAtIndexPath:indexPath];
-  DCHECK_LT(indexOfDistantTab, session->tabs.size());
-  return session->tabs[indexOfDistantTab].get();
-}
-
 #pragma mark - Long press and context menus
 
 - (void)handleLongPress:(UILongPressGestureRecognizer*)longPressGesture {
@@ -543,8 +703,8 @@
     DCHECK_LE(indexPath.section,
               [self numberOfSectionsInTableView:self.tableView]);
 
-    CellType cellType = [self cellType:indexPath];
-    if (cellType != CELL_SESSION_SECTION_HEADER) {
+    NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
+    if ([self isSessionSectionIdentifier:itemType]) {
       NOTREACHED();
       return;
     }
@@ -590,8 +750,10 @@
 }
 
 - (void)openTabsFromSessionAtIndexPath:(NSIndexPath*)indexPath {
+  // TO-ADAPT.
+  return;
   synced_sessions::DistantSession const* session =
-      [self sessionAtIndexPath:indexPath];
+      [self sessionForTabAtIndexPath:indexPath];
   [self dismissRecentTabsModal];
   for (auto const& tab : session->tabs) {
     [self.loader webPageOrderedOpen:tab->virtual_url
@@ -602,15 +764,20 @@
 }
 
 - (void)removeSessionAtIndexPath:(NSIndexPath*)indexPath {
-  DCHECK_EQ([self cellType:indexPath], CELL_SESSION_SECTION_HEADER);
+  // TO-ADAPT.
+  return;
+  DCHECK([self
+      isSessionSectionIdentifier:
+          [self.tableViewModel sectionIdentifierForSection:indexPath.section]]);
   synced_sessions::DistantSession const* session =
-      [self sessionAtIndexPath:indexPath];
+      [self sessionForTabAtIndexPath:indexPath];
   std::string sessionTagCopy = session->tag;
   syncer::SyncService* syncService =
       IOSChromeProfileSyncServiceFactory::GetForBrowserState(self.browserState);
   sync_sessions::OpenTabsUIDelegate* openTabs =
       syncService->GetOpenTabsUIDelegate();
-  _syncedSessions->EraseSession([self indexOfSessionAtIndexPath:indexPath]);
+  _syncedSessions->EraseSession(
+      [self indexOfSessionForTabAtIndexPath:indexPath]);
   [self.tableView
         deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
       withRowAnimation:UITableViewRowAnimationLeft];
@@ -625,298 +792,18 @@
 
 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
   CGPoint point = [gestureRecognizer locationInView:self.tableView];
-  NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:point];
-  if (!indexPath)
-    return NO;
-  CellType cellType = [self cellType:indexPath];
-  // Context menus can be opened on a section header for tabs.
-  return cellType == CELL_SESSION_SECTION_HEADER;
-}
-
-#pragma mark - UITableViewDataSource
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
-  switch (self.sessionState) {
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS:
-      return [self numberOfSectionsBeforeSessionOrOtherDevicesSections] +
-             [self numberOfSessionSections];
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_OFF:
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-    case SessionsSyncUserState::USER_SIGNED_OUT:
-    case SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS:
-      return [self numberOfSectionsBeforeSessionOrOtherDevicesSections] + 1;
-  }
-}
-
-- (NSInteger)tableView:(UITableView*)tableView
-    numberOfRowsInSection:(NSInteger)section {
-  switch ([self sectionType:section]) {
-    case CLOSED_TAB_SECTION:
-      if ([self sectionIsCollapsed:kRecentlyClosedCollapsedKey])
-        return 1;
-      else
-        return [self numberOfCellsInRecentlyClosedTabsSection];
-    case SEPARATOR_SECTION:
-      return 1;
-    case OTHER_DEVICES_SECTION:
-      if (self.sessionState ==
-          SessionsSyncUserState::USER_SIGNED_IN_SYNC_IN_PROGRESS)
-        return 1;
-      if ([self sectionIsCollapsed:kOtherDeviceCollapsedKey])
-        return 1;
-      else
-        return 2;
-    case SESSION_SECTION: {
-      DCHECK(self.sessionState ==
-             SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS);
-      size_t sessionIndex =
-          section - [self numberOfSectionsBeforeSessionOrOtherDevicesSections];
-      DCHECK_LT(sessionIndex, _syncedSessions->GetSessionCount());
-      synced_sessions::DistantSession const* distantSession =
-          _syncedSessions->GetSession(sessionIndex);
-      NSString* key = [self keyForDistantSession:distantSession];
-      if ([self sectionIsCollapsed:key])
-        return 1;
-      else
-        return distantSession->tabs.size() + 1;
+  // Context menus can be opened on a Session section header.
+  for (NSInteger section = 0; section < [self.tableViewModel numberOfSections];
+       section++) {
+    NSInteger itemType =
+        [self.tableViewModel sectionIdentifierForSection:section];
+    if (CGRectContainsPoint([self.tableView rectForHeaderInSection:section],
+                            point) &&
+        [self isSessionSectionIdentifier:itemType]) {
+      return YES;
     }
   }
-}
-
-- (UITableViewCell*)tableView:(UITableView*)tableView
-        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
-  UITableViewCell* cell =
-      [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
-                             reuseIdentifier:nil];
-  UIView* contentView = cell.contentView;
-  CGFloat contentViewTopMargin = 0;
-
-  UIView* subview;
-  CellType cellType = [self cellType:indexPath];
-  switch (cellType) {
-    case CELL_CLOSED_TAB_SECTION_HEADER: {
-      BOOL collapsed = [self sectionIsCollapsed:kRecentlyClosedCollapsedKey];
-      subview = [[GenericSectionHeaderView alloc]
-                initWithType:recent_tabs::RECENTLY_CLOSED_TABS_SECTION_HEADER
-          sectionIsCollapsed:collapsed];
-      [subview setTag:kSectionHeader];
-      break;
-    }
-    case CELL_CLOSED_TAB_DATA: {
-      SessionTabDataView* genericTabData =
-          [[SessionTabDataView alloc] initWithFrame:CGRectZero];
-      [genericTabData
-          updateWithTabRestoreEntry:[self tabRestoreEntryAtIndex:indexPath]
-                       browserState:self.browserState];
-      subview = genericTabData;
-      break;
-    }
-    case CELL_SHOW_FULL_HISTORY:
-      subview = [[ShowFullHistoryView alloc] initWithFrame:CGRectZero];
-      break;
-    case CELL_SEPARATOR:
-      subview = [[RecentlyClosedSectionFooter alloc] initWithFrame:CGRectZero];
-      [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
-      break;
-    case CELL_OTHER_DEVICES_SECTION_HEADER: {
-      BOOL collapsed = [self sectionIsCollapsed:kOtherDeviceCollapsedKey];
-      subview = [[GenericSectionHeaderView alloc]
-                initWithType:recent_tabs::OTHER_DEVICES_SECTION_HEADER
-          sectionIsCollapsed:collapsed];
-      [subview setTag:kSectionHeader];
-      break;
-    }
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
-      subview = [[SignedInSyncOffView alloc] initWithFrame:CGRectZero
-                                              browserState:self.browserState
-                                                 presenter:self];
-      [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
-      break;
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-      subview = [[SignedInSyncOnNoSessionsView alloc] initWithFrame:CGRectZero];
-      [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
-      break;
-    case CELL_OTHER_DEVICES_SIGNIN_PROMO: {
-      if (!self.signinPromoViewMediator) {
-        self.signinPromoViewMediator = [[SigninPromoViewMediator alloc]
-            initWithBrowserState:self.browserState
-                     accessPoint:signin_metrics::AccessPoint::
-                                     ACCESS_POINT_RECENT_TABS
-                       presenter:self /* id<SigninPresenter> */];
-        self.signinPromoViewMediator.consumer = self;
-      }
-      contentViewTopMargin = kSigninPromoViewTopMargin;
-      SigninPromoView* signinPromoView =
-          [[SigninPromoView alloc] initWithFrame:CGRectZero];
-      signinPromoView.delegate = self.signinPromoViewMediator;
-      signinPromoView.textLabel.text =
-          l10n_util::GetNSString(IDS_IOS_SIGNIN_PROMO_RECENT_TABS);
-      signinPromoView.textLabel.preferredMaxLayoutWidth =
-          CGRectGetWidth(self.tableView.bounds) -
-          2 * signinPromoView.horizontalPadding;
-      SigninPromoViewConfigurator* configurator =
-          [self.signinPromoViewMediator createConfigurator];
-      [configurator configureSigninPromoView:signinPromoView];
-      subview = signinPromoView;
-      [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
-      [self.signinPromoViewMediator signinPromoViewVisible];
-      break;
-    }
-    case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
-      subview = [[SignedInSyncInProgressView alloc] initWithFrame:CGRectZero];
-      [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
-      break;
-    case CELL_SESSION_SECTION_HEADER: {
-      synced_sessions::DistantSession const* distantSession =
-          [self sessionAtIndexPath:indexPath];
-      NSString* key = [self keyForDistantSession:distantSession];
-      BOOL collapsed = [self sectionIsCollapsed:key];
-      SessionSectionHeaderView* sessionSectionHeader =
-          [[SessionSectionHeaderView alloc] initWithFrame:CGRectZero
-                                       sectionIsCollapsed:collapsed];
-      [sessionSectionHeader updateWithSession:distantSession];
-      subview = sessionSectionHeader;
-      [subview setTag:kSectionHeader];
-      break;
-    }
-    case CELL_SESSION_TAB_DATA: {
-      SessionTabDataView* genericTabData =
-          [[SessionTabDataView alloc] initWithFrame:CGRectZero];
-      [genericTabData updateWithDistantTab:[self distantTabAtIndex:indexPath]
-                              browserState:self.browserState];
-      subview = genericTabData;
-      break;
-    }
-  }
-
-  DCHECK(subview);
-  [contentView addSubview:subview];
-
-  // Sets constraints on the subview.
-  [subview setTranslatesAutoresizingMaskIntoConstraints:NO];
-
-  NSDictionary* viewsDictionary = @{@"view" : subview};
-  // This set of constraints should match the constraints set on the
-  // RecentlyClosedSectionFooter.
-  // clang-format off
-  NSArray* constraints = @[
-                           @"V:|-(TopMargin)-[view]-0-|",
-                           @"H:|-(>=0)-[view(<=548)]-(>=0)-|",
-                           @"H:[view(==548@500)]"
-                           ];
-  // clang-format on
-  [contentView addConstraint:[NSLayoutConstraint
-                                 constraintWithItem:subview
-                                          attribute:NSLayoutAttributeCenterX
-                                          relatedBy:NSLayoutRelationEqual
-                                             toItem:contentView
-                                          attribute:NSLayoutAttributeCenterX
-                                         multiplier:1
-                                           constant:0]];
-  NSDictionary* metrics = @{ @"TopMargin" : @(contentViewTopMargin) };
-  ApplyVisualConstraintsWithMetrics(constraints, viewsDictionary, metrics);
-  return cell;
-}
-
-#pragma mark - UITableViewDelegate
-
-- (NSIndexPath*)tableView:(UITableView*)tableView
-    willSelectRowAtIndexPath:(NSIndexPath*)indexPath {
-  DCHECK_EQ(tableView, self.tableView);
-  CellType cellType = [self cellType:indexPath];
-  switch (cellType) {
-    case CELL_CLOSED_TAB_SECTION_HEADER:
-    case CELL_OTHER_DEVICES_SECTION_HEADER:
-    case CELL_SESSION_SECTION_HEADER:
-    case CELL_CLOSED_TAB_DATA:
-    case CELL_SESSION_TAB_DATA:
-    case CELL_SHOW_FULL_HISTORY:
-      return indexPath;
-    case CELL_SEPARATOR:
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-    case CELL_OTHER_DEVICES_SIGNIN_PROMO:
-    case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
-      return nil;
-  }
-}
-
-- (void)tableView:(UITableView*)tableView
-    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
-  DCHECK_EQ(tableView, self.tableView);
-  CellType cellType = [self cellType:indexPath];
-  switch (cellType) {
-    case CELL_CLOSED_TAB_SECTION_HEADER:
-    case CELL_OTHER_DEVICES_SECTION_HEADER:
-    case CELL_SESSION_SECTION_HEADER:
-      // Collapse or uncollapse section.
-      [tableView deselectRowAtIndexPath:indexPath animated:NO];
-      [self toggleExpansionOfSection:indexPath.section];
-      break;
-    case CELL_CLOSED_TAB_DATA:
-      // Open new tab.
-      [self openTabWithTabRestoreEntry:[self tabRestoreEntryAtIndex:indexPath]];
-      break;
-    case CELL_SESSION_TAB_DATA:
-      // Open new tab.
-      [self openTabWithContentOfDistantTab:[self distantTabAtIndex:indexPath]];
-      break;
-    case CELL_SHOW_FULL_HISTORY:
-      [tableView deselectRowAtIndexPath:indexPath animated:NO];
-      [self showFullHistory];
-      break;
-    case CELL_SEPARATOR:
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-    case CELL_OTHER_DEVICES_SIGNIN_PROMO:
-    case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
-      NOTREACHED();
-      break;
-  }
-}
-
-- (CGFloat)tableView:(UITableView*)tableView
-    heightForRowAtIndexPath:(NSIndexPath*)indexPath {
-  DCHECK_EQ(self.tableView, tableView);
-  CellType cellType = [self cellType:indexPath];
-  switch (cellType) {
-    case CELL_SHOW_FULL_HISTORY:
-      return [ShowFullHistoryView desiredHeightInUITableViewCell];
-    case CELL_SEPARATOR:
-      return [RecentlyClosedSectionFooter desiredHeightInUITableViewCell];
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_OFF:
-      return [SignedInSyncOffView desiredHeightInUITableViewCell];
-    case CELL_OTHER_DEVICES_SIGNED_IN_SYNC_ON_NO_SESSIONS:
-      return [SignedInSyncOnNoSessionsView desiredHeightInUITableViewCell];
-    case CELL_OTHER_DEVICES_SIGNIN_PROMO:
-      return UITableViewAutomaticDimension;
-    case CELL_SESSION_SECTION_HEADER:
-      return [SessionSectionHeaderView desiredHeightInUITableViewCell];
-    case CELL_CLOSED_TAB_DATA:
-    case CELL_SESSION_TAB_DATA:
-      return [SessionTabDataView desiredHeightInUITableViewCell];
-    case CELL_CLOSED_TAB_SECTION_HEADER:
-    case CELL_OTHER_DEVICES_SECTION_HEADER:
-      return [GenericSectionHeaderView desiredHeightInUITableViewCell];
-    case CELL_OTHER_DEVICES_SYNC_IN_PROGRESS:
-      return [SignedInSyncInProgressView desiredHeightInUITableViewCell];
-  }
-}
-
-- (UIView*)tableView:(UITableView*)tableView
-    viewForHeaderInSection:(NSInteger)section {
-  if ([self sectionType:section] == CLOSED_TAB_SECTION) {
-    return [[RecentlyTabsTopSpacingHeader alloc] initWithFrame:CGRectZero];
-  }
-  return nil;
-}
-
-- (CGFloat)tableView:(UITableView*)tableView
-    heightForHeaderInSection:(NSInteger)section {
-  if ([self sectionType:section] == CLOSED_TAB_SECTION) {
-    return [RecentlyTabsTopSpacingHeader desiredHeightInUITableViewCell];
-  }
-  return 0;
+  return NO;
 }
 
 #pragma mark - SigninPromoViewConsumer
@@ -927,11 +814,9 @@
   DCHECK(self.signinPromoViewMediator);
   if ([self sectionIsCollapsed:kOtherDeviceCollapsedKey])
     return;
-  NSInteger sectionIndex =
-      [self sectionIndexForSectionType:OTHER_DEVICES_SECTION];
-  DCHECK(sectionIndex != NSNotFound);
   NSIndexPath* indexPath =
-      [NSIndexPath indexPathForRow:1 inSection:sectionIndex];
+      [self.tableViewModel indexPathForItemType:ItemTypeOtherDevicesSigninPromo
+                              sectionIdentifier:SectionIdentifierOtherDevices];
   if (identityChanged) {
     [self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
                           withRowAnimation:UITableViewRowAnimationNone];
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
index 6bdbbf7..8a67452 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.mm
@@ -31,6 +31,10 @@
   TableViewTextHeaderFooterView* header =
       base::mac::ObjCCastStrict<TableViewTextHeaderFooterView>(headerFooter);
   header.textLabel.text = self.text;
+  // Set the contentView backgroundColor, not the header's.
+  header.contentView.backgroundColor = [UIColor whiteColor];
+  header.textLabel.font =
+      [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
 }
 
 @end
@@ -46,7 +50,6 @@
     // Text Label, set font sizes using dynamic type.
     _textLabel = [[UILabel alloc] init];
     _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
-    _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
 
     // Container View.
     UIView* containerView = [[UIView alloc] init];
diff --git a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
index d08cd7d2..d97b528 100644
--- a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
+++ b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
@@ -21,6 +21,15 @@
   _tableViewModel = [[TableViewModel alloc] init];
 }
 
+#pragma mark - ViewLifeCycle
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  [self.tableView setBackgroundColor:[UIColor whiteColor]];
+  [self.tableView setSeparatorColor:[UIColor grayColor]];
+  [self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 56, 0, 0)];
+}
+
 #pragma mark - UITableViewDataSource
 
 - (UITableViewCell*)tableView:(UITableView*)tableView
@@ -54,7 +63,7 @@
   TableViewHeaderFooterItem* item =
       [self.tableViewModel headerForSection:section];
   if (!item)
-    return [super tableView:self.tableView viewForHeaderInSection:section];
+    return [[UIView alloc] initWithFrame:CGRectZero];
   Class headerFooterClass = [item cellClass];
   NSString* reuseIdentifier = NSStringFromClass(headerFooterClass);
   [self.tableView registerClass:headerFooterClass
@@ -70,7 +79,7 @@
   TableViewHeaderFooterItem* item =
       [self.tableViewModel footerForSection:section];
   if (!item)
-    return [super tableView:self.tableView viewForHeaderInSection:section];
+    return [[UIView alloc] initWithFrame:CGRectZero];
   Class headerFooterClass = [item cellClass];
   NSString* reuseIdentifier = NSStringFromClass(headerFooterClass);
   [self.tableView registerClass:headerFooterClass
diff --git a/ipc/ipc_mojo_bootstrap_unittest.cc b/ipc/ipc_mojo_bootstrap_unittest.cc
index 46dca0e..5bc2422 100644
--- a/ipc/ipc_mojo_bootstrap_unittest.cc
+++ b/ipc/ipc_mojo_bootstrap_unittest.cc
@@ -65,7 +65,11 @@
       : binding_(this, std::move(request)),
         on_peer_pid_set_(on_peer_pid_set),
         message_expectation_(message_expectation) {}
-  ~PeerPidReceiver() override {}
+  ~PeerPidReceiver() override {
+    bool expected_message =
+        message_expectation_ != MessageExpectation::kNotExpected;
+    EXPECT_EQ(expected_message, received_message_);
+  }
 
   // mojom::Channel:
   void SetPeerPid(int32_t pid) override {
@@ -77,6 +81,7 @@
                base::Optional<std::vector<mojo::native::SerializedHandlePtr>>
                    handles) override {
     ASSERT_NE(MessageExpectation::kNotExpected, message_expectation_);
+    received_message_ = true;
 
     IPC::Message message(reinterpret_cast<const char*>(data.data()),
                          static_cast<uint32_t>(data.size()));
@@ -97,6 +102,8 @@
   MessageExpectation message_expectation_;
   int32_t peer_pid_ = -1;
 
+  bool received_message_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(PeerPidReceiver);
 };
 
diff --git a/media/video/gpu_memory_buffer_video_frame_pool.cc b/media/video/gpu_memory_buffer_video_frame_pool.cc
index 8c6198da..bba15221 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool.cc
@@ -16,6 +16,7 @@
 
 #include "base/barrier_closure.h"
 #include "base/bind.h"
+#include "base/containers/circular_deque.h"
 #include "base/containers/stack_container.h"
 #include "base/location.h"
 #include "base/macros.h"
@@ -117,25 +118,35 @@
     base::TimeTicks last_use_time_;
   };
 
+  // Struct to keep track of requested videoframe copies.
+  struct VideoFrameCopyRequest {
+    VideoFrameCopyRequest(scoped_refptr<VideoFrame> video_frame,
+                          FrameReadyCB frame_ready_cb)
+        : video_frame(video_frame), frame_ready_cb(std::move(frame_ready_cb)) {}
+    scoped_refptr<VideoFrame> video_frame;
+    FrameReadyCB frame_ready_cb;
+  };
+
+  // Start the copy of a video_frame on the worker_task_runner_.
+  // It assumes there are currently no in-flight copies.
+  void StartCopy(const scoped_refptr<VideoFrame>& video_frame);
+
   // Copy |video_frame| data into |frame_resources| and calls |frame_ready_cb|
   // when done.
   void CopyVideoFrameToGpuMemoryBuffers(
       const scoped_refptr<VideoFrame>& video_frame,
-      FrameResources* frame_resources,
-      FrameReadyCB frame_ready_cb);
+      FrameResources* frame_resources);
 
   // Called when all the data has been copied.
   void OnCopiesDone(const scoped_refptr<VideoFrame>& video_frame,
-                    FrameResources* frame_resources,
-                    FrameReadyCB frame_ready_cb);
+                    FrameResources* frame_resources);
 
   // Prepares GL resources, mailboxes and calls |frame_ready_cb| with the new
   // VideoFrame. This has to be run on |media_task_runner_| where
-  // |frame_ready_cb| will also be run.
+  // |frame_ready_cb| associated with video_frame will also be run.
   void BindAndCreateMailboxesHardwareFrameResources(
       const scoped_refptr<VideoFrame>& video_frame,
-      FrameResources* frame_resources,
-      FrameReadyCB frame_ready_cb);
+      FrameResources* frame_resources);
 
   // Return true if |resources| can be used to represent a frame for
   // specific |format| and |size|.
@@ -179,6 +190,9 @@
   // |tick_clock_| is always a DefaultTickClock outside of testing.
   base::TickClock* tick_clock_;
 
+  // Queued up video frames for copies. The front is the currently
+  // in-flight copy, new copies are added at the end.
+  base::circular_deque<VideoFrameCopyRequest> frame_copy_requests_;
   bool in_shutdown_;
 
   DISALLOW_COPY_AND_ASSIGN(PoolImpl);
@@ -563,19 +577,9 @@
       return;
   }
 
-  const gfx::Size coded_size = CodedSize(video_frame, output_format_);
-  // Acquire resources. Incompatible ones will be dropped from the pool.
-  FrameResources* frame_resources =
-      GetOrCreateFrameResources(coded_size, output_format_);
-  if (!frame_resources) {
-    std::move(frame_ready_cb).Run(video_frame);
-    return;
-  }
-
-  worker_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&PoolImpl::CopyVideoFrameToGpuMemoryBuffers,
-                                this, video_frame, frame_resources,
-                                base::Passed(&frame_ready_cb)));
+  frame_copy_requests_.emplace_back(video_frame, std::move(frame_ready_cb));
+  if (frame_copy_requests_.size() == 1u)
+    StartCopy(video_frame);
 }
 
 bool GpuMemoryBufferVideoFramePool::PoolImpl::OnMemoryDump(
@@ -623,8 +627,7 @@
 
 void GpuMemoryBufferVideoFramePool::PoolImpl::OnCopiesDone(
     const scoped_refptr<VideoFrame>& video_frame,
-    FrameResources* frame_resources,
-    FrameReadyCB frame_ready_cb) {
+    FrameResources* frame_resources) {
   for (const auto& plane_resource : frame_resources->plane_resources) {
     if (plane_resource.gpu_memory_buffer) {
       plane_resource.gpu_memory_buffer->Unmap();
@@ -639,8 +642,27 @@
   media_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&PoolImpl::BindAndCreateMailboxesHardwareFrameResources,
-                     this, video_frame, frame_resources,
-                     base::Passed(&frame_ready_cb)));
+                     this, video_frame, frame_resources));
+}
+
+void GpuMemoryBufferVideoFramePool::PoolImpl::StartCopy(
+    const scoped_refptr<VideoFrame>& video_frame) {
+  DCHECK(media_task_runner_->BelongsToCurrentThread());
+  DCHECK(!frame_copy_requests_.empty());
+
+  const gfx::Size coded_size = CodedSize(video_frame, output_format_);
+  // Acquire resources. Incompatible ones will be dropped from the pool.
+  FrameResources* frame_resources =
+      GetOrCreateFrameResources(coded_size, output_format_);
+  if (!frame_resources) {
+    std::move(frame_copy_requests_.front().frame_ready_cb).Run(video_frame);
+    frame_copy_requests_.pop_front();
+    return;
+  }
+
+  worker_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PoolImpl::CopyVideoFrameToGpuMemoryBuffers,
+                                this, video_frame, frame_resources));
 }
 
 // Copies |video_frame| into |frame_resources| asynchronously, posting n tasks
@@ -648,8 +670,7 @@
 // After the barrier is passed OnCopiesDone will be called.
 void GpuMemoryBufferVideoFramePool::PoolImpl::CopyVideoFrameToGpuMemoryBuffers(
     const scoped_refptr<VideoFrame>& video_frame,
-    FrameResources* frame_resources,
-    FrameReadyCB frame_ready_cb) {
+    FrameResources* frame_resources) {
   // Compute the number of tasks to post and create the barrier.
   const size_t num_planes = VideoFrame::NumPlanes(VideoFormat(output_format_));
   const size_t planes_per_copy = PlanesPerCopy(output_format_);
@@ -666,8 +687,7 @@
   }
 
   const base::Closure copies_done =
-      base::Bind(&PoolImpl::OnCopiesDone, this, video_frame, frame_resources,
-                 base::Passed(&frame_ready_cb));
+      base::Bind(&PoolImpl::OnCopiesDone, this, video_frame, frame_resources);
   const base::RepeatingClosure barrier =
       base::BarrierClosure(copies, copies_done);
 
@@ -763,13 +783,13 @@
 void GpuMemoryBufferVideoFramePool::PoolImpl::
     BindAndCreateMailboxesHardwareFrameResources(
         const scoped_refptr<VideoFrame>& video_frame,
-        FrameResources* frame_resources,
-        FrameReadyCB frame_ready_cb) {
+        FrameResources* frame_resources) {
   std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
       gpu_factories_->GetGLContextLock());
   if (!lock) {
     frame_resources->MarkUnused(tick_clock_->NowTicks());
-    std::move(frame_ready_cb).Run(video_frame);
+    std::move(frame_copy_requests_.front().frame_ready_cb).Run(video_frame);
+    frame_copy_requests_.pop_front();
     return;
   }
   gpu::gles2::GLES2Interface* gles2 = lock->ContextGL();
@@ -824,7 +844,8 @@
   if (!frame) {
     frame_resources->MarkUnused(tick_clock_->NowTicks());
     release_mailbox_callback.Run(gpu::SyncToken());
-    std::move(frame_ready_cb).Run(video_frame);
+    std::move(frame_copy_requests_.front().frame_ready_cb).Run(video_frame);
+    frame_copy_requests_.pop_front();
     return;
   }
 
@@ -864,7 +885,15 @@
   frame->metadata()->SetBoolean(VideoFrameMetadata::READ_LOCK_FENCES_ENABLED,
                                 true);
 
-  std::move(frame_ready_cb).Run(frame);
+  lock.reset();  // Release the lock to avoid deadlocks.
+  DCHECK(!frame_copy_requests_.empty());
+  std::move(frame_copy_requests_.front().frame_ready_cb).Run(frame);
+  frame_copy_requests_.pop_front();
+
+  if (!frame_copy_requests_.empty()) {
+    VideoFrameCopyRequest& copy_request = frame_copy_requests_.front();
+    StartCopy(copy_request.video_frame);
+  }
 }
 
 // Destroy all the resources posting one task per FrameResources
@@ -899,7 +928,6 @@
 GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources(
     const gfx::Size& size,
     GpuVideoAcceleratorFactories::OutputFormat format) {
-  DCHECK(!in_shutdown_);
 
   auto it = resources_pool_.begin();
   while (it != resources_pool_.end()) {
diff --git a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
index 397f938b..e734fb6 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool_unittest.cc
@@ -414,4 +414,30 @@
   EXPECT_EQ(3u, gles2_->deleted_textures_count());
 }
 
+// Test when we request two copies in a row, there should be at most one frame
+// copy in flight at any time.
+TEST_F(GpuMemoryBufferVideoFramePoolTest, AtMostOneCopyInFlight) {
+  mock_gpu_factories_->SetVideoFrameOutputFormat(
+      media::GpuVideoAcceleratorFactories::OutputFormat::UYVY);
+
+  scoped_refptr<VideoFrame> software_frame_1 = CreateTestYUVVideoFrame(10);
+  scoped_refptr<VideoFrame> frame_1;
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame_1,
+      base::BindOnce(MaybeCreateHardwareFrameCallback, &frame_1));
+
+  scoped_refptr<VideoFrame> software_frame_2 = CreateTestYUVVideoFrame(10);
+  scoped_refptr<VideoFrame> frame_2;
+  gpu_memory_buffer_pool_->MaybeCreateHardwareFrame(
+      software_frame_2,
+      base::BindOnce(MaybeCreateHardwareFrameCallback, &frame_2));
+
+  media_task_runner_->RunUntilIdle();
+  EXPECT_EQ(1u, copy_task_runner_->NumPendingTasks());
+  copy_task_runner_->RunUntilIdle();
+  media_task_runner_->RunUntilIdle();
+  EXPECT_EQ(1u, copy_task_runner_->NumPendingTasks());
+  RunUntilIdle();
+}
+
 }  // namespace media
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index a8f9c48d..55b7ba7c 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -88,6 +88,7 @@
   defines = [ "IS_MOJO_CPP_BINDINGS_BASE_IMPL" ]
 
   public_deps = [
+    ":mojo_features",
     "//base",
     "//mojo/public/cpp/system",
   ]
@@ -160,7 +161,6 @@
 
   public_deps = [
     ":bindings_base",
-    ":mojo_features",
     ":struct_traits",
     "//base",
     "//ipc:message_support",
diff --git a/net/base/network_interfaces_fuchsia.cc b/net/base/network_interfaces_fuchsia.cc
index 61c9e80..65ef4f8 100644
--- a/net/base/network_interfaces_fuchsia.cc
+++ b/net/base/network_interfaces_fuchsia.cc
@@ -18,37 +18,38 @@
     return false;
   }
 
-  netc_get_if_info_t netconfig;
-  int size = ioctl_netc_get_if_info(s, &netconfig);
-  PCHECK(close(s) == 0);
-
-  if (size < 0) {
-    PLOG(ERROR) << "ioctl_netc_get_if_info";
+  uint32_t num_ifs = 0;
+  if (ioctl_netc_get_num_ifs(s, &num_ifs) < 0) {
+    PLOG(ERROR) << "ioctl_netc_get_num_ifs";
+    PCHECK(close(s) == 0);
     return false;
   }
 
-  networks->clear();
+  for (uint32_t i = 0; i < num_ifs; ++i) {
+    netc_if_info_t interface;
 
-  for (size_t i = 0; i < netconfig.n_info; ++i) {
-    netc_if_info_t* interface = netconfig.info + i;
+    if (ioctl_netc_get_if_info_at(s, &i, &interface) < 0) {
+      PLOG(WARNING) << "ioctl_netc_get_if_info_at";
+      continue;
+    }
 
     // Skip loopback addresses.
     if (internal::IsLoopbackOrUnspecifiedAddress(
-            reinterpret_cast<sockaddr*>(&(interface->addr)))) {
+            reinterpret_cast<sockaddr*>(&(interface.addr)))) {
       continue;
     }
 
     IPEndPoint address;
-    if (!address.FromSockAddr(reinterpret_cast<sockaddr*>(&(interface->addr)),
-                              sizeof(interface->addr))) {
+    if (!address.FromSockAddr(reinterpret_cast<sockaddr*>(&(interface.addr)),
+                              sizeof(interface.addr))) {
       DLOG(WARNING) << "ioctl_netc_get_if_info returned invalid address.";
       continue;
     }
 
     int prefix_length = 0;
     IPEndPoint netmask;
-    if (netmask.FromSockAddr(reinterpret_cast<sockaddr*>(&(interface->netmask)),
-                             sizeof(interface->netmask))) {
+    if (netmask.FromSockAddr(reinterpret_cast<sockaddr*>(&(interface.netmask)),
+                             sizeof(interface.netmask))) {
       prefix_length = MaskPrefixLength(netmask.address());
     }
 
@@ -58,11 +59,13 @@
     int attributes = 0;
 
     networks->push_back(
-        NetworkInterface(interface->name, interface->name, interface->index,
+        NetworkInterface(interface.name, interface.name, interface.index,
                          NetworkChangeNotifier::CONNECTION_UNKNOWN,
                          address.address(), prefix_length, attributes));
   }
 
+  PCHECK(close(s) == 0);
+
   return true;
 }
 
diff --git a/net/quic/core/quic_ietf_framer_test.cc b/net/quic/core/quic_ietf_framer_test.cc
index 42d84f8..a24e414 100644
--- a/net/quic/core/quic_ietf_framer_test.cc
+++ b/net/quic/core/quic_ietf_framer_test.cc
@@ -832,9 +832,11 @@
 }
 
 TEST_F(QuicIetfFramerTest, PathChallengeFrame) {
-  QuicPathFrameBuffer buffer0 = {0, 0, 0, 0, 0, 0, 0, 0};
-  QuicPathFrameBuffer buffer1 = {0x80, 0x91, 0xa2, 0xb3,
-                                 0xc4, 0xd5, 0xe5, 0xf7};
+  // Double-braces needed on some platforms due to
+  // https://bugs.llvm.org/show_bug.cgi?id=21629
+  QuicPathFrameBuffer buffer0 = {{0, 0, 0, 0, 0, 0, 0, 0}};
+  QuicPathFrameBuffer buffer1 = {
+      {0x80, 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe5, 0xf7}};
   char packet_buffer[kNormalPacketBufferSize];
   EXPECT_TRUE(
       TryPathChallengeFrame(packet_buffer, sizeof(packet_buffer), buffer0));
@@ -843,9 +845,11 @@
 }
 
 TEST_F(QuicIetfFramerTest, PathResponseFrame) {
-  QuicPathFrameBuffer buffer0 = {0, 0, 0, 0, 0, 0, 0, 0};
-  QuicPathFrameBuffer buffer1 = {0x80, 0x91, 0xa2, 0xb3,
-                                 0xc4, 0xd5, 0xe5, 0xf7};
+  // Double-braces needed on some platforms due to
+  // https://bugs.llvm.org/show_bug.cgi?id=21629
+  QuicPathFrameBuffer buffer0 = {{0, 0, 0, 0, 0, 0, 0, 0}};
+  QuicPathFrameBuffer buffer1 = {
+      {0x80, 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe5, 0xf7}};
   char packet_buffer[kNormalPacketBufferSize];
   EXPECT_TRUE(
       TryPathResponseFrame(packet_buffer, sizeof(packet_buffer), buffer0));
diff --git a/net/socket/udp_socket_posix.cc b/net/socket/udp_socket_posix.cc
index 7732ecc4..9bc55d7 100644
--- a/net/socket/udp_socket_posix.cc
+++ b/net/socket/udp_socket_posix.cc
@@ -94,14 +94,19 @@
     return MapSystemError(errno);
   result = reinterpret_cast<sockaddr_in*>(&ifr.ifr_addr);
 #elif defined(OS_FUCHSIA)
-  netc_get_if_info_t netconfig;
-  int size = ioctl_netc_get_if_info(socket, &netconfig);
-  if (size < 0)
+  uint32_t num_ifs = 0;
+  if (ioctl_netc_get_num_ifs(socket, &num_ifs) < 0) {
+    PLOG(ERROR) << "ioctl_netc_get_num_ifs";
     return MapSystemError(errno);
-  for (size_t i = 0; i < netconfig.n_info; ++i) {
-    netc_if_info_t* interface = netconfig.info + i;
-    if (interface->index == index && interface->addr.ss_family == AF_INET) {
-      result = reinterpret_cast<sockaddr_in*>(&(interface->addr));
+  }
+  for (uint32_t i = 0; i < num_ifs; ++i) {
+    netc_if_info_t interface;
+    if (ioctl_netc_get_if_info_at(socket, &i, &interface) < 0) {
+      PLOG(WARNING) << "ioctl_netc_get_if_info_at";
+      continue;
+    }
+    if (interface.index == index && interface.addr.ss_family == AF_INET) {
+      result = reinterpret_cast<sockaddr_in*>(&(interface.addr));
       break;
     }
   }
diff --git a/printing/printed_document.cc b/printing/printed_document.cc
index 05987fcf..bc5aecc 100644
--- a/printing/printed_document.cc
+++ b/printing/printed_document.cc
@@ -131,6 +131,11 @@
 PrintedDocument::~PrintedDocument() = default;
 
 #if defined(OS_WIN)
+void PrintedDocument::SetConvertingPdf() {
+  base::AutoLock lock(lock_);
+  mutable_.converting_pdf_ = true;
+}
+
 void PrintedDocument::SetPage(int page_number,
                               std::unique_ptr<MetafilePlayer> metafile,
                               float shrink,
@@ -195,6 +200,9 @@
   if (!mutable_.page_count_)
     return false;
 #if defined(OS_WIN)
+  if (mutable_.converting_pdf_)
+    return true;
+
   PageNumber page(immutable_.settings_, mutable_.page_count_);
   if (page == PageNumber::npos())
     return false;
diff --git a/printing/printed_document.h b/printing/printed_document.h
index 9a52abb..08d9946 100644
--- a/printing/printed_document.h
+++ b/printing/printed_document.h
@@ -43,6 +43,11 @@
                   int cookie);
 
 #if defined(OS_WIN)
+  // Indicates that the PDF has been generated and the document is waiting for
+  // conversion for printing. This is needed on Windows so that the print job
+  // is not cancelled if the web contents dies before PDF conversion finishes.
+  void SetConvertingPdf();
+
   // Sets a page's data. 0-based. Note: locks for a short amount of time.
   void SetPage(int page_number,
                std::unique_ptr<MetafilePlayer> metafile,
@@ -78,7 +83,7 @@
 
   // Returns true if all the necessary pages for the settings are already
   // rendered.
-  // Note: locks while parsing the whole tree.
+  // Note: This function always locks and may parse the whole tree.
   bool IsComplete() const;
 
   // Sets the number of pages in the document to be rendered. Can only be set
@@ -153,6 +158,9 @@
     // Contains the pages' representation. This is a collection of PrintedPage.
     // Warning: Lock must be held when accessing this member.
     PrintedPages pages_;
+
+    // Whether the PDF is being converted for printing.
+    bool converting_pdf_ = false;
 #else
     std::unique_ptr<MetafilePlayer> metafile_;
 #endif
diff --git a/remoting/ios/app/client_connection_view_controller.mm b/remoting/ios/app/client_connection_view_controller.mm
index 98355de..e651af3b 100644
--- a/remoting/ios/app/client_connection_view_controller.mm
+++ b/remoting/ios/app/client_connection_view_controller.mm
@@ -43,8 +43,6 @@
 static const CGFloat kPadding = 20.f;
 static const CGFloat kMargin = 20.f;
 
-static const CGFloat kBarHeight = 58.f;
-
 static const CGFloat kKeyboardAnimationTime = 0.3;
 
 static NSString* const kConnectionErrorFeedbackContext =
@@ -108,13 +106,13 @@
     _navBar.translatesAutoresizingMaskIntoConstraints = NO;
 
     // Attach navBar to the top of the view.
+    UILayoutGuide* layoutGuide =
+        remoting::SafeAreaLayoutGuideForView(self.view);
     [NSLayoutConstraint activateConstraints:@[
-      [[_navBar topAnchor] constraintEqualToAnchor:[self.view topAnchor]],
-      [[_navBar leadingAnchor]
-          constraintEqualToAnchor:[self.view leadingAnchor]],
-      [[_navBar trailingAnchor]
-          constraintEqualToAnchor:[self.view trailingAnchor]],
-      [[_navBar heightAnchor] constraintEqualToConstant:kBarHeight],
+      [_navBar.topAnchor constraintEqualToAnchor:layoutGuide.topAnchor],
+      [_navBar.leadingAnchor constraintEqualToAnchor:layoutGuide.leadingAnchor],
+      [_navBar.trailingAnchor
+          constraintEqualToAnchor:layoutGuide.trailingAnchor],
     ]];
   }
   return self;
diff --git a/remoting/protocol/webrtc_connection_to_host.cc b/remoting/protocol/webrtc_connection_to_host.cc
index bfc5c2c..37c795e0 100644
--- a/remoting/protocol/webrtc_connection_to_host.cc
+++ b/remoting/protocol/webrtc_connection_to_host.cc
@@ -151,7 +151,7 @@
 void WebrtcConnectionToHost::OnWebrtcTransportMediaStreamAdded(
     scoped_refptr<webrtc::MediaStreamInterface> stream) {
   if (stream->GetVideoTracks().size() > 0) {
-    GetOrCreateVideoAdapter(stream->label())->SetMediaStream(stream);
+    GetOrCreateVideoAdapter(stream->id())->SetMediaStream(stream);
   } else if (stream->GetAudioTracks().size() > 0) {
     audio_adapter_.reset(new WebrtcAudioSinkAdapter(stream, audio_consumer_));
   } else {
@@ -161,7 +161,7 @@
 
 void WebrtcConnectionToHost::OnWebrtcTransportMediaStreamRemoved(
     scoped_refptr<webrtc::MediaStreamInterface> stream) {
-  if (video_adapter_ && video_adapter_->label() == stream->label())
+  if (video_adapter_ && video_adapter_->label() == stream->id())
     video_adapter_.reset();
 }
 
diff --git a/remoting/protocol/webrtc_video_renderer_adapter.cc b/remoting/protocol/webrtc_video_renderer_adapter.cc
index 56e7701..dd30ced 100644
--- a/remoting/protocol/webrtc_video_renderer_adapter.cc
+++ b/remoting/protocol/webrtc_video_renderer_adapter.cc
@@ -73,7 +73,7 @@
 
 void WebrtcVideoRendererAdapter::SetMediaStream(
     scoped_refptr<webrtc::MediaStreamInterface> media_stream) {
-  DCHECK_EQ(media_stream->label(), label());
+  DCHECK_EQ(media_stream->id(), label());
 
   media_stream_ = std::move(media_stream);
 
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
index 2bc1700..e3a37d0 100644
--- a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
+++ b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
@@ -200,7 +200,7 @@
 
   dump->platform_private_footprint->rss_anon_bytes = rss_anon_bytes;
   dump->platform_private_footprint->vm_swap_bytes = vm_swap_bytes;
-  dump->resident_set_kb = process_metrics->GetRSS() / 1024;
+  dump->resident_set_kb = process_metrics->GetResidentSetSize() / 1024;
 
   return true;
 }
diff --git a/services/ui/service.cc b/services/ui/service.cc
index dafa492e..c69ea7d 100644
--- a/services/ui/service.cc
+++ b/services/ui/service.cc
@@ -267,48 +267,52 @@
     window_server_->SetGpuHost(std::move(gpu_host));
 
     registry_.AddInterface<mojom::Gpu>(
-        base::Bind(&Service::BindGpuRequest, base::Unretained(this)));
+        base::BindRepeating(&Service::BindGpuRequest, base::Unretained(this)));
 #if defined(OS_CHROMEOS)
     registry_.AddInterface<mojom::Arc>(
-        base::Bind(&Service::BindArcRequest, base::Unretained(this)));
+        base::BindRepeating(&Service::BindArcRequest, base::Unretained(this)));
 #endif  // defined(OS_CHROMEOS)
   }
-  registry_.AddInterface<mojom::VideoDetector>(
-      base::Bind(&Service::BindVideoDetectorRequest, base::Unretained(this)));
+  registry_.AddInterface<mojom::VideoDetector>(base::BindRepeating(
+      &Service::BindVideoDetectorRequest, base::Unretained(this)));
 
   ime_driver_.Init(context()->connector(), test_config_);
 
   registry_with_source_info_.AddInterface<mojom::AccessibilityManager>(
-      base::Bind(&Service::BindAccessibilityManagerRequest,
-                 base::Unretained(this)));
-  registry_with_source_info_.AddInterface<mojom::Clipboard>(
-      base::Bind(&Service::BindClipboardRequest, base::Unretained(this)));
+      base::BindRepeating(&Service::BindAccessibilityManagerRequest,
+                          base::Unretained(this)));
+  registry_with_source_info_.AddInterface<mojom::Clipboard>(base::BindRepeating(
+      &Service::BindClipboardRequest, base::Unretained(this)));
   registry_with_source_info_.AddInterface<mojom::DisplayManager>(
-      base::Bind(&Service::BindDisplayManagerRequest, base::Unretained(this)));
-  registry_.AddInterface<mojom::IMERegistrar>(
-      base::Bind(&Service::BindIMERegistrarRequest, base::Unretained(this)));
-  registry_.AddInterface<mojom::IMEDriver>(
-      base::Bind(&Service::BindIMEDriverRequest, base::Unretained(this)));
+      base::BindRepeating(&Service::BindDisplayManagerRequest,
+                          base::Unretained(this)));
+  registry_.AddInterface<mojom::IMERegistrar>(base::BindRepeating(
+      &Service::BindIMERegistrarRequest, base::Unretained(this)));
+  registry_.AddInterface<mojom::IMEDriver>(base::BindRepeating(
+      &Service::BindIMEDriverRequest, base::Unretained(this)));
   registry_with_source_info_.AddInterface<mojom::UserActivityMonitor>(
-      base::Bind(&Service::BindUserActivityMonitorRequest,
-                 base::Unretained(this)));
-  registry_with_source_info_.AddInterface<WindowTreeHostFactory>(base::Bind(
-      &Service::BindWindowTreeHostFactoryRequest, base::Unretained(this)));
+      base::BindRepeating(&Service::BindUserActivityMonitorRequest,
+                          base::Unretained(this)));
+  registry_with_source_info_.AddInterface<WindowTreeHostFactory>(
+      base::BindRepeating(&Service::BindWindowTreeHostFactoryRequest,
+                          base::Unretained(this)));
   registry_with_source_info_
-      .AddInterface<mojom::WindowManagerWindowTreeFactory>(
-          base::Bind(&Service::BindWindowManagerWindowTreeFactoryRequest,
-                     base::Unretained(this)));
-  registry_with_source_info_.AddInterface<mojom::WindowTreeFactory>(base::Bind(
-      &Service::BindWindowTreeFactoryRequest, base::Unretained(this)));
+      .AddInterface<mojom::WindowManagerWindowTreeFactory>(base::BindRepeating(
+          &Service::BindWindowManagerWindowTreeFactoryRequest,
+          base::Unretained(this)));
+  registry_with_source_info_.AddInterface<mojom::WindowTreeFactory>(
+      base::BindRepeating(&Service::BindWindowTreeFactoryRequest,
+                          base::Unretained(this)));
   registry_with_source_info_
       .AddInterface<discardable_memory::mojom::DiscardableSharedMemoryManager>(
-          base::Bind(&Service::BindDiscardableSharedMemoryManagerRequest,
-                     base::Unretained(this)));
+          base::BindRepeating(
+              &Service::BindDiscardableSharedMemoryManagerRequest,
+              base::Unretained(this)));
   if (test_config_) {
-    registry_.AddInterface<WindowServerTest>(base::Bind(
+    registry_.AddInterface<WindowServerTest>(base::BindRepeating(
         &Service::BindWindowServerTestRequest, base::Unretained(this)));
   }
-  registry_.AddInterface<mojom::RemoteEventDispatcher>(base::Bind(
+  registry_.AddInterface<mojom::RemoteEventDispatcher>(base::BindRepeating(
       &Service::BindRemoteEventDispatcherRequest, base::Unretained(this)));
 
   // On non-Linux platforms there will be no DeviceDataManager instance and no
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 1d12fbf..76eff548 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -351,7 +351,6 @@
   # Remove unused util sources.
   sources -= [
     "//third_party/skia/src/utils/SkCamera.cpp",
-    "//third_party/skia/src/utils/SkDumpCanvas.cpp",
     "//third_party/skia/src/utils/SkFrontBufferedStream.cpp",
     "//third_party/skia/src/utils/SkInterpolator.cpp",
     "//third_party/skia/src/utils/SkOSPath.cpp",
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 0d3a3a3..6323effb 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -400,6 +400,50 @@
       }
     ]
   },
+  "Mac 10.12 Laptop Low End": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "-v",
+          "--xvfb",
+          "--browser=release_x64"
+        ],
+        "isolate_name": "performance_test_suite",
+        "merge": {
+          "args": [
+            "--service-account-file",
+            "/creds/service_accounts/service-account-chromium-perf-histograms.json"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "performance_test_suite",
+        "override_compile_targets": [
+          "performance_test_suite"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "pool": "Chrome-perf-fyi"
+            }
+          ],
+          "expiration": 36000,
+          "hard_timeout": 36000,
+          "ignore_task_failure": false,
+          "io_timeout": 3600,
+          "shards": 26,
+          "upload_test_results": true
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"id\": \"build195-a9\"}, {\"id\": \"build196-a9\"}, {\"id\": \"build197-a9\"}, {\"id\": \"build198-a9\"}, {\"id\": \"build199-a9\"}, {\"id\": \"build200-a9\"}, {\"id\": \"build201-a9\"}, {\"id\": \"build202-a9\"}, {\"id\": \"build203-a9\"}, {\"id\": \"build204-a9\"}, {\"id\": \"build205-a9\"}, {\"id\": \"build206-a9\"}, {\"id\": \"build207-a9\"}, {\"id\": \"build208-a9\"}, {\"id\": \"build209-a9\"}, {\"id\": \"build210-a9\"}, {\"id\": \"build211-a9\"}, {\"id\": \"build212-a9\"}, {\"id\": \"build213-a9\"}, {\"id\": \"build214-a9\"}, {\"id\": \"build215-a9\"}, {\"id\": \"build216-a9\"}, {\"id\": \"build217-a9\"}, {\"id\": \"build218-a9\"}, {\"id\": \"build219-a9\"}, {\"id\": \"build220-a9\"}]"
+          ],
+          "script": "//testing/trigger_scripts/perf_device_trigger.py"
+        }
+      }
+    ]
+  },
   "Mojo Linux Perf": {
     "isolated_scripts": [
       {
@@ -477,49 +521,6 @@
           ],
           "script": "//testing/trigger_scripts/perf_device_trigger.py"
         }
-      },
-      {
-        "args": [
-          "-v",
-          "--xvfb",
-          "--browser=reference",
-          "--testing=true"
-        ],
-        "isolate_name": "performance_test_suite",
-        "merge": {
-          "args": [
-            "--configuration-name",
-            "buildbot-test",
-            "--service-account-file",
-            "/creds/service_accounts/service-account-chromium-perf-histograms.json"
-          ],
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "performance_test_suite",
-        "override_compile_targets": [
-          "performance_test_suite"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 36000,
-          "ignore_task_failure": false,
-          "io_timeout": 3600,
-          "shards": 1,
-          "upload_test_results": true
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"id\": \"swarm823-c4\"}]"
-          ],
-          "script": "//testing/trigger_scripts/perf_device_trigger.py"
-        }
       }
     ]
   }
diff --git a/testing/buildbot/filters/mash.ash_unittests.filter b/testing/buildbot/filters/mash.ash_unittests.filter
index ab0af2ac..092a3cb 100644
--- a/testing/buildbot/filters/mash.ash_unittests.filter
+++ b/testing/buildbot/filters/mash.ash_unittests.filter
@@ -150,6 +150,7 @@
 -MultiWindowResizeControllerTest.IsOverWindows
 -MultiWindowResizeControllerTest.Three
 -MultiWindowResizeControllerTest.TwoSnappedWindows
+-MultiWindowResizeControllerTest.WindowStateChange
 -NativeCursorManagerAshTest.FractionalScale
 -NativeCursorManagerAshTest.LockCursor
 -NativeCursorManagerAshTest.SetCursor
diff --git a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
index 86b11fc6..32ecb793 100644
--- a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
@@ -76,6 +76,8 @@
 -WebViewTests/WebViewTest.WebViewInBackgroundPage/1
 # Need support for blocking cookies via content settings:
 # https://crbug.com/803452.
+-ClientHintsBrowserTest.ClientHintsLifetimeNotAttachedCookiesBlocked
+-ClientHintsBrowserTest.ClientHintsNoLifetimeCookiesNotAllowed
 -ContentSettingsTest.AllowCookiesForASessionUsingExceptions
 -ContentSettingsTest.RedirectCrossOrigin
 -ContentSettingsTest.RedirectLoopCookies
@@ -90,10 +92,6 @@
 -DnsProbeBrowserTest.NoInternetProbeResultWithSlowBrokenCorrections
 
 # crbug.com/776589 Intercepting requests with net::URLRequestFilter.
--ClientHintsBrowserTest.ClientHintsHttpsSubresourceDifferentOrigin
--ClientHintsBrowserTest.ClientHintsLifetimeNotAttachedCookiesBlocked
--ClientHintsBrowserTest.ClientHintsNoLifetimeCookiesNotAllowed
--ClientHintsBrowserTest.ClientHintsNoLifetimeScriptNotAllowed
 -ContinueWhereILeftOffTest.CookiesClearedOnExit
 -DevToolsSanityTest.TestNetworkPushTime
 -DownloadExtensionTest.DownloadExtensionTest_Download_FileSystemURL
diff --git a/testing/buildbot/filters/mojo.fyi.viz.content_browsertests.filter b/testing/buildbot/filters/mojo.fyi.viz.content_browsertests.filter
index d1b598c..29e4a6bf 100644
--- a/testing/buildbot/filters/mojo.fyi.viz.content_browsertests.filter
+++ b/testing/buildbot/filters/mojo.fyi.viz.content_browsertests.filter
@@ -1,14 +1,21 @@
+#### Dev Tools
 # content::DevToolsProtocolTest::WaitForResponse times out
 # http://crbug.com/784941
 -CaptureScreenshotTest.*
 
+#### ui::LatencyInfo
 # Fails to receive histogram updates http://crbug.com/786151
 -ScrollLatencyBrowserTest.*
 
-# Need to handle lost context. https://crbug.com/776050
+# OnGpuSwapBuffersCompletedInternal not called. http://crbug.com/791557
+-MouseLatencyBrowserTest.CoalescedMouseMovesCorrectlyTerminated
+
+#### GPU
+# GPU Shutdown https://crbug.com/781714
 -WebRtcCaptureFromElementBrowserTest.CaptureFromCanvas2DHandlesContextLoss
 -WebRtcCaptureFromElementBrowserTest.CaptureFromOpaqueCanvas2DHandlesContextLoss
 
+#### WaitForChildFrameSurfaceReady
 # WaitForChildFrameSurfaceReady doesn't work http://crbug.com/763452
 -PointerLockBrowserTest.*
 -SitePerProcessGestureHitTestBrowserTest.*
@@ -17,11 +24,6 @@
 -SitePerProcessMouseWheelHitTestBrowserTest.*
 -TouchSelectionForCrossProcessFramesTests/TouchSelectionControllerClientAuraSiteIsolationTest.*
 
-# Waiting for CompositorFrames times out http://crbug.com/787941
--SitePerProcessBrowserTest.CompositorFrameSwapped
--SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFrames
--SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFramesAfterNavigation
-
 # Further WaitForChildFrameSurfaceReady doesn't work http://crbug.com/787945
 -SitePerProcessBrowserTest.CompositorViewportPixelSizeTest
 -SitePerProcessBrowserTest.GestureFlingStartEventsBubble
@@ -39,13 +41,13 @@
 -SitePerProcessHitTestBrowserTest.CrossProcessMouseCapture*
 -SitePerProcessHitTestBrowserTest.CrossProcessMouseEnterAndLeaveTest*
 -SitePerProcessHitTestBrowserTest.CursorUpdateReceivedFromCrossSiteIframe*
+-SitePerProcessHitTestBrowserTest.HitTestLayerSquashing*
+-SitePerProcessHitTestBrowserTest.HitTestNestedFrames*
+-SitePerProcessHitTestBrowserTest.HitTestWatermark*
 -SitePerProcessHitTestBrowserTest.InputEventRouterGesturePreventDefaultTargetMapTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterGestureTargetMapTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterTouchpadGestureTargetTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterWheelCoalesceTest*
--SitePerProcessHitTestBrowserTest.HitTestLayerSquashing*
--SitePerProcessHitTestBrowserTest.HitTestNestedFrames*
--SitePerProcessHitTestBrowserTest.HitTestWatermark*
 -SitePerProcessHitTestBrowserTest.NestedSurfaceHitTestTest*
 -SitePerProcessHitTestBrowserTest.OverlapSurfaceHitTestTest*
 -SitePerProcessHitTestBrowserTest.PopupMenuTest*
@@ -54,33 +56,45 @@
 -SitePerProcessHitTestBrowserTest.SubframeTouchEventRouting*
 -SitePerProcessHitTestBrowserTest.SurfaceHitTestPointerEventsNone*
 -SitePerProcessHitTestBrowserTest.SurfaceHitTestTest*
--SitePerProcessMacBrowserTest.InputEventRouterTouchpadGestureTargetTest
 -SitePerProcessNonIntegerScaleFactorHitTestBrowserTest.NestedSurfaceHitTestTest*
 
-# Copy Surface timing out http://crbug.com/785257
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTest.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestHiDPI.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCapture.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI.*
-
-# No ContextProvider http://crbug.com/785268
--ImageTransportFactoryTearDownBrowserTest.*
--ImageTransportFactoryBrowserTest.TestLostContext
-
-# GetSnapshotFromBrowser doesn't return snapshots http://crbug.com/785308
--SnapshotBrowserTest.*
+#### Compositor Frame Submission
+# Waiting for CompositorFrames times out http://crbug.com/787941
+-SitePerProcessBrowserTest.CompositorFrameSwapped
+-SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFrames
+-SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFramesAfterNavigation
 
 # Touch selection information is not provided to the browser
 # http://crbug.com/777882
 -TouchSelectionControllerClientAuraScaleFactorTest.*
 -TouchSelectionControllerClientAuraTest.*
 
+#### Tab Capture
+# Copy Surface timing out http://crbug.com/785257
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestHiDPI.*
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCapture.*
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI.*
+
+# GetSnapshotFromBrowser doesn't return snapshots http://crbug.com/785308
+-SnapshotBrowserTest.*
+
+#### VizProcessTransportFactory
+# No ContextProvider http://crbug.com/785268
+-ImageTransportFactoryTearDownBrowserTest.*
+-ImageTransportFactoryBrowserTest.TestLostContext
+
+#### Surface Invariants
+# Surface Invariants Failure on Mac http://crbug.com/817827
+-NavigationControllerBrowserTest.FrameNavigationEntry_RecreatedSubframeBackForward
+
+#### GL Renderer
+# GL Renderer Check Failure on Mac https://crbug.com/817830
+-WebContentsVideoCaptureDeviceBrowserTestP.CapturesContentChanges/*
+
+#### Unknown Flakes
 # TODO: investigate flaky failure http://crbug.com/783434
 -GpuProcessHostBrowserTest.Shutdown
 
-# OnGpuSwapBuffersCompletedInternal not called. http://crbug.com/791557
--MouseLatencyBrowserTest.CoalescedMouseMovesCorrectlyTerminated
-
 # TODO: investigate flaky failure http://crbug.com/790683
 -SitePerProcessBrowserTest.CrossSiteIframeBlockedByXFrameOptionsOrCSP
 
@@ -91,9 +105,3 @@
 
 # Flaky Result on Windows-7 http://crbug.com/883463
 -MainThreadEventQueueBrowserTest.MouseMove
-
-# Surface Invariants Failure on Mac http://crbug.com/817827
--NavigationControllerBrowserTest.FrameNavigationEntry_RecreatedSubframeBackForward
-
-# GL Renderer Check Failure on Mac https://crbug.com/817830
--WebContentsVideoCaptureDeviceBrowserTestP.CapturesContentChanges/*
diff --git a/testing/buildbot/filters/viz.browser_tests.filter b/testing/buildbot/filters/viz.browser_tests.filter
index 12e6e6e..8ed9070e 100644
--- a/testing/buildbot/filters/viz.browser_tests.filter
+++ b/testing/buildbot/filters/viz.browser_tests.filter
@@ -1,3 +1,4 @@
+#### HostFrameSinkManager Crashes
 # HostFrameSinkManager::CreateRootCompositorFrameSink Crash crbug.com/796575
 -AppWindowApiTest.OnBoundsChangedEvent
 -ForceMaximizeOnFirstRunTest.*TwoRuns
@@ -7,48 +8,35 @@
 -ShelfAppBrowserTest.LaunchAppFromDisplayWithoutFocus0
 -ShelfAppBrowserTest.LaunchAppFromDisplayWithoutFocus1
 
-# viz::HostFrameSinkManager::CreateCompositorFrameSinkSupport crash.
+#### HostFrameSinkManager::CreateCompositorFrameSinkSupport crash.
 # http://crbug.com/807465
 -ArcAccessibilityHelperBridgeBrowserTest.PreferenceChange
 
-# PictureLayerTilingSet::ComputeSkewport crash crbug.com/796594
--LoginFeedbackTest.Basic
-
-# RenderWidgetHostViewChildFrame::SendSurfaceInfoToEmbedderImpl crash
-# crbug.com/797801
--HostedAppProcessModelTest.IframeNavigationsInsideHostedApp/1
-
+# Tab Capture Errors
 # Tab Capture is still in development: crbug.com/754864
--CastStreamingApiTestWithPixelOutput.EndToEnd
--ChromeScreenshotGrabberBrowserTest.TakeScreenshot
--PluginPowerSaverBrowserTest.PosterTests
--PluginPowerSaverBrowserTest.SmallCrossOrigin
--PluginPowerSaverBrowserTest.SmallerThanPlayIcon
--TabCaptureApiPixelTest.EndToEndThroughWebRTC
--TabCaptureApiPixelTest.EndToEndWithoutRemoting
 -TabCaptureApiPixelTest.OffscreenTabEndToEnd
 -TabCaptureApiPixelTest.OffscreenTabEvilTests
 -ThumbnailTest.ShouldCaptureOnNavigatingAwayExplicitWait
 -ThumbnailTest.ShouldCaptureOnNavigatingAwaySlowPageLoad
 
-# Tab Capture on Viz fails FeedbackTest: crbug.com/810389
+# Tab Capture on Viz fails at CopyOutputRequest: crbug.com/810389
 -FeedbackTest.*
+-LoginFeedbackTest.Basic
 
+#### Compositor Frame Observation Timeouts
 # WaitForChildFrameSurfaceReady crashes crbug.com/787945
 -PDFExtensionTest.ContextMenuCoordinates
 -SitePerProcessDevToolsSanityTest.InputDispatchEventsToOOPIF
 -WebViewTests/WebViewTest.InterstitialPageFocusedWidget/1
 -WebViewTests/WebViewTest.ReloadAfterCrash/1
 
-# Flaky timeouts. http://crbug.com/807773
--PDFExtensionClipboardTest.*
-
 # WebViewBrowserTest::ScrollWaiter Doesn't Work in Viz crbug.com/796336
 -WebViewScrollBubbling/WebViewGuestScrollTouchTest.TestGuestGestureScrollsBubble/0
 -WebViewScrollBubbling/WebViewGuestScrollTouchTest.TestGuestGestureScrollsBubble/1
 -WebViewScrollBubbling/WebViewGuestScrollTouchTest.TestGuestGestureScrollsBubble/2
 -WebViewScrollBubbling/WebViewGuestScrollTouchTest.TestGuestGestureScrollsBubble/3
 
+#### FrameSinkManager Crashes
 # FrameSinkManagerImpl::RegisterFrameSinkHierarchy Crash crbug.com/796344
 # Linux and Windows failures
 -WebViewTests/WebViewTest.Shim_TestAssignSrcAfterCrash/0
@@ -56,16 +44,11 @@
 -WebViewTests/WebViewTest.Shim_TestReloadAfterTerminate/0
 -WebViewTests/WebViewTest.Shim_TestTerminateAfterExit/0
 
+#### Hit Testing
 # Incorrect Focus State crbug.com/818205
 -WebViewTests/WebViewFocusTest.TouchFocusesEmbedder/0
 
-# Windows only failures
--ExtensionApiTabTest.TabsOnUpdated
-
-# Individual test failure, Windows only
--CloudPrintPolicyTest.NormalPassedFlag
--PolicyMakeDefaultBrowserTest.MakeDefaultDisabled
-
+#### Surface Invariants
 # Surface Invariants Failure on Mac http://crbug.com/817827
 -ActivityLogApiTest.TriggerEvent
 -AllUrlsApiTest.WhitelistedExtension
@@ -86,6 +69,7 @@
 -ServiceWorkerTestWithNativeBindings/ServiceWorkerTest.ServiceWorkerSuspensionOnExtensionUnload/0
 -TabManagerTest.DiscardTabsWithMinimizedAndOccludedWindows
 
+#### GL Renderer
 # GL Renderer Check Failure on Mac https://crbug.com/817830
 -DeclarativeNetRequestBrowserTest.BlockRequests_UrlFilter/0
 -DeclarativeNetRequestBrowserTest.BlockRequests_UrlFilter/1
@@ -113,3 +97,7 @@
 -SSLUIWorkerFetchTest.MixedContentSubFrame/1
 -ThumbnailTest.ShouldContainProperContentIfCapturedOnNavigatingAway
 -ThumbnailTest.ShouldContainProperContentIfCapturedOnTabSwitch
+
+# Unknown Flakes
+# Flaky timeouts. http://crbug.com/807773
+-PDFExtensionClipboardTest.*
diff --git a/testing/buildbot/filters/viz.content_browsertests.filter b/testing/buildbot/filters/viz.content_browsertests.filter
index 3b0a143f..931a2052 100644
--- a/testing/buildbot/filters/viz.content_browsertests.filter
+++ b/testing/buildbot/filters/viz.content_browsertests.filter
@@ -1,21 +1,21 @@
+#### Dev Tools
 # content::DevToolsProtocolTest::WaitForResponse times out
 # http://crbug.com/784941
 -CaptureScreenshotTest.*
 
-# FrameWatcher::WaitFrames times out http://crbug.com/785013
--NonBlockingEventBrowserTest.*
--TouchActionBrowserTest.*
-
+#### ui::LatencyInfo
 # Fails to receive histogram updates http://crbug.com/786151
 -ScrollLatencyBrowserTest.*
 
-# Flaky timeout while waiting for scoll update http://crbug.com/786132
--WheelScrollLatchingBrowserTest.WheelScrollingRelatchWhenLatchedScrollerRemoved
+# OnGpuSwapBuffersCompletedInternal not called. http://crbug.com/791557
+-MouseLatencyBrowserTest.CoalescedMouseMovesCorrectlyTerminated
 
-# Need to handle lost context. https://crbug.com/776050
+#### GPU
+# GPU Shutdown https://crbug.com/781714
 -WebRtcCaptureFromElementBrowserTest.CaptureFromCanvas2DHandlesContextLoss
 -WebRtcCaptureFromElementBrowserTest.CaptureFromOpaqueCanvas2DHandlesContextLoss
 
+#### WaitForChildFrameSurfaceReady
 # WaitForChildFrameSurfaceReady doesn't work http://crbug.com/763452
 -PointerLockBrowserTest.*
 -SitePerProcessGestureHitTestBrowserTest.*
@@ -24,16 +24,12 @@
 -SitePerProcessMouseWheelHitTestBrowserTest.*
 -TouchSelectionForCrossProcessFramesTests/TouchSelectionControllerClientAuraSiteIsolationTest.*
 
-# Waiting for CompositorFrames times out http://crbug.com/787941
--SitePerProcessBrowserTest.CompositorFrameSwapped
--SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFrames
--SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFramesAfterNavigation
-
 # Further WaitForChildFrameSurfaceReady doesn't work http://crbug.com/787945
 -SitePerProcessBrowserTest.CompositorViewportPixelSizeTest
 -SitePerProcessBrowserTest.GestureFlingStartEventsBubble
 -SitePerProcessBrowserTest.NavigateCrashedSubframeToSameSite
 -SitePerProcessBrowserTest.OOPIFDetachDuringAnimation
+-SitePerProcessBrowserTest.PhysicalBackingSizeTest
 -SitePerProcessBrowserTest.ScrollBubblingFromNestedOOPIFTest
 -SitePerProcessBrowserTest.ScrollBubblingFromOOPIFTest
 -SitePerProcessBrowserTest.ScrollBubblingFromOOPIFWithBodyOverflowHidden
@@ -46,13 +42,13 @@
 -SitePerProcessHitTestBrowserTest.CrossProcessMouseCapture*
 -SitePerProcessHitTestBrowserTest.CrossProcessMouseEnterAndLeaveTest*
 -SitePerProcessHitTestBrowserTest.CursorUpdateReceivedFromCrossSiteIframe*
+-SitePerProcessHitTestBrowserTest.HitTestLayerSquashing*
+-SitePerProcessHitTestBrowserTest.HitTestNestedFrames*
+-SitePerProcessHitTestBrowserTest.HitTestWatermark*
 -SitePerProcessHitTestBrowserTest.InputEventRouterGesturePreventDefaultTargetMapTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterGestureTargetMapTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterTouchpadGestureTargetTest*
 -SitePerProcessHitTestBrowserTest.InputEventRouterWheelCoalesceTest*
--SitePerProcessHitTestBrowserTest.HitTestLayerSquashing*
--SitePerProcessHitTestBrowserTest.HitTestNestedFrames*
--SitePerProcessHitTestBrowserTest.HitTestWatermark*
 -SitePerProcessHitTestBrowserTest.NestedSurfaceHitTestTest*
 -SitePerProcessHitTestBrowserTest.OverlapSurfaceHitTestTest*
 -SitePerProcessHitTestBrowserTest.PopupMenuTest*
@@ -63,30 +59,43 @@
 -SitePerProcessHitTestBrowserTest.SurfaceHitTestTest*
 -SitePerProcessNonIntegerScaleFactorHitTestBrowserTest.NestedSurfaceHitTestTest*
 
-# Copy Surface timing out http://crbug.com/785257
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTest.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestHiDPI.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCapture.*
--GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI.*
-
-# No ContextProvider http://crbug.com/785268
--ImageTransportFactoryTearDownBrowserTest.*
--ImageTransportFactoryBrowserTest.TestLostContext
-
-# GetSnapshotFromBrowser doesn't return snapshots http://crbug.com/785308
--SnapshotBrowserTest.*
+#### Compositor Frame Submission
+# Waiting for CompositorFrames times out http://crbug.com/787941
+-SitePerProcessBrowserTest.CompositorFrameSwapped
+-SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFrames
+-SitePerProcessBrowserTest.HiddenOOPIFWillNotGenerateCompositorFramesAfterNavigation
 
 # Touch selection information is not provided to the browser
 # http://crbug.com/777882
 -TouchSelectionControllerClientAuraScaleFactorTest.*
 -TouchSelectionControllerClientAuraTest.*
 
+#### Tab Capture
+# Copy Surface timing out http://crbug.com/785257
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestHiDPI.*
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCapture.*
+-GLAndSoftwareCompositing/CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI.*
+
+# GetSnapshotFromBrowser doesn't return snapshots http://crbug.com/785308
+-SnapshotBrowserTest.*
+
+#### VizProcessTransportFactory
+# No ContextProvider http://crbug.com/785268
+-ImageTransportFactoryTearDownBrowserTest.*
+-ImageTransportFactoryBrowserTest.TestLostContext
+
+#### Surface Invariants
+# Surface Invariants Failure on Mac http://crbug.com/817827
+-NavigationControllerBrowserTest.FrameNavigationEntry_RecreatedSubframeBackForward
+
+#### GL Renderer
+# GL Renderer Check Failure on Mac https://crbug.com/817830
+-WebContentsVideoCaptureDeviceBrowserTestP.CapturesContentChanges/*
+
+#### Unknown Flakes
 # TODO: investigate flaky failure http://crbug.com/783434
 -GpuProcessHostBrowserTest.Shutdown
 
-# OnGpuSwapBuffersCompletedInternal not called. http://crbug.com/791557
--MouseLatencyBrowserTest.CoalescedMouseMovesCorrectlyTerminated
-
 # TODO: investigate flaky failure http://crbug.com/790683
 -SitePerProcessBrowserTest.CrossSiteIframeBlockedByXFrameOptionsOrCSP
 
@@ -95,13 +104,5 @@
 -IsolatedOriginTest.ProcessLimit
 -SitePerProcessBrowserTest.RFPHDestruction
 
-# Tests failures related to surface sync. http://crbug.com/793302
--RenderWidgetInitialSizeTest.InitialSize
--RenderWidgetTest.OnResize
-
 # Flaky Result on Windows-7 http://crbug.com/883463
 -MainThreadEventQueueBrowserTest.MouseMove
-
-# Still work TODO to trigger render process kills on either surface invariant
-# violations or copy request permission violations. http://crbug.com/771354
--RenderWidgetHostBrowserTest.ProhibitsCopyRequestsFromRenderer
diff --git a/testing/buildbot/filters/viz.content_unittests.filter b/testing/buildbot/filters/viz.content_unittests.filter
index 5cba3a36..b890bf3 100644
--- a/testing/buildbot/filters/viz.content_unittests.filter
+++ b/testing/buildbot/filters/viz.content_unittests.filter
@@ -1,5 +1,4 @@
--RenderWidgetHostInputEventRouterTest.DoNotCoalesceGestureEvents
--RenderWidgetHostInputEventRouterTest.DoNotCoalesceTouchEvents
+# Legacy SubmitCompositorFrame tests
 -RenderWidgetHostViewAuraSurfaceSynchronizationTest.CompositorFrameSinkChange
 -RenderWidgetHostViewAuraSurfaceSynchronizationTest.DiscardDelegatedFrames
 -RenderWidgetHostViewAuraSurfaceSynchronizationTest.DropFallbackWhenHidden
@@ -10,13 +9,18 @@
 -RenderWidgetHostViewAuraTest.ForwardsBeginFrameAcks
 -RenderWidgetHostViewAuraTest.HitTestRegionListSubmitted
 -RenderWidgetHostViewAuraTest.OutputSurfaceIdChange
--RenderWidgetHostViewAuraTest.TwoOutputSurfaces
--RenderWidgetHostViewChildFrameTest.FrameEviction
 -RenderWidgetHostViewChildFrameTest.SwapCompositorFrame
 -RenderWidgetHostViewGuestSurfaceTest.TestGuestSurface
 
+# Legacy using TestImageTransportFactory::GetFrameSinkManager
+-RenderWidgetHostViewAuraTest.ForwardsBeginFrameAcks
+-RenderWidgetHostViewAuraTest.TwoOutputSurfaces
+-RenderWidgetHostViewChildFrameTest.FrameEviction
+
 # Not finding the correct target crbug.com/796605
 -RenderWidgetHostInputEventRouterTest.DoNotChangeTargetViewDuringTouchScrollGesture
+-RenderWidgetHostInputEventRouterTest.DoNotCoalesceGestureEvents
+-RenderWidgetHostInputEventRouterTest.DoNotCoalesceTouchEvents
 
 # TODO(crbug.com/601869): Reflector needs to be rewritten for viz.
 -ReflectorImplTest.*
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 55ef2ca..890b7fb 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3034,17 +3034,6 @@
                         "bg_sub_limit": "4"
                     },
                     "enable_features": [
-                        "ReportRendererPeakMemoryStats",
-                        "ResourceLoadScheduler"
-                    ]
-                },
-                {
-                    "name": "Enabled_bg_limit_8",
-                    "params": {
-                        "bg_limit": "8"
-                    },
-                    "enable_features": [
-                        "ReportRendererPeakMemoryStats",
                         "ResourceLoadScheduler"
                     ]
                 }
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
index 064b67b..ab6815d 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
@@ -9,8 +9,9 @@
 crbug.com/794631 virtual/unified-autoplay/external/wpt/feature-policy/autoplay-allowed-by-feature-policy-attribute.https.sub.html [ Failure ]
 crbug.com/794631 virtual/unified-autoplay/external/wpt/feature-policy/autoplay-allowed-by-feature-policy.https.sub.html [ Failure ]
 
-# https://crbug.com/793127: Crash related to frame consolidation CL.
+# https://crbug.com/793127: NOTREACHED() from clamy@ tickled by frame consolidation CL?
 crbug.com/793127 http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash ]
+crbug.com/793127 external/wpt/content-security-policy/securitypolicyviolation/upgrade-insecure-requests-reporting.https.html [ Crash ]
 
 # https://crbug.com/769502: PlzNavigate-related, not-yet-investigated timeout.
 crbug.com/769502 fast/loader/recursive-before-unload-crash.html [ Timeout ]
@@ -134,43 +135,15 @@
 # TODO(lukasza, alexmos): Triage these failures.
 Bug(none) external/wpt/IndexedDB/interleaved-cursors.html [ Timeout ]
 Bug(none) external/wpt/xhr/xmlhttprequest-sync-default-feature-policy.sub.html [ Timeout ]
-Bug(none) external/wpt/content-security-policy/frame-ancestors/frame-ancestors-nested-cross-in-cross-star-allow.html [ Timeout ]
 Bug(none) external/wpt/content-security-policy/frame-ancestors/frame-ancestors-nested-cross-in-same-star-allow.html [ Timeout ]
-Bug(none) external/wpt/content-security-policy/securitypolicyviolation/upgrade-insecure-requests-reporting.https.html [ Crash ]
-Bug(none) external/wpt/credential-management/credentialscontainer-create-basics.https.html [ Crash ]
 Bug(none) external/wpt/css-fonts/font-display/font-display.html [ Crash ]
-Bug(none) external/wpt/dom/nodes/Element-matches.html [ Failure ]
-Bug(none) external/wpt/encrypted-media/encrypted-media-default-feature-policy.https.sub.html [ Timeout ]
-Bug(none) external/wpt/feature-policy/payment-allowed-by-feature-policy.https.sub.html [ Timeout ]
-Bug(none) external/wpt/feature-policy/payment-disabled-by-feature-policy.https.sub.html [ Timeout ]
 Bug(none) external/wpt/fetch/api/basic/keepalive.html [ Timeout ]
 Bug(none) external/wpt/fullscreen/api/element-ready-check-allowed-cross-origin-manual.sub.html [ Failure ]
 Bug(none) external/wpt/fullscreen/api/element-ready-check-not-allowed-cross-origin-manual.sub.html [ Failure ]
 Bug(none) external/wpt/html/browsers/browsing-the-web/navigating-across-documents/004.html [ Timeout ]
-Bug(none) external/wpt/html/browsers/history/the-location-interface/location-origin-idna.sub.window.html [ Timeout ]
-Bug(none) external/wpt/html/semantics/embedded-content/media-elements/error-codes/error.html [ Timeout ]
-Bug(none) external/wpt/html/semantics/embedded-content/media-elements/ready-states/autoplay.html [ Timeout ]
-Bug(none) external/wpt/html/semantics/embedded-content/media-elements/seeking/seek-to-currentTime.html [ Timeout ]
 Bug(none) external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub.html [ Failure ]
-Bug(none) external/wpt/preload/dynamic-adding-preload-nonce.html [ Failure ]
-Bug(none) external/wpt/preload/dynamic-adding-preload.html [ Failure ]
-Bug(none) external/wpt/preload/link-header-on-subresource.html [ Failure ]
-Bug(none) external/wpt/preload/link-header-preload-delay-onload.html [ Failure ]
-Bug(none) external/wpt/preload/link-header-preload-nonce.html [ Failure ]
-Bug(none) external/wpt/preload/link-header-preload.html [ Failure ]
-Bug(none) external/wpt/preload/onerror-event.html [ Failure ]
-Bug(none) external/wpt/preload/onload-event.html [ Failure ]
-Bug(none) external/wpt/preload/preload-with-type.html [ Failure ]
-Bug(none) external/wpt/preload/single-download-late-used-preload.html [ Failure ]
-Bug(none) external/wpt/preload/single-download-preload.html [ Failure ]
-Bug(none) external/wpt/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html [ Failure ]
-Bug(none) external/wpt/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash Timeout ]
 Bug(none) external/wpt/webmessaging/event.origin.sub.htm [ Failure ]
 Bug(none) external/wpt/webmessaging/postMessage_asterisk_xorigin.sub.htm [ Failure ]
-Bug(none) external/wpt/webmessaging/with-ports/021.html [ Failure ]
-Bug(none) external/wpt/webmessaging/without-ports/020.html [ Failure ]
-Bug(none) external/wpt/webusb/usb-disabled-by-feature-policy.https.sub.html [ Timeout ]
-Bug(none) external/wpt/webvr/webvr-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html [ Timeout ]
 Bug(none) storage/indexeddb/blob-valid-before-commit.html [ Failure ]
 Bug(none) virtual/outofblink-cors/external/wpt/fetch/api/basic/keepalive.html [ Timeout ]
 
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index ef453e9b..0b5bea9 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -45,8 +45,8 @@
 [ Win Linux ] fast/dom/partial-layout-overlay-scrollbars.html [ WontFix ]
 
 # Tests trak table support / letter spacing specific to Mac system font
-# Only maintain this for latest Mac OS
-[ Win Linux Android Mac10.10 Mac10.11 Mac10.12 ] fast/text/mac-system-ui-trak.html [ WontFix ]
+# Only maintain this for the latest macOS (and Retina is currently 10.12).
+[ Win Linux Android Mac10.10 Mac10.11 Mac10.12 Retina ] fast/text/mac-system-ui-trak.html [ WontFix ]
 
  # Mac's popup behavior is different.
 [ Mac ] fast/forms/select/menulist-onchange-fired-with-key-up-down.html [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index e3ca497..eaee435 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1319,9 +1319,9 @@
 crbug.com/736319 [ Linux Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-006a.html [ Failure Pass ]
 crbug.com/736319 [ Linux Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-007.html [ Failure Pass ]
 
-crbug.com/752449 [ Mac10.12 Mac10.13 ] external/wpt/css/css-fonts/matching/fixed-stretch-style-over-weight.html [ Failure ]
-crbug.com/752449 [ Mac10.12 Mac10.13 ] external/wpt/css/css-fonts/matching/stretch-distance-over-weight-distance.html [ Failure ]
-crbug.com/752449 [ Mac10.12 ] external/wpt/css/css-fonts/matching/style-ranges-over-weight-direction.html [ Failure ]
+crbug.com/752449 [ Mac10.12 Mac10.13 Retina ] external/wpt/css/css-fonts/matching/fixed-stretch-style-over-weight.html [ Failure ]
+crbug.com/752449 [ Mac10.12 Retina ] external/wpt/css/css-fonts/matching/stretch-distance-over-weight-distance.html [ Failure ]
+crbug.com/752449 [ Mac10.12 Retina ] external/wpt/css/css-fonts/matching/style-ranges-over-weight-direction.html [ Failure ]
 crbug.com/796619 [ Win10 ] external/wpt/css/css-fonts/matching/fixed-stretch-style-over-weight.html [ Failure ]
 crbug.com/796619 [ Win10 ] external/wpt/css/css-fonts/matching/stretch-distance-over-weight-distance.html [ Failure ]
 crbug.com/796619 [ Win10 ] external/wpt/css/css-fonts/matching/style-ranges-over-weight-direction.html [ Failure ]
@@ -1671,8 +1671,6 @@
 crbug.com/803200 external/wpt/websockets/cookies/006.html?wss [ Failure ]
 crbug.com/803200 external/wpt/websockets/opening-handshake/005.html?wss [ Pass Failure ]
 
-crbug.com/805463 external/wpt/acid/acid3/numbered-tests.html [ Skip ]
-
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 [ Mac10.11 ] external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.html [ Timeout ]
 crbug.com/626703 [ Linux Win ] external/wpt/css/css-text/letter-spacing/letter-spacing-control-chars-001.html [ Failure ]
@@ -3410,9 +3408,6 @@
 
 # Sheriff 2018-03-07
 crbug.com/819591 virtual/threaded/animations/hit-testing/composited-with-hit-testing.html [ Failure Pass ]
-crbug.com/819683 [ Linux ] paint/invalidation/background/obscured-background-no-repaint.html [ Crash ]
-crbug.com/819851 [ Mac ] paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown.html [ Skip ]
-crbug.com/819851 [ Mac ] paint/invalidation/forms/radio-focus-by-mouse-then-keydown.html [ Skip ]
 crbug.com/819778 [ Linux ] external/wpt/css/cssom-view/interfaces.html [ Pass Timeout ]
 
 # Sheriff 2018-03-08
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-expected.txt
index 2677032f..36067da 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-expected.txt
@@ -595,7 +595,8 @@
       "backendNodeId": "<number>",
       "childNodeIndexes": [
         53
-      ]
+      ],
+      "shadowRootType": "open"
     },
     {
       "nodeType": 3,
@@ -625,7 +626,8 @@
           "value": "font-family: ahem;"
         }
       ],
-      "layoutNodeIndex": 28
+      "layoutNodeIndex": 28,
+      "shadowRootType": "open"
     },
     {
       "nodeType": 1,
@@ -635,7 +637,8 @@
       "childNodeIndexes": [
         57
       ],
-      "layoutNodeIndex": 29
+      "layoutNodeIndex": 29,
+      "shadowRootType": "open"
     },
     {
       "nodeType": 3,
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-input-value-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-input-value-expected.txt
index 30e63cf..eae4313 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-input-value-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-input-value-expected.txt
@@ -119,7 +119,8 @@
           "name": "name",
           "value": "user-agent-custom-assign-slot"
         }
-      ]
+      ],
+      "shadowRootType": "user-agent"
     },
     {
       "nodeType": 1,
@@ -205,6 +206,7 @@
         16
       ],
       "layoutNodeIndex": 7,
+      "shadowRootType": "user-agent",
       "isClickable": true
     },
     {
@@ -418,6 +420,7 @@
         31
       ],
       "layoutNodeIndex": 22,
+      "shadowRootType": "user-agent",
       "isClickable": true
     },
     {
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/animated-row-background.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/animated-row-background.html
index b74fa63..b8faff44 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/table/animated-row-background.html
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/animated-row-background.html
@@ -12,6 +12,9 @@
 <img id="image">
 <script src="../resources/text-based-repaint.js"></script>
 <script>
+// The occasional under-invalidation of svg animation as background is harmless.
+if (window.internals)
+  internals.runtimeFlags.paintUnderInvalidationCheckingEnabled = false;
 window.testIsAsync = true;
 function repaintTest() {
   if (window.internals)
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.png
deleted file mode 100644
index 7aa97bf..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.txt
deleted file mode 100644
index ed6cb91..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counter-04-b-expected.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-layer at (0,0) size 800x600
-  LayoutView at (0,0) size 800x600
-layer at (0,0) size 800x103
-  LayoutBlockFlow {HTML} at (0,0) size 800x103
-    LayoutBlockFlow {BODY} at (8,16) size 784x79
-      LayoutBlockFlow {P} at (0,0) size 784x18
-        LayoutText {#text} at (0,0) size 237x18
-          text run at (0,0) width 237: "The following two lines should look "
-        LayoutInline {EM} at (0,0) size 93x18
-          LayoutText {#text} at (236,0) size 93x18
-            text run at (236,0) width 93: "approximately"
-        LayoutText {#text} at (328,0) size 66x18
-          text run at (328,0) width 66: " the same:"
-      LayoutBlockFlow {DIV} at (0,34) size 784x19
-        LayoutInline {SPAN} at (0,0) size 9x18
-          LayoutInline {<pseudo:before>} at (0,0) size 9x18
-            LayoutCounter (anonymous) at (0,1) size 9x18
-              text run at (0,1) width 9: "\x{25A0}"
-        LayoutText {#text} at (8,1) size 5x18
-          text run at (8,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (12,1) size 10x18
-              text run at (12,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (21,1) size 5x18
-          text run at (21,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (25,1) size 10x18
-              text run at (25,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (34,1) size 5x18
-          text run at (34,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 9x18
-          LayoutInline {<pseudo:before>} at (0,0) size 9x18
-            LayoutCounter (anonymous) at (38,1) size 9x18
-              text run at (38,1) width 9: "\x{25A0}"
-        LayoutText {#text} at (46,1) size 5x18
-          text run at (46,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (50,1) size 10x18
-              text run at (50,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (59,1) size 5x18
-          text run at (59,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (63,1) size 10x18
-              text run at (63,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (72,1) size 5x18
-          text run at (72,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 9x18
-          LayoutInline {<pseudo:before>} at (0,0) size 9x18
-            LayoutCounter (anonymous) at (76,1) size 9x18
-              text run at (76,1) width 9: "\x{25A0}"
-        LayoutText {#text} at (84,1) size 5x18
-          text run at (84,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (88,1) size 10x18
-              text run at (88,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (97,1) size 5x18
-          text run at (97,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (101,1) size 10x18
-              text run at (101,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (110,1) size 5x18
-          text run at (110,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 9x18
-          LayoutInline {<pseudo:before>} at (0,0) size 9x18
-            LayoutCounter (anonymous) at (114,1) size 9x18
-              text run at (114,1) width 9: "\x{25A0}"
-        LayoutText {#text} at (122,1) size 5x18
-          text run at (122,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (126,1) size 10x18
-              text run at (126,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (135,1) size 5x18
-          text run at (135,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 10x18
-          LayoutInline {<pseudo:before>} at (0,0) size 10x18
-            LayoutCounter (anonymous) at (139,1) size 10x18
-              text run at (139,1) width 10: "\x{25A0}"
-        LayoutText {#text} at (0,0) size 0x0
-      LayoutBlockFlow {DIV} at (0,53) size 784x26
-        LayoutText {#text} at (0,6) size 236x18
-          text run at (0,6) width 20: "\x{25FE} "
-          text run at (20,6) width 20: "\x{25FE} "
-          text run at (40,6) width 20: "\x{25FE} "
-          text run at (60,6) width 20: "\x{25FE} "
-          text run at (80,6) width 20: "\x{25FE} "
-          text run at (100,6) width 20: "\x{25FE} "
-          text run at (120,6) width 20: "\x{25FE} "
-          text run at (140,6) width 20: "\x{25FE} "
-          text run at (160,6) width 20: "\x{25FE} "
-          text run at (180,6) width 20: "\x{25FE} "
-          text run at (200,6) width 20: "\x{25FE} "
-          text run at (220,6) width 16: "\x{25FE}"
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.png
deleted file mode 100644
index b5be8c9e..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.txt
deleted file mode 100644
index 978769db..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/css2.1/t1202-counters-04-b-expected.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-layer at (0,0) size 800x600
-  LayoutView at (0,0) size 800x600
-layer at (0,0) size 800x103
-  LayoutBlockFlow {HTML} at (0,0) size 800x103
-    LayoutBlockFlow {BODY} at (8,16) size 784x79
-      LayoutBlockFlow {P} at (0,0) size 784x18
-        LayoutText {#text} at (0,0) size 237x18
-          text run at (0,0) width 237: "The following two lines should look "
-        LayoutInline {EM} at (0,0) size 93x18
-          LayoutText {#text} at (236,0) size 93x18
-            text run at (236,0) width 93: "approximately"
-        LayoutText {#text} at (328,0) size 66x18
-          text run at (328,0) width 66: " the same:"
-      LayoutBlockFlow {DIV} at (0,34) size 784x19
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (0,1) size 22x18
-              text run at (0,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (21,1) size 5x18
-          text run at (21,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (25,1) size 22x18
-              text run at (25,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (46,1) size 5x18
-          text run at (46,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 23x18
-          LayoutInline {<pseudo:before>} at (0,0) size 23x18
-            LayoutCounter (anonymous) at (50,1) size 23x18
-              text run at (50,1) width 23: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (72,1) size 5x18
-          text run at (72,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (76,1) size 22x18
-              text run at (76,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (97,1) size 5x18
-          text run at (97,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (101,1) size 22x18
-              text run at (101,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (122,1) size 5x18
-          text run at (122,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 23x18
-          LayoutInline {<pseudo:before>} at (0,0) size 23x18
-            LayoutCounter (anonymous) at (126,1) size 23x18
-              text run at (126,1) width 23: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (148,1) size 5x18
-          text run at (148,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (152,1) size 22x18
-              text run at (152,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (173,1) size 5x18
-          text run at (173,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (177,1) size 22x18
-              text run at (177,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (198,1) size 5x18
-          text run at (198,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 23x18
-          LayoutInline {<pseudo:before>} at (0,0) size 23x18
-            LayoutCounter (anonymous) at (202,1) size 23x18
-              text run at (202,1) width 23: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (224,1) size 5x18
-          text run at (224,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (228,1) size 22x18
-              text run at (228,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (249,1) size 5x18
-          text run at (249,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 22x18
-          LayoutInline {<pseudo:before>} at (0,0) size 22x18
-            LayoutCounter (anonymous) at (253,1) size 22x18
-              text run at (253,1) width 22: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (274,1) size 5x18
-          text run at (274,1) width 5: " "
-        LayoutInline {SPAN} at (0,0) size 23x18
-          LayoutInline {<pseudo:before>} at (0,0) size 23x18
-            LayoutCounter (anonymous) at (278,1) size 23x18
-              text run at (278,1) width 23: "\x{25A0}.\x{25A0}"
-        LayoutText {#text} at (0,0) size 0x0
-      LayoutBlockFlow {DIV} at (0,53) size 784x26
-        LayoutText {#text} at (0,6) size 476x18
-          text run at (0,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (40,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (80,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (120,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (160,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (200,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (240,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (280,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (320,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (360,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (400,6) width 40: "\x{25FE}.\x{25FE} "
-          text run at (440,6) width 36: "\x{25FE}.\x{25FE}"
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/editing/deleting/delete-line-006-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/editing/deleting/delete-line-006-expected.png
deleted file mode 100644
index 4bc44f34..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/editing/deleting/delete-line-006-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/external/wpt/battery-status/battery-full-manual.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/external/wpt/battery-status/battery-full-manual.https-expected.txt
deleted file mode 100644
index 0f4003d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/external/wpt/battery-status/battery-full-manual.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Battery Test: battery full, charger plugged in assert_equals: chargingTime must be set to 0 expected 0 but got Infinity
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/block/positioning/047-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/block/positioning/047-expected.png
deleted file mode 100644
index f8ca3a3..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/block/positioning/047-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/datetimelocal/datetimelocal-appearance-l10n-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/datetimelocal/datetimelocal-appearance-l10n-expected.png
deleted file mode 100644
index 64a5f017..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/datetimelocal/datetimelocal-appearance-l10n-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/formmove3-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/formmove3-expected.png
deleted file mode 100644
index 93f5e17e..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/formmove3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/month/month-appearance-l10n-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/month/month-appearance-l10n-expected.png
deleted file mode 100644
index 0545e8c..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/month/month-appearance-l10n-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/number/number-appearance-spinbutton-layer-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/number/number-appearance-spinbutton-layer-expected.png
deleted file mode 100644
index 519c89cc..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/number/number-appearance-spinbutton-layer-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.png
deleted file mode 100644
index 59184aed..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.txt
deleted file mode 100644
index 732e3be..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/search/search-display-none-cancel-button-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-layer at (0,0) size 800x600
-  LayoutView at (0,0) size 800x600
-layer at (0,0) size 800x600
-  LayoutBlockFlow {HTML} at (0,0) size 800x600
-    LayoutBlockFlow {BODY} at (8,8) size 784x584
-      LayoutText {#text} at (0,0) size 510x18
-        text run at (0,0) width 510: "This tests that the display:none style will work on a search field's cancel button."
-      LayoutBR {BR} at (509,14) size 1x0
-      LayoutTextControl {INPUT} at (0,18) size 133x19 [bgcolor=#FFFFFF] [border: (2px inset #EEEEEE)]
-        LayoutFlexibleBox {DIV} at (4,3) size 125x13
-          LayoutBlockFlow {DIV} at (0,0) size 125x13
-      LayoutText {#text} at (0,0) size 0x0
-layer at (12,29) size 125x13
-  LayoutBlockFlow {DIV} at (0,0) size 125x13
-    LayoutText {#text} at (0,0) size 20x13
-      text run at (0,0) width 20: "test"
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/003-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/003-expected.png
deleted file mode 100644
index 06ffd8f..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/003-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/select-background-none-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/select-background-none-expected.png
deleted file mode 100644
index 479595c..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/select/select-background-none-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-expected.png
deleted file mode 100644
index 60343011..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/input-spaces-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/input-spaces-expected.png
deleted file mode 100644
index 33b9d6b6..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/input-spaces-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/textfield-overflow-by-value-update-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/textfield-overflow-by-value-update-expected.png
deleted file mode 100644
index 6a5adf5..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/forms/text/textfield-overflow-by-value-update-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/invalid/014-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/invalid/014-expected.png
deleted file mode 100644
index 6cbaa21..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/invalid/014-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/replaced/replaced-breaking-mixture-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/replaced/replaced-breaking-mixture-expected.png
deleted file mode 100644
index c169fca64..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/replaced/replaced-breaking-mixture-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/color-emoji-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/color-emoji-expected.png
deleted file mode 100644
index 6759624..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/color-emoji-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/complex-preferred-logical-widths-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/complex-preferred-logical-widths-expected.png
deleted file mode 100644
index fa2211a..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/complex-preferred-logical-widths-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/emoticons-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/emoticons-expected.png
deleted file mode 100644
index dba363d..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/emoticons-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-expected.png
new file mode 100644
index 0000000..30bd61f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-nested-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-nested-expected.png
new file mode 100644
index 0000000..5103b0c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/selection/flexbox-selection-nested-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/unicode-fallback-font-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/unicode-fallback-font-expected.png
deleted file mode 100644
index 62b09ba..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/text/unicode-fallback-font-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/writing-mode/text-combine-various-fonts-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fast/writing-mode/text-combine-various-fonts-expected.png
deleted file mode 100644
index 129f53f..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fast/writing-mode/text-combine-various-fonts-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/fonts/sans-serif-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/fonts/sans-serif-expected.png
deleted file mode 100644
index 9b223cd..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/fonts/sans-serif-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/http/tests/misc/bad-charset-alias-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/http/tests/misc/bad-charset-alias-expected.txt
deleted file mode 100644
index 88c213f6..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/http/tests/misc/bad-charset-alias-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Test that iso-8859-1 aliases that aren't known to Firefox and IE aren't supported (we should fall back to parent frame charset).
-
-SUCCESS
-
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/ogham-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/ogham-expected.txt
deleted file mode 100644
index f56d468..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/ogham-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
- ᚁᚂᚃᚄᚅᚆᚇᚈᚉᚊᚋᚌᚍᚎᚏᚐᚑᚒᚓᚔᚕᚖᚗᚘᚙᚚ᚛᚜
-#oghammonofont:
-"Noto Sans Ogham" : 29
-
- ᚁᚂᚃᚄᚅᚆᚇᚈᚉᚊᚋᚌᚍᚎᚏᚐᚑᚒᚓᚔᚕᚖᚗᚘᚙᚚ᚛᚜
-#oghamdefaultfont:
-"Noto Sans Ogham" : 29
-
-There should be two lines of Ogham above.
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/tifinagh-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/tifinagh-expected.txt
deleted file mode 100644
index 4793163..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/inspector-protocol/layout-fonts/tifinagh-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-ⵉⵎⴷⴰⵏⴻⵏ, ⴰⴽⴽⴻⵏ ⵎⴰ ⵍⵍⴰⵏ ⵜⵜⵍⴰⵍⴻⵏ ⴷ ⵉⵍⴻⵍⵍⵉⵢⴻⵏ ⵎⵙⴰⵡⴰⵏ ⴷⵉ ⵍⵃⵡⴻⵕⵎⴰ ⴷ ⵢⵉⵣⴻⵔⴼⴰⵏ-ⵖⵓⵔ ⵙⴻⵏ ⵜⴰⵎⵙⴰⴽⵡⵉⵜ ⴷ ⵍⴰⵇⵓⴻⵍ ⵓ ⵢⴻⵙⵙⴻⴼⴽ ⴰⴷ-ⵜⵉⵍⵉ ⵜⴻⴳⵎⴰⵜⵜ ⴳⴰⵔ ⴰⵙⴻⵏ
-#tifinagh_text:
-"Noto Sans Tifinagh" : 109,
-"Times" : 23
-
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/media/video-zoom-controls-expected.png
deleted file mode 100644
index d144fd2..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/media/video-zoom-controls-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/coords-units-03-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/coords-units-03-b-expected.png
deleted file mode 100644
index e8ebf3a..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/coords-units-03-b-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/painting-marker-06-f-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/painting-marker-06-f-expected.png
deleted file mode 100644
index d8f72d75..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/painting-marker-06-f-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-02-b-expected.png
deleted file mode 100644
index 270060a..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-02-b-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-09-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-09-b-expected.png
deleted file mode 100644
index 8c95a97..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1-SE/text-intro-09-b-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/struct-use-05-b-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/struct-use-05-b-expected.txt
deleted file mode 100644
index e111d5c..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/struct-use-05-b-expected.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-layer at (0,0) size 800x600
-  LayoutView at (0,0) size 800x600
-layer at (0,0) size 800x600
-  LayoutSVGRoot {svg} at (0,0) size 800x600
-    LayoutSVGContainer {g} at (0,0) size 0x0
-      LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
-        LayoutSVGResourceLinearGradient {linearGradient} [id="linearGrad1"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,1)]
-          LayoutSVGGradientStop {stop} [offset=0.00] [color=#4169E1]
-          LayoutSVGGradientStop {stop} [offset=0.33] [color=#FFFFFF]
-          LayoutSVGGradientStop {stop} [offset=0.50] [color=#00008B]
-          LayoutSVGGradientStop {stop} [offset=0.66] [color=#FFFFFF]
-          LayoutSVGGradientStop {stop} [offset=1.00] [color=#4169E1]
-        LayoutSVGResourceRadialGradient {radialGradient} [id="radialGrad1"] [gradientUnits=objectBoundingBox] [center=(0.50,0.50)] [focal=(0.15,0.15)] [radius=0.50] [focalRadius=0.00]
-          LayoutSVGGradientStop {stop} [offset=0.00] [color=#4169E1]
-          LayoutSVGGradientStop {stop} [offset=0.33] [color=#FFFFFF]
-          LayoutSVGGradientStop {stop} [offset=0.50] [color=#00008B]
-          LayoutSVGGradientStop {stop} [offset=0.66] [color=#FFFFFF]
-          LayoutSVGGradientStop {stop} [offset=1.00] [color=#4169E1]
-      LayoutSVGContainer {use} at (0,0) size 0x0
-      LayoutSVGContainer {use} at (0,0) size 0x0
-      LayoutSVGContainer {use} at (0,0) size 0x0
-      LayoutSVGContainer {use} at (0,0) size 0x0
-    LayoutSVGText {text} at (42.75,13.39) size 394.47x27.59 contains 1 chunk(s)
-      LayoutSVGInlineText {#text} at (42.75,13.39) size 394.47x27.59
-        chunk 1 (middle anchor) text run 1 at (42.76,35.00) startOffset 0 endOffset 39 width 394.48: "External references and computed values"
-    LayoutSVGText {text} at (10,304) size 263.31x46.19 contains 1 chunk(s)
-      LayoutSVGInlineText {#text} at (10,304) size 263.31x46.19
-        chunk 1 text run 1 at (10.00,340.00) startOffset 0 endOffset 16 width 263.31: "$Revision: 1.6 $"
-    LayoutSVGRect {rect} at (1,1) size 478x358 [stroke={[type=SOLID] [color=#000000]}] [x=1.00] [y=1.00] [width=478.00] [height=358.00]
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/text-fonts-01-t-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/text-fonts-01-t-expected.png
deleted file mode 100644
index 47db25f..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/W3C-SVG-1.1/text-fonts-01-t-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/text/text-selection-fonts-01-t-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/svg/text/text-selection-fonts-01-t-expected.png
deleted file mode 100644
index 77e4510..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/svg/text/text-selection-fonts-01-t-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug194024-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug194024-expected.png
deleted file mode 100644
index fb45699..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug194024-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug28928-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug28928-expected.png
deleted file mode 100644
index 9be97bc9..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/bugs/bug28928-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/other/move_row-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/other/move_row-expected.png
deleted file mode 100644
index af44f4b..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/tables/mozilla/other/move_row-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
deleted file mode 100644
index 5d54f98..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-filter-all-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
index 2d26010..40840209 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/prefer_compositing_to_lcd_text/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
Binary files differ
diff --git a/third_party/WebKit/Source/core/css/parser/CSSSelectorParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSSelectorParser.cpp
index 893cce3..609f99cd 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSSelectorParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSSelectorParser.cpp
@@ -945,6 +945,15 @@
           DCHECK(RuntimeEnabledFeatures::CSSMatchesEnabled());
           feature = WebFeature::kCSSSelectorPseudoMatches;
           break;
+        case CSSSelector::kPseudoFocusVisible:
+          DCHECK(RuntimeEnabledFeatures::CSSFocusVisibleEnabled());
+          if (context_->Mode() != kUASheetMode)
+            feature = WebFeature::kCSSSelectorPseudoFocusVisible;
+          break;
+        case CSSSelector::kPseudoFocus:
+          if (context_->Mode() != kUASheetMode)
+            feature = WebFeature::kCSSSelectorPseudoFocus;
+          break;
         case CSSSelector::kPseudoAnyLink:
           feature = WebFeature::kCSSSelectorPseudoAnyLink;
           break;
diff --git a/third_party/WebKit/Source/core/dom/ExecutionContext.h b/third_party/WebKit/Source/core/dom/ExecutionContext.h
index 236d525d..edf072fa 100644
--- a/third_party/WebKit/Source/core/dom/ExecutionContext.h
+++ b/third_party/WebKit/Source/core/dom/ExecutionContext.h
@@ -176,7 +176,7 @@
   bool IsWindowInteractionAllowed() const;
 
   // Decides whether this context is privileged, as described in
-  // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-privileged.
+  // https://w3c.github.io/webappsec-secure-contexts/#is-settings-object-contextually-secure.
   virtual bool IsSecureContext(String& error_message) const = 0;
   virtual bool IsSecureContext() const;
 
diff --git a/third_party/WebKit/Source/core/exported/WebViewImpl.cpp b/third_party/WebKit/Source/core/exported/WebViewImpl.cpp
index 494a9bc..d52b3a9 100644
--- a/third_party/WebKit/Source/core/exported/WebViewImpl.cpp
+++ b/third_party/WebKit/Source/core/exported/WebViewImpl.cpp
@@ -3855,4 +3855,8 @@
   return ime_accept_events_ ? FocusedLocalFrameInWidget() : nullptr;
 }
 
+void WebViewImpl::FreezePage() {
+  Scheduler()->SetPageFrozen(true);
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/exported/WebViewImpl.h b/third_party/WebKit/Source/core/exported/WebViewImpl.h
index 4e820e77..b27c3f1 100644
--- a/third_party/WebKit/Source/core/exported/WebViewImpl.h
+++ b/third_party/WebKit/Source/core/exported/WebViewImpl.h
@@ -236,6 +236,7 @@
   void SetShowFPSCounter(bool) override;
   void SetShowScrollBottleneckRects(bool) override;
   void AcceptLanguagesChanged() override;
+  void FreezePage() override;
 
   // WebScheduler::InterventionReporter implementation:
   void ReportIntervention(const WebString& message) override;
diff --git a/third_party/WebKit/Source/core/frame/UseCounterTest.cpp b/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
index 22029bd..9d619f2 100644
--- a/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
+++ b/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
@@ -249,47 +249,6 @@
       [&](LocalFrame* frame) { use_counter.DidCommitLoad(frame); }, kSvgUrl);
 }
 
-TEST_F(UseCounterTest, CSSSelectorPseudoAnyLink) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kCSSSelectorPseudoAnyLink;
-  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
-  document.documentElement()->SetInnerHTMLFromString(
-      "<style>:any-link { color: red; }</style>");
-  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
-}
-
-TEST_F(UseCounterTest, CSSSelectorPseudoWebkitAnyLink) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kCSSSelectorPseudoWebkitAnyLink;
-  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
-  document.documentElement()->SetInnerHTMLFromString(
-      "<style>:-webkit-any-link { color: red; }</style>");
-  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
-}
-
-TEST_F(UseCounterTest, CSSTypedOMStylePropertyMap) {
-  UseCounter use_counter;
-  WebFeature feature = WebFeature::kCSSTypedOMStylePropertyMap;
-  EXPECT_FALSE(use_counter.IsCounted(GetDocument(), feature));
-  use_counter.Count(GetDocument(), feature);
-  EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature));
-}
-
-TEST_F(UseCounterTest, CSSSelectorPseudoMatches) {
-  std::unique_ptr<DummyPageHolder> dummy_page_holder =
-      DummyPageHolder::Create(IntSize(800, 600));
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kCSSSelectorPseudoMatches;
-  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
-  document.documentElement()->SetInnerHTMLFromString(
-      "<style>.a+:matches(.b, .c+.d) { color: red; }</style>");
-  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
-}
-
 TEST_F(UseCounterTest, InspectorDisablesMeasurement) {
   UseCounter use_counter;
 
@@ -336,6 +295,58 @@
       UseCounter::MapCSSPropertyIdToCSSSampleIdForHistogram(property), 1);
 }
 
+/*
+ * Counter-specific tests
+ *
+ * NOTE: Most individual UseCounters don't need dedicated test cases.  They are
+ * "tested" by analyzing the data they generate including on some known pages.
+ * Feel free to add tests for counters where the triggering logic is
+ * non-trivial, but it's not required. Manual analysis is necessary to trust the
+ * data anyway, real-world pages are full of edge-cases and surprises that you
+ * won't find in unit testing anyway.
+ */
+
+TEST_F(UseCounterTest, CSSSelectorPseudoAnyLink) {
+  std::unique_ptr<DummyPageHolder> dummy_page_holder =
+      DummyPageHolder::Create(IntSize(800, 600));
+  Document& document = dummy_page_holder->GetDocument();
+  WebFeature feature = WebFeature::kCSSSelectorPseudoAnyLink;
+  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
+  document.documentElement()->SetInnerHTMLFromString(
+      "<style>:any-link { color: red; }</style>");
+  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
+}
+
+TEST_F(UseCounterTest, CSSSelectorPseudoWebkitAnyLink) {
+  std::unique_ptr<DummyPageHolder> dummy_page_holder =
+      DummyPageHolder::Create(IntSize(800, 600));
+  Document& document = dummy_page_holder->GetDocument();
+  WebFeature feature = WebFeature::kCSSSelectorPseudoWebkitAnyLink;
+  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
+  document.documentElement()->SetInnerHTMLFromString(
+      "<style>:-webkit-any-link { color: red; }</style>");
+  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
+}
+
+TEST_F(UseCounterTest, CSSTypedOMStylePropertyMap) {
+  UseCounter use_counter;
+  WebFeature feature = WebFeature::kCSSTypedOMStylePropertyMap;
+  EXPECT_FALSE(use_counter.IsCounted(GetDocument(), feature));
+  use_counter.Count(GetDocument(), feature);
+  EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature));
+}
+
+TEST_F(UseCounterTest, CSSSelectorPseudoMatches) {
+  std::unique_ptr<DummyPageHolder> dummy_page_holder =
+      DummyPageHolder::Create(IntSize(800, 600));
+  Document& document = dummy_page_holder->GetDocument();
+  WebFeature feature = WebFeature::kCSSSelectorPseudoMatches;
+  EXPECT_FALSE(UseCounter::IsCounted(document, feature));
+  document.documentElement()->SetInnerHTMLFromString(
+      "<style>.a+:matches(.b, .c+.d) { color: red; }</style>");
+  EXPECT_TRUE(UseCounter::IsCounted(document, feature));
+}
+
 TEST_F(UseCounterTest, DropMeasurementOnViewSourcePages) {
   UseCounter use_counter;
   SetIsViewSource();
diff --git a/third_party/WebKit/Source/core/frame/Window.idl b/third_party/WebKit/Source/core/frame/Window.idl
index 777ad74..d24b0f4a 100644
--- a/third_party/WebKit/Source/core/frame/Window.idl
+++ b/third_party/WebKit/Source/core/frame/Window.idl
@@ -213,7 +213,7 @@
     attribute EventHandler onwebkitanimationstart;
     attribute EventHandler onwebkittransitionend;
 
-    // https://w3c.github.io/webappsec/specs/powerfulfeatures/#monkey-patching-global-object
+    // https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
     readonly attribute boolean isSecureContext;
 
     attribute DOMMatrixConstructor WebKitCSSMatrix;
diff --git a/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp
index 7478ca9..16111e73 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorDOMSnapshotAgent.cpp
@@ -279,7 +279,8 @@
     DocumentType* doc_type = ToDocumentType(node);
     value->setPublicId(doc_type->publicId());
     value->setSystemId(doc_type->systemId());
-  } else if (node->IsInShadowTree()) {
+  }
+  if (node->IsInShadowTree()) {
     value->setShadowRootType(
         InspectorDOMAgent::GetShadowRootType(node->ContainingShadowRoot()));
   }
diff --git a/third_party/WebKit/Source/core/paint/BoxPainter.cpp b/third_party/WebKit/Source/core/paint/BoxPainter.cpp
index d52c6920..124d9c3b 100644
--- a/third_party/WebKit/Source/core/paint/BoxPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/BoxPainter.cpp
@@ -112,7 +112,8 @@
   // because we always paint using the latest data (buffered ranges, current
   // time and duration) which may be different from the cached data, and for
   // delayed-invalidation object because it may change before it's actually
-  // invalidated.
+  // invalidated. Note that we still report harmless under-invalidation of
+  // non-delayed-invalidation animated background, which should be ignored.
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
       (style.Appearance() == kMediaSliderPart ||
        layout_box_.FullPaintInvalidationReason() ==
diff --git a/third_party/WebKit/Source/core/paint/ImagePainter.cpp b/third_party/WebKit/Source/core/paint/ImagePainter.cpp
index bbd8df0..35f57f6 100644
--- a/third_party/WebKit/Source/core/paint/ImagePainter.cpp
+++ b/third_party/WebKit/Source/core/paint/ImagePainter.cpp
@@ -109,12 +109,12 @@
                                                   paint_info.phase))
     return;
 
-  // Disable cache in under-invalidation checking mode for delayed-invalidation
-  // image because it may change before it's actually invalidated.
+  // Disable cache in under-invalidation checking mode for animated image
+  // because it may change before it's actually invalidated.
   Optional<DisplayItemCacheSkipper> cache_skipper;
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
-      layout_image_.FullPaintInvalidationReason() ==
-          PaintInvalidationReason::kDelayedFull)
+      layout_image_.ImageResource() &&
+      layout_image_.ImageResource()->MaybeAnimated())
     cache_skipper.emplace(context);
 
   LayoutRect content_rect(paint_offset + layout_image_.ContentBoxOffset(),
diff --git a/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js b/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js
index 704966b..41bf1f8 100644
--- a/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js
+++ b/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js
@@ -273,7 +273,7 @@
      * @param {boolean} encoded
      * @this {Workspace.UISourceCode}
      */
-    function contentLoaded(updatedContent, encoded) {
+    async function contentLoaded(updatedContent, encoded) {
       this._checkingContent = false;
       if (updatedContent === null) {
         const workingCopy = this.workingCopy();
@@ -290,14 +290,19 @@
       }
 
       if (!this.isDirty() || this._workingCopy === updatedContent) {
-        this._contentCommitted(updatedContent, false);
+        this._contentCommitted(/** @type {string} */ (updatedContent), false);
         return;
       }
 
+      await Common.Revealer.reveal(this);
+
+      // Make sure we are in the next frame before stopping the world with confirm
+      await new Promise(resolve => setTimeout(resolve, 0));
+
       const shouldUpdate =
           window.confirm(Common.UIString('This file was changed externally. Would you like to reload it?'));
       if (shouldUpdate)
-        this._contentCommitted(updatedContent, false);
+        this._contentCommitted(/** @type {string} */ (updatedContent), false);
       else
         this._lastAcceptedContent = updatedContent;
     }
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObject.cpp b/third_party/WebKit/Source/modules/accessibility/AXObject.cpp
index 3be6906c..dea30a43 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObject.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXObject.cpp
@@ -1819,6 +1819,10 @@
   return const_cast<AXObject*>(this);
 }
 
+int AXObject::ChildCount() const {
+  return static_cast<int>(Children().size());
+}
+
 const AXObject::AXObjectVector& AXObject::Children() const {
   return const_cast<AXObject*>(this)->Children();
 }
@@ -1829,6 +1833,36 @@
   return children_;
 }
 
+AXObject* AXObject::FirstChild() const {
+  return ChildCount() ? *Children().begin() : nullptr;
+}
+
+AXObject* AXObject::LastChild() const {
+  return ChildCount() ? *(Children().end() - 1) : nullptr;
+}
+
+AXObject* AXObject::DeepestFirstChild() const {
+  if (!ChildCount())
+    return nullptr;
+
+  AXObject* deepest_child = FirstChild();
+  while (deepest_child->ChildCount())
+    deepest_child = deepest_child->FirstChild();
+
+  return deepest_child;
+}
+
+AXObject* AXObject::DeepestLastChild() const {
+  if (!ChildCount())
+    return nullptr;
+
+  AXObject* deepest_child = LastChild();
+  while (deepest_child->ChildCount())
+    deepest_child = deepest_child->LastChild();
+
+  return deepest_child;
+}
+
 bool AXObject::IsAncestorOf(const AXObject& descendant) const {
   return descendant.IsDescendantOf(*this);
 }
@@ -1840,6 +1874,59 @@
   return !!parent;
 }
 
+AXObject* AXObject::NextSibling() const {
+  AXObject* parent = ParentObjectUnignored();
+  if (!parent)
+    return nullptr;
+
+  if (IndexInParent() < parent->ChildCount() - 1)
+    return *(parent->Children().begin() + IndexInParent() + 1);
+
+  return nullptr;
+}
+
+AXObject* AXObject::PreviousSibling() const {
+  AXObject* parent = ParentObjectUnignored();
+  if (!parent)
+    return nullptr;
+
+  if (IndexInParent() > 0)
+    return *(parent->Children().begin() + IndexInParent() - 1);
+
+  return nullptr;
+}
+
+AXObject* AXObject::NextInTreeObject(bool can_wrap_to_first_element) const {
+  if (ChildCount())
+    return FirstChild();
+
+  if (NextSibling())
+    return NextSibling();
+  AXObject* current_object = const_cast<AXObject*>(this);
+  while (current_object->ParentObjectUnignored()) {
+    current_object = current_object->ParentObjectUnignored();
+    AXObject* sibling = current_object->NextSibling();
+    if (sibling)
+      return sibling;
+  }
+
+  return can_wrap_to_first_element ? current_object : nullptr;
+}
+
+AXObject* AXObject::PreviousInTreeObject(bool can_wrap_to_last_element) const {
+  AXObject* sibling = PreviousSibling();
+  if (!sibling) {
+    if (ParentObjectUnignored())
+      return ParentObjectUnignored();
+    return can_wrap_to_last_element ? DeepestLastChild() : nullptr;
+  }
+
+  if (sibling->ChildCount())
+    return sibling->DeepestLastChild();
+
+  return sibling;
+}
+
 AXObject* AXObject::ParentObject() const {
   if (IsDetached())
     return nullptr;
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObject.h b/third_party/WebKit/Source/modules/accessibility/AXObject.h
index 1fdd009..8dc2266 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObject.h
+++ b/third_party/WebKit/Source/modules/accessibility/AXObject.h
@@ -635,10 +635,21 @@
 
   // High-level accessibility tree access. Other modules should only use these
   // functions.
+  int ChildCount() const;
   const AXObjectVector& Children() const;
   const AXObjectVector& Children();
+  AXObject* FirstChild() const;
+  AXObject* LastChild() const;
+  AXObject* DeepestFirstChild() const;
+  AXObject* DeepestLastChild() const;
   bool IsAncestorOf(const AXObject&) const;
   bool IsDescendantOf(const AXObject&) const;
+  AXObject* NextSibling() const;
+  AXObject* PreviousSibling() const;
+  // Next object in tree using depth-first pre-order traversal.
+  AXObject* NextInTreeObject(bool can_wrap_to_first_element = false) const;
+  // Previous object in tree using depth-first pre-order traversal.
+  AXObject* PreviousInTreeObject(bool can_wrap_to_last_element = false) const;
   AXObject* ParentObject() const;
   AXObject* ParentObjectIfExists() const;
   virtual AXObject* ComputeParent() const = 0;
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp b/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
index debe051..c4701f5 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
@@ -36,4 +36,46 @@
   EXPECT_FALSE(button->IsAncestorOf(*root));
 }
 
+TEST_F(AccessibilityTest, SimpleTreeNavigation) {
+  SetBodyInnerHTML(R"HTML(<input id='input' type='text' value='value'>"
+                   R"<p id='paragraph'>hello<br id='br'>there</p>"
+                   R"<button id='button'>button</button>)HTML");
+
+  const AXObject* root = GetAXRootObject();
+  ASSERT_NE(nullptr, root);
+  const AXObject* input = GetAXObjectByElementId("input");
+  ASSERT_NE(nullptr, input);
+  const AXObject* paragraph = GetAXObjectByElementId("paragraph");
+  ASSERT_NE(nullptr, paragraph);
+  const AXObject* br = GetAXObjectByElementId("br");
+  ASSERT_NE(nullptr, br);
+  const AXObject* button = GetAXObjectByElementId("button");
+  ASSERT_NE(nullptr, button);
+
+  EXPECT_EQ(input, root->FirstChild());
+  EXPECT_EQ(button, root->LastChild());
+  EXPECT_EQ(button, root->DeepestLastChild());
+
+  ASSERT_NE(nullptr, paragraph->FirstChild());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole,
+            paragraph->FirstChild()->RoleValue());
+  ASSERT_NE(nullptr, paragraph->LastChild());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole,
+            paragraph->LastChild()->RoleValue());
+  ASSERT_NE(nullptr, paragraph->DeepestFirstChild());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole,
+            paragraph->DeepestFirstChild()->RoleValue());
+  ASSERT_NE(nullptr, paragraph->DeepestLastChild());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole,
+            paragraph->DeepestLastChild()->RoleValue());
+
+  // There is a static text element in between the input and the paragraph.
+  EXPECT_EQ(paragraph->PreviousSibling(), input->NextSibling());
+  ASSERT_NE(nullptr, br->NextSibling());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole, br->NextSibling()->RoleValue());
+  ASSERT_NE(nullptr, br->PreviousSibling());
+  EXPECT_EQ(AccessibilityRole::kStaticTextRole,
+            br->PreviousSibling()->RoleValue());
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerContainer.cpp b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerContainer.cpp
index 8f063805b..4e9435c5 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerContainer.cpp
+++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerContainer.cpp
@@ -183,7 +183,7 @@
       execution_context->GetSecurityOrigin();
   String error_message;
   // Restrict to secure origins:
-  // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-privileged
+  // https://w3c.github.io/webappsec-secure-contexts/#is-settings-object-contextually-secure
   if (!execution_context->IsSecureContext(error_message)) {
     callbacks->OnError(WebServiceWorkerError(
         mojom::blink::ServiceWorkerErrorType::kSecurity, error_message));
diff --git a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
index 2377614..ba3a62e8 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
+++ b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.cpp
@@ -4268,6 +4268,13 @@
     return;
   }
 
+  if (transform_feedback_binding_->active() &&
+      !transform_feedback_binding_->paused()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "bindTransformFeedback",
+                      "transform feedback is active and not paused");
+    return;
+  }
+
   WebGLTransformFeedback* feedback_to_be_bound;
   if (feedback) {
     feedback_to_be_bound = feedback;
@@ -4287,22 +4294,50 @@
   if (!ValidateTransformFeedbackPrimitiveMode("beginTransformFeedback",
                                               primitive_mode))
     return;
+  if (!current_program_) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "beginTransformFeedback",
+                      "no program object is active");
+    return;
+  }
+  if (transform_feedback_binding_->active()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "beginTransformFeedback",
+                      "transform feedback is already active");
+    return;
+  }
+  int required_buffer_count =
+      current_program_->GetRequiredTransformFeedbackBufferCount(this);
+  if (required_buffer_count == 0) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "beginTransformFeedback",
+                      "current active program does not specify any transform "
+                      "feedback varyings to record");
+    return;
+  }
+  if (!transform_feedback_binding_->HasEnoughBuffers(required_buffer_count)) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "beginTransformFeedback",
+                      "not enough transform feedback buffers bound");
+    return;
+  }
 
   ContextGL()->BeginTransformFeedback(primitive_mode);
-
-  if (current_program_)
-    current_program_->IncreaseActiveTransformFeedbackCount();
-
-  if (transform_feedback_binding_)
-    transform_feedback_binding_->SetProgram(current_program_);
+  current_program_->IncreaseActiveTransformFeedbackCount();
+  transform_feedback_binding_->SetProgram(current_program_);
+  transform_feedback_binding_->SetActive(true);
+  transform_feedback_binding_->SetPaused(false);
 }
 
 void WebGL2RenderingContextBase::endTransformFeedback() {
   if (isContextLost())
     return;
+  if (!transform_feedback_binding_->active()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "endTransformFeedback",
+                      "transform feedback is not active");
+    return;
+  }
 
   ContextGL()->EndTransformFeedback();
 
+  transform_feedback_binding_->SetPaused(false);
+  transform_feedback_binding_->SetActive(false);
   if (current_program_)
     current_program_->DecreaseActiveTransformFeedbackCount();
 }
@@ -4340,6 +4375,9 @@
     varying_strings.push_back(keep_alive.back().data());
   }
 
+  program->SetRequiredTransformFeedbackBufferCount(
+      buffer_mode == GL_INTERLEAVED_ATTRIBS ? 1 : varyings.size());
+
   ContextGL()->TransformFeedbackVaryings(ObjectOrZero(program), varyings.size(),
                                          varying_strings.data(), buffer_mode);
 }
@@ -4391,6 +4429,18 @@
   if (isContextLost())
     return;
 
+  if (!transform_feedback_binding_->active()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "pauseTransformFeedback",
+                      "transform feedback is not active");
+    return;
+  }
+  if (transform_feedback_binding_->paused()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "pauseTransformFeedback",
+                      "transform feedback is already paused");
+    return;
+  }
+
+  transform_feedback_binding_->SetPaused(true);
   ContextGL()->PauseTransformFeedback();
 }
 
@@ -4398,13 +4448,21 @@
   if (isContextLost())
     return;
 
-  if (transform_feedback_binding_ &&
-      transform_feedback_binding_->GetProgram() != current_program_) {
+  if (!transform_feedback_binding_->ValidateProgramForResume(
+          current_program_)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "resumeTransformFeedback",
-                      "the program object is not active");
+                      "the current program is not the same as when "
+                      "beginTransformFeedback was called");
+    return;
+  }
+  if (!transform_feedback_binding_->active() ||
+      !transform_feedback_binding_->paused()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "resumeTransformFeedback",
+                      "transform feedback is not active or not paused");
     return;
   }
 
+  transform_feedback_binding_->SetPaused(false);
   ContextGL()->ResumeTransformFeedback();
 }
 
@@ -4436,6 +4494,12 @@
                       "attempt to bind a deleted buffer");
     return;
   }
+  if (target == GL_TRANSFORM_FEEDBACK_BUFFER &&
+      transform_feedback_binding_->active()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
+                      "transform feedback is active");
+    return;
+  }
   if (!ValidateAndUpdateBufferBindBaseTarget("bindBufferBase", target, index,
                                              buffer))
     return;
@@ -4458,6 +4522,12 @@
                       "attempt to bind a deleted buffer");
     return;
   }
+  if (target == GL_TRANSFORM_FEEDBACK_BUFFER &&
+      transform_feedback_binding_->active()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
+                      "transform feedback is active");
+    return;
+  }
   if (!ValidateValueFitNonNegInt32("bindBufferRange", "offset", offset) ||
       !ValidateValueFitNonNegInt32("bindBufferRange", "size", size)) {
     return;
@@ -5039,32 +5109,6 @@
   }
 }
 
-bool WebGL2RenderingContextBase::IsBufferBoundToTransformFeedback(
-    WebGLBuffer* buffer) {
-  DCHECK(buffer);
-  return transform_feedback_binding_->IsBufferBoundToTransformFeedback(buffer);
-}
-
-bool WebGL2RenderingContextBase::IsBufferBoundToNonTransformFeedback(
-    WebGLBuffer* buffer) {
-  DCHECK(buffer);
-
-  if (bound_array_buffer_ == buffer ||
-      bound_vertex_array_object_->BoundElementArrayBuffer() == buffer ||
-      bound_copy_read_buffer_ == buffer || bound_copy_write_buffer_ == buffer ||
-      bound_pixel_pack_buffer_ == buffer ||
-      bound_pixel_unpack_buffer_ == buffer || bound_uniform_buffer_ == buffer) {
-    return true;
-  }
-
-  for (size_t i = 0; i <= max_bound_uniform_buffer_index_; ++i) {
-    if (bound_indexed_uniform_buffers_[i] == buffer)
-      return true;
-  }
-
-  return false;
-}
-
 bool WebGL2RenderingContextBase::ValidateBufferTargetCompatibility(
     const char* function_name,
     GLenum target,
@@ -5106,20 +5150,6 @@
       break;
   }
 
-  if (target == GL_TRANSFORM_FEEDBACK_BUFFER) {
-    if (IsBufferBoundToNonTransformFeedback(buffer)) {
-      SynthesizeGLError(GL_INVALID_OPERATION, function_name,
-                        "a buffer bound to TRANSFORM_FEEDBACK_BUFFER can not "
-                        "be bound to any other targets");
-      return false;
-    }
-  } else if (IsBufferBoundToTransformFeedback(buffer)) {
-    SynthesizeGLError(GL_INVALID_OPERATION, function_name,
-                      "a buffer bound to TRANSFORM_FEEDBACK_BUFFER can not be "
-                      "bound to any other targets");
-    return false;
-  }
-
   return true;
 }
 
@@ -5814,6 +5844,16 @@
   bindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer_binding_.Get());
 }
 
+void WebGL2RenderingContextBase::useProgram(WebGLProgram* program) {
+  if (transform_feedback_binding_->active() &&
+      !transform_feedback_binding_->paused()) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "useProgram",
+                      "transform feedback is active and not paused");
+    return;
+  }
+  WebGLRenderingContextBase::useProgram(program);
+}
+
 GLint WebGL2RenderingContextBase::GetMaxTransformFeedbackSeparateAttribs()
     const {
   return max_transform_feedback_separate_attribs_;
diff --git a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.h b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.h
index a5c6b42..ccbdfb3 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.h
+++ b/third_party/WebKit/Source/modules/webgl/WebGL2RenderingContextBase.h
@@ -971,6 +971,7 @@
                   GLenum type,
                   MaybeShared<DOMArrayBufferView> pixels) override;
   void RestoreCurrentFramebuffer() override;
+  void useProgram(WebGLProgram*) override;
 
   /* Helpers */
   GLint GetMaxTransformFeedbackSeparateAttribs() const;
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLProgram.cpp b/third_party/WebKit/Source/modules/webgl/WebGLProgram.cpp
index 92c4042..b1e572d 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLProgram.cpp
+++ b/third_party/WebKit/Source/modules/webgl/WebGLProgram.cpp
@@ -40,7 +40,9 @@
       link_status_(false),
       link_count_(0),
       active_transform_feedback_count_(0),
-      info_valid_(true) {
+      info_valid_(true),
+      required_transform_feedback_buffer_count_(0),
+      required_transform_feedback_buffer_count_after_next_link_(0) {
   SetObject(ctx->ContextGL()->CreateProgram());
 }
 
@@ -138,6 +140,10 @@
   gpu::gles2::GLES2Interface* gl = context->ContextGL();
   link_status_ = 0;
   gl->GetProgramiv(object_, GL_LINK_STATUS, &link_status_);
+  if (link_status_ == GL_TRUE) {
+    required_transform_feedback_buffer_count_ =
+        required_transform_feedback_buffer_count_after_next_link_;
+  }
   info_valid_ = true;
 }
 
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLProgram.h b/third_party/WebKit/Source/modules/webgl/WebGLProgram.h
index 4edd3193..ed146a9 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLProgram.h
+++ b/third_party/WebKit/Source/modules/webgl/WebGLProgram.h
@@ -57,6 +57,15 @@
   void IncreaseActiveTransformFeedbackCount();
   void DecreaseActiveTransformFeedbackCount();
 
+  void SetRequiredTransformFeedbackBufferCount(int count) {
+    required_transform_feedback_buffer_count_after_next_link_ = count;
+  }
+  int GetRequiredTransformFeedbackBufferCount(
+      WebGLRenderingContextBase* context) {
+    CacheInfoIfNeeded(context);
+    return required_transform_feedback_buffer_count_;
+  }
+
   WebGLShader* GetAttachedShader(GLenum);
   bool AttachShader(WebGLShader*);
   bool DetachShader(WebGLShader*);
@@ -88,6 +97,10 @@
   TraceWrapperMember<WebGLShader> fragment_shader_;
 
   bool info_valid_;
+
+  // The number of transform feedback buffers this program will write to.
+  int required_transform_feedback_buffer_count_;
+  int required_transform_feedback_buffer_count_after_next_link_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
index 5af4eb1..ff5878f 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
+++ b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
@@ -496,7 +496,7 @@
                         GLboolean transpose,
                         Vector<GLfloat>& value);
 
-  void useProgram(WebGLProgram*);
+  virtual void useProgram(WebGLProgram*);
   void validateProgram(WebGLProgram*);
 
   void vertexAttrib1f(GLuint index, GLfloat x);
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.cpp b/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.cpp
index 7e7c6c1..d2b1c6dc 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.cpp
+++ b/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.cpp
@@ -21,7 +21,9 @@
       object_(0),
       type_(type),
       target_(0),
-      program_(nullptr) {
+      program_(nullptr),
+      active_(false),
+      paused_(false) {
   GLint max_attribs = ctx->GetMaxTransformFeedbackSeparateAttribs();
   DCHECK_GE(max_attribs, 0);
   bound_indexed_transform_feedback_buffers_.resize(max_attribs);
@@ -80,6 +82,13 @@
 
 void WebGLTransformFeedback::SetProgram(WebGLProgram* program) {
   program_ = program;
+  program_link_count_ = program->LinkCount();
+}
+
+bool WebGLTransformFeedback::ValidateProgramForResume(
+    WebGLProgram* program) const {
+  return program && program_ == program &&
+         program->LinkCount() == program_link_count_;
 }
 
 void WebGLTransformFeedback::SetBoundTransformFeedbackBuffer(
@@ -121,6 +130,16 @@
   return true;
 }
 
+bool WebGLTransformFeedback::HasEnoughBuffers(GLuint num_required) const {
+  if (num_required > bound_indexed_transform_feedback_buffers_.size())
+    return false;
+  for (GLuint i = 0; i < num_required; i++) {
+    if (!bound_indexed_transform_feedback_buffers_[i])
+      return false;
+  }
+  return true;
+}
+
 bool WebGLTransformFeedback::IsBufferBoundToTransformFeedback(
     WebGLBuffer* buffer) {
   if (bound_transform_feedback_buffer_ == buffer)
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.h b/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.h
index c751bbf..0272b48 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.h
+++ b/third_party/WebKit/Source/modules/webgl/WebGLTransformFeedback.h
@@ -49,6 +49,7 @@
   bool SetBoundIndexedTransformFeedbackBuffer(GLuint index, WebGLBuffer*);
   bool GetBoundIndexedTransformFeedbackBuffer(GLuint index,
                                               WebGLBuffer** outBuffer) const;
+  bool HasEnoughBuffers(GLuint num_required) const;
 
   bool IsBufferBoundToTransformFeedback(WebGLBuffer*);
 
@@ -57,6 +58,20 @@
   virtual void Trace(blink::Visitor*);
   virtual void TraceWrappers(const ScriptWrappableVisitor*) const;
 
+  bool active() const { return active_; }
+  bool paused() const { return paused_; }
+
+  void SetActive(bool active) {
+    active_ = active;
+    DCHECK(active_ || !paused_);
+  }
+  void SetPaused(bool paused) {
+    paused_ = paused;
+    DCHECK(active_ || !paused_);
+  }
+
+  bool ValidateProgramForResume(WebGLProgram*) const;
+
  protected:
   explicit WebGLTransformFeedback(WebGL2RenderingContextBase*, TFType);
 
@@ -75,6 +90,9 @@
       bound_indexed_transform_feedback_buffers_;
 
   Member<WebGLProgram> program_;
+  unsigned program_link_count_;
+  bool active_;
+  bool paused_;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn
index 4f256d0d..e6202e9 100644
--- a/third_party/WebKit/Source/platform/BUILD.gn
+++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -1053,6 +1053,8 @@
     "graphics/VideoFrameSubmitter.h",
     "graphics/WebGraphicsContext3DProviderWrapper.cpp",
     "graphics/WebGraphicsContext3DProviderWrapper.h",
+    "graphics/compositing/ChunkToLayerMapper.cpp",
+    "graphics/compositing/ChunkToLayerMapper.h",
     "graphics/compositing/CompositedLayerRasterInvalidator.cpp",
     "graphics/compositing/CompositedLayerRasterInvalidator.h",
     "graphics/compositing/ContentLayerClientImpl.cpp",
@@ -1856,6 +1858,7 @@
     "graphics/PlaceholderImageTest.cpp",
     "graphics/StaticBitmapImageTest.cpp",
     "graphics/VideoFrameSubmitterTest.cpp",
+    "graphics/compositing/ChunkToLayerMapperTest.cpp",
     "graphics/compositing/CompositedLayerRasterInvalidatorTest.cpp",
     "graphics/compositing/PaintArtifactCompositorTest.cpp",
     "graphics/compositing/PaintChunksToCcLayerTest.cpp",
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.cpp b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.cpp
new file mode 100644
index 0000000..22ea570
--- /dev/null
+++ b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.cpp
@@ -0,0 +1,107 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "platform/graphics/compositing/ChunkToLayerMapper.h"
+
+#include "platform/graphics/paint/GeometryMapper.h"
+#include "platform/graphics/paint/PaintChunk.h"
+
+namespace blink {
+
+void ChunkToLayerMapper::SwitchToChunk(const PaintChunk& chunk) {
+  outset_for_raster_effects_ = chunk.outset_for_raster_effects;
+
+  const auto& new_chunk_state =
+      chunk.properties.property_tree_state.GetPropertyTreeState();
+  if (new_chunk_state == chunk_state_)
+    return;
+
+  if (new_chunk_state == layer_state_) {
+    has_filter_that_moves_pixels_ = false;
+    transform_ = TransformationMatrix().Translate(-layer_offset_.x(),
+                                                  -layer_offset_.y());
+    clip_rect_ = FloatClipRect();
+    chunk_state_ = new_chunk_state;
+    return;
+  }
+
+  if (new_chunk_state.Transform() != chunk_state_.Transform()) {
+    transform_ = GeometryMapper::SourceToDestinationProjection(
+        new_chunk_state.Transform(), layer_state_.Transform());
+    transform_.PostTranslate(-layer_offset_.x(), -layer_offset_.y());
+  }
+
+  bool new_has_filter_that_moves_pixels = has_filter_that_moves_pixels_;
+  if (new_chunk_state.Effect() != chunk_state_.Effect()) {
+    new_has_filter_that_moves_pixels = false;
+    for (const auto* effect = new_chunk_state.Effect();
+         effect && effect != layer_state_.Effect(); effect = effect->Parent()) {
+      if (effect->HasFilterThatMovesPixels()) {
+        new_has_filter_that_moves_pixels = true;
+        break;
+      }
+    }
+  }
+
+  bool needs_clip_recalculation =
+      new_has_filter_that_moves_pixels != has_filter_that_moves_pixels_ ||
+      new_chunk_state.Clip() != chunk_state_.Clip();
+  if (needs_clip_recalculation) {
+    clip_rect_ =
+        GeometryMapper::LocalToAncestorClipRect(new_chunk_state, layer_state_);
+    if (!clip_rect_.IsInfinite())
+      clip_rect_.MoveBy(FloatPoint(-layer_offset_.x(), -layer_offset_.y()));
+  }
+
+  chunk_state_ = new_chunk_state;
+  has_filter_that_moves_pixels_ = new_has_filter_that_moves_pixels;
+}
+
+IntRect ChunkToLayerMapper::MapVisualRect(const FloatRect& rect) const {
+  if (rect.IsEmpty())
+    return IntRect();
+
+  if (UNLIKELY(has_filter_that_moves_pixels_))
+    return MapUsingGeometryMapper(rect);
+
+  FloatRect mapped_rect = transform_.MapRect(rect);
+  if (!mapped_rect.IsEmpty() && !clip_rect_.IsInfinite())
+    mapped_rect.Intersect(clip_rect_.Rect());
+
+  if (mapped_rect.IsEmpty()) {
+    DCHECK_EQ(IntRect(), MapUsingGeometryMapper(rect));
+    return IntRect();
+  }
+
+  mapped_rect.Inflate(outset_for_raster_effects_);
+  auto result = EnclosingIntRect(mapped_rect);
+#if DCHECK_IS_ON()
+  auto slow_result = MapUsingGeometryMapper(rect);
+  if (result != slow_result) {
+    // Not a DCHECK because this may result from a floating point error.
+    LOG(WARNING) << "ChunkToLayerMapper::MapVisualRect: Different results from"
+                 << "fast path (" << result << ") and slow path ("
+                 << slow_result << ")";
+  }
+#endif
+  return result;
+}
+
+// This is called when the fast path doesn't apply if there is any filter that
+// moves pixels. GeometryMapper::LocalToAncestorVisualRect() will apply the
+// visual effects of the filters, though slowly.
+IntRect ChunkToLayerMapper::MapUsingGeometryMapper(
+    const FloatRect& rect) const {
+  FloatClipRect visual_rect(rect);
+  GeometryMapper::LocalToAncestorVisualRect(chunk_state_, layer_state_,
+                                            visual_rect);
+  if (visual_rect.Rect().IsEmpty())
+    return IntRect();
+
+  visual_rect.Rect().Move(-layer_offset_.x(), -layer_offset_.y());
+  visual_rect.Rect().Inflate(outset_for_raster_effects_);
+  return EnclosingIntRect(visual_rect.Rect());
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.h b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.h
new file mode 100644
index 0000000..5ac3d0f2
--- /dev/null
+++ b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapper.h
@@ -0,0 +1,64 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ChunkToLayerMapper_h
+#define ChunkToLayerMapper_h
+
+#include "platform/graphics/paint/FloatClipRect.h"
+#include "platform/graphics/paint/PropertyTreeState.h"
+
+namespace blink {
+
+struct PaintChunk;
+
+// Maps geometry from PaintChunks to the containing composited layer.
+// It provides higher performance than GeometryMapper by reusing computed
+// transforms and clips for unchanged states within or across paint chunks.
+class PLATFORM_EXPORT ChunkToLayerMapper {
+  DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
+
+ public:
+  ChunkToLayerMapper(const PropertyTreeState& layer_state,
+                     const gfx::Vector2dF& layer_offset)
+      : layer_state_(layer_state),
+        layer_offset_(layer_offset),
+        chunk_state_(nullptr, nullptr, nullptr) {}
+
+  // This class can map from multiple chunks. Before mapping from a chunk, this
+  // method must be called to prepare for the chunk.
+  void SwitchToChunk(const PaintChunk&);
+
+  // Maps a visual rectangle in the current chunk space into the layer space.
+  IntRect MapVisualRect(const FloatRect&) const;
+
+  // Returns the combined transform from the current chunk to the layer.
+  const TransformationMatrix& Transform() const { return transform_; }
+
+  // Returns the combined clip from the current chunk to the layer if it can
+  // be calculated (there is no filter that moves pixels), or infinite loose
+  // clip rect otherwise.
+  const FloatClipRect& ClipRect() const { return clip_rect_; }
+
+ private:
+  friend class ChunkToLayerMapperTest;
+
+  IntRect MapUsingGeometryMapper(const FloatRect&) const;
+
+  const PropertyTreeState layer_state_;
+  const gfx::Vector2dF layer_offset_;
+
+  // The following fields are chunk-specific which are updated in
+  // SwitchToChunk().
+  PropertyTreeState chunk_state_;
+  float outset_for_raster_effects_ = 0.f;
+  TransformationMatrix transform_;
+  FloatClipRect clip_rect_;
+  // True if there is any pixel-moving filter between chunk state and layer
+  // state, and we will call GeometryMapper for each mapping.
+  bool has_filter_that_moves_pixels_ = false;
+};
+
+}  // namespace blink
+
+#endif  // PaintArtifactCompositor_h
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapperTest.cpp b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapperTest.cpp
new file mode 100644
index 0000000..6cb686a9
--- /dev/null
+++ b/third_party/WebKit/Source/platform/graphics/compositing/ChunkToLayerMapperTest.cpp
@@ -0,0 +1,234 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "platform/graphics/compositing/ChunkToLayerMapper.h"
+
+#include "platform/graphics/paint/DisplayItem.h"
+#include "platform/graphics/paint/PaintChunk.h"
+#include "platform/testing/FakeDisplayItemClient.h"
+#include "platform/wtf/Optional.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+class ChunkToLayerMapperTest : public ::testing::Test {
+ protected:
+  static PaintChunk Chunk(const PropertyTreeState& state) {
+    DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client, ());
+    DEFINE_STATIC_LOCAL(
+        Optional<PaintChunk::Id>, id,
+        (PaintChunk::Id(fake_client, DisplayItem::kDrawingFirst)));
+    PaintChunk chunk(0, 0, *id, PaintChunkProperties(state));
+    return chunk;
+  }
+
+  // A state containing arbitrary values which should not affect test results
+  // if the state is used as a layer state.
+  PropertyTreeState LayerState() {
+    DEFINE_STATIC_REF(
+        TransformPaintPropertyNode, transform,
+        TransformPaintPropertyNode::Create(
+            TransformPaintPropertyNode::Root(),
+            TransformationMatrix().Translate(123, 456), FloatPoint3D(1, 2, 3)));
+    DEFINE_STATIC_REF(
+        ClipPaintPropertyNode, clip,
+        ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(), transform,
+                                      FloatRoundedRect(12, 34, 56, 78)));
+    DEFINE_STATIC_REF(
+        EffectPaintPropertyNode, effect,
+        EffectPaintPropertyNode::Create(
+            EffectPaintPropertyNode::Root(), transform, clip,
+            kColorFilterLuminanceToAlpha, CompositorFilterOperations(), 0.789f,
+            SkBlendMode::kSrcIn));
+    return PropertyTreeState(transform, clip, effect);
+  }
+
+  bool HasFilterThatMovesPixels(const ChunkToLayerMapper& mapper) {
+    return mapper.has_filter_that_moves_pixels_;
+  }
+};
+
+TEST_F(ChunkToLayerMapperTest, OneChunkUsingLayerState) {
+  ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
+  auto chunk = Chunk(LayerState());
+  mapper.SwitchToChunk(chunk);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30.2f, 30.7f, 87.3f, 98.1f)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+}
+
+TEST_F(ChunkToLayerMapperTest, TwoChunkUsingLayerState) {
+  ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
+  auto chunk1 = Chunk(LayerState());
+  auto chunk2 = Chunk(LayerState());
+
+  mapper.SwitchToChunk(chunk1);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30.2f, 30.7f, 87.3f, 98.1f)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+
+  mapper.SwitchToChunk(chunk2);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(20, 10, 88, 99),
+            mapper.MapVisualRect(FloatRect(30.2f, 30.7f, 87.3f, 98.1f)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+}
+
+TEST_F(ChunkToLayerMapperTest, TwoChunkSameState) {
+  ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
+  auto transform = TransformPaintPropertyNode::Create(
+      LayerState().Transform(), TransformationMatrix().Scale(2),
+      FloatPoint3D());
+  auto clip = ClipPaintPropertyNode::Create(LayerState().Clip(),
+                                            LayerState().Transform(),
+                                            FloatRoundedRect(10, 10, 100, 100));
+  auto effect = LayerState().Effect();
+  auto chunk1 = Chunk(PropertyTreeState(transform.get(), clip.get(), effect));
+  auto chunk2 = Chunk(PropertyTreeState(transform.get(), clip.get(), effect));
+
+  mapper.SwitchToChunk(chunk1);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20).Scale(2),
+            mapper.Transform());
+  EXPECT_EQ(FloatRect(0, -10, 100, 100), mapper.ClipRect().Rect());
+  EXPECT_TRUE(mapper.ClipRect().IsTight());
+  EXPECT_EQ(IntRect(50, 40, 50, 50),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+
+  mapper.SwitchToChunk(chunk2);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20).Scale(2),
+            mapper.Transform());
+  EXPECT_EQ(FloatRect(0, -10, 100, 100), mapper.ClipRect().Rect());
+  EXPECT_TRUE(mapper.ClipRect().IsTight());
+  EXPECT_EQ(IntRect(50, 40, 50, 50),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+}
+
+TEST_F(ChunkToLayerMapperTest, TwoChunkDifferentState) {
+  ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
+  auto transform1 = TransformPaintPropertyNode::Create(
+      LayerState().Transform(), TransformationMatrix().Scale(2),
+      FloatPoint3D());
+  auto clip1 = ClipPaintPropertyNode::Create(
+      LayerState().Clip(), LayerState().Transform(),
+      FloatRoundedRect(10, 10, 100, 100));
+  auto effect = LayerState().Effect();
+  auto chunk1 = Chunk(PropertyTreeState(transform1.get(), clip1.get(), effect));
+
+  auto transform2 = TransformPaintPropertyNode::Create(
+      transform1, TransformationMatrix().Translate(20, 30), FloatPoint3D());
+  auto clip2 = ClipPaintPropertyNode::Create(LayerState().Clip(), transform2,
+                                             FloatRoundedRect(0, 0, 20, 20));
+  auto chunk2 = Chunk(PropertyTreeState(transform2.get(), clip2.get(), effect));
+
+  mapper.SwitchToChunk(chunk1);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20).Scale(2),
+            mapper.Transform());
+  EXPECT_EQ(FloatRect(0, -10, 100, 100), mapper.ClipRect().Rect());
+  EXPECT_TRUE(mapper.ClipRect().IsTight());
+  EXPECT_EQ(IntRect(50, 40, 50, 50),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+
+  mapper.SwitchToChunk(chunk2);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(
+      TransformationMatrix().Translate(-10, -20).Scale(2).Translate(20, 30),
+      mapper.Transform());
+  EXPECT_EQ(FloatRect(30, 40, 40, 40), mapper.ClipRect().Rect());
+  EXPECT_FALSE(mapper.ClipRect().IsTight());
+  EXPECT_EQ(IntRect(30, 40, 40, 40),
+            mapper.MapVisualRect(FloatRect(0, 0, 200, 200)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+}
+
+TEST_F(ChunkToLayerMapperTest, SlowPath) {
+  ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
+  auto chunk1 = Chunk(LayerState());
+
+  // Chunk2 has a blur filter. Should use the slow path.
+  CompositorFilterOperations filter2;
+  filter2.AppendBlurFilter(20);
+  auto effect2 = EffectPaintPropertyNode::Create(
+      EffectPaintPropertyNode::Root(), LayerState().Transform(),
+      LayerState().Clip(), kColorFilterNone, filter2, 1.f, SkBlendMode::kDstIn);
+  auto chunk2 = Chunk(PropertyTreeState(LayerState().Transform(),
+                                        LayerState().Clip(), effect2.get()));
+
+  // Chunk3 has a different effect which inherits from chunk2's effect.
+  // Should use the slow path.
+  auto effect3 = EffectPaintPropertyNode::Create(
+      effect2.get(), LayerState().Transform(), LayerState().Clip(),
+      kColorFilterNone, CompositorFilterOperations(), 1.f, SkBlendMode::kDstIn);
+  auto chunk3 = Chunk(PropertyTreeState(LayerState().Transform(),
+                                        LayerState().Clip(), effect3.get()));
+
+  // Chunk4 has an opacity filter effect which inherits from the layer's effect.
+  // Should use the fast path.
+  CompositorFilterOperations filter4;
+  filter4.AppendOpacityFilter(0.5);
+  auto effect4 = EffectPaintPropertyNode::Create(
+      LayerState().Effect(), LayerState().Transform(), LayerState().Clip(),
+      kColorFilterNone, CompositorFilterOperations(), 1.f, SkBlendMode::kDstIn);
+  auto chunk4 = Chunk(PropertyTreeState(LayerState().Transform(),
+                                        LayerState().Clip(), effect4.get()));
+
+  // Chunk5 goes back to the layer state.
+  auto chunk5 = Chunk(LayerState());
+
+  mapper.SwitchToChunk(chunk1);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+
+  mapper.SwitchToChunk(chunk2);
+  EXPECT_TRUE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_TRUE(mapper.ClipRect().IsInfinite());
+  EXPECT_EQ(IntRect(-40, -50, 208, 219),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(-40, -50, 208, 219),
+            mapper.MapVisualRect(FloatRect(30.2f, 30.7f, 87.3f, 98.1f)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+
+  mapper.SwitchToChunk(chunk3);
+  EXPECT_TRUE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_TRUE(mapper.ClipRect().IsInfinite());
+  EXPECT_EQ(IntRect(-40, -50, 208, 219),
+            mapper.MapVisualRect(FloatRect(30, 30, 88, 99)));
+  EXPECT_EQ(IntRect(-40, -50, 208, 219),
+            mapper.MapVisualRect(FloatRect(30.2f, 30.7f, 87.3f, 98.1f)));
+  EXPECT_EQ(IntRect(), mapper.MapVisualRect(FloatRect()));
+
+  mapper.SwitchToChunk(chunk4);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+
+  mapper.SwitchToChunk(chunk5);
+  EXPECT_FALSE(HasFilterThatMovesPixels(mapper));
+  EXPECT_EQ(TransformationMatrix().Translate(-10, -20), mapper.Transform());
+  EXPECT_EQ(FloatClipRect(), mapper.ClipRect());
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.cpp b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.cpp
index a11f22e..aad465d 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.cpp
@@ -30,44 +30,6 @@
   }
 }
 
-IntRect CompositedLayerRasterInvalidator::MapRectFromChunkToLayer(
-    const FloatRect& r,
-    const PaintChunk& chunk,
-    const PropertyTreeState& layer_state) const {
-  return ClipByLayerBounds(PaintChunksToCcLayer::MapRectFromChunkToLayer(
-      r, chunk, layer_state, layer_bounds_.OffsetFromOrigin()));
-}
-
-TransformationMatrix CompositedLayerRasterInvalidator::ChunkToLayerTransform(
-    const PaintChunk& chunk,
-    const PropertyTreeState& layer_state) const {
-  auto matrix = GeometryMapper::SourceToDestinationProjection(
-      chunk.properties.property_tree_state.Transform(),
-      layer_state.Transform());
-  matrix.Translate(-layer_bounds_.x(), -layer_bounds_.y());
-  return matrix;
-}
-
-// Returns the clip rect when we know it is precise (no radius, no complex
-// transform, no pixel moving filter, etc.)
-FloatClipRect CompositedLayerRasterInvalidator::ChunkToLayerClip(
-    const PaintChunk& chunk,
-    const PropertyTreeState& layer_state) const {
-  FloatClipRect clip_rect;
-  if (chunk.properties.property_tree_state.Effect() != layer_state.Effect()) {
-    // Don't bother GeometryMapper because we don't need the rect when it's not
-    // tight because of the effect nodes.
-    clip_rect.ClearIsTight();
-  } else {
-    clip_rect = GeometryMapper::LocalToAncestorClipRect(
-        chunk.properties.property_tree_state.GetPropertyTreeState(),
-        layer_state);
-    if (clip_rect.IsTight())
-      clip_rect.MoveBy(FloatPoint(-layer_bounds_.x(), -layer_bounds_.y()));
-  }
-  return clip_rect;
-}
-
 size_t CompositedLayerRasterInvalidator::MatchNewChunkToOldChunk(
     const PaintChunk& new_chunk,
     size_t old_index) {
@@ -141,15 +103,18 @@
 // is slightly larger than O(n).
 void CompositedLayerRasterInvalidator::GenerateRasterInvalidations(
     const Vector<const PaintChunk*>& new_chunks,
-    const Vector<PaintChunkInfo>& new_chunks_info,
-    const PropertyTreeState& layer_state) {
+    const PropertyTreeState& layer_state,
+    Vector<PaintChunkInfo>& new_chunks_info) {
+  ChunkToLayerMapper mapper(layer_state, layer_bounds_.OffsetFromOrigin());
   Vector<bool> old_chunks_matched;
   old_chunks_matched.resize(paint_chunks_info_.size());
   size_t old_index = 0;
   size_t max_matched_old_index = 0;
   for (size_t new_index = 0; new_index < new_chunks.size(); ++new_index) {
     const auto& new_chunk = *new_chunks[new_index];
-    const auto& new_chunk_info = new_chunks_info[new_index];
+    mapper.SwitchToChunk(new_chunk);
+    const auto& new_chunk_info =
+        new_chunks_info.emplace_back(*this, mapper, new_chunk);
 
     if (!new_chunk.is_cacheable) {
       FullyInvalidateNewChunk(new_chunk_info,
@@ -191,7 +156,7 @@
         IncrementallyInvalidateChunk(old_chunk_info, new_chunk_info);
 
       // Add the raster invalidations found by PaintController within the chunk.
-      AddDisplayItemRasterInvalidations(new_chunk, layer_state);
+      AddDisplayItemRasterInvalidations(new_chunk, mapper);
     }
 
     old_index = matched_old_index + 1;
@@ -213,14 +178,17 @@
 
 void CompositedLayerRasterInvalidator::AddDisplayItemRasterInvalidations(
     const PaintChunk& chunk,
-    const PropertyTreeState& layer_state) {
+    const ChunkToLayerMapper& mapper) {
   DCHECK(chunk.raster_invalidation_tracking.IsEmpty() ||
          chunk.raster_invalidation_rects.size() ==
              chunk.raster_invalidation_tracking.size());
 
+  if (chunk.raster_invalidation_rects.IsEmpty())
+    return;
+
   for (size_t i = 0; i < chunk.raster_invalidation_rects.size(); ++i) {
-    auto rect = MapRectFromChunkToLayer(chunk.raster_invalidation_rects[i],
-                                        chunk, layer_state);
+    auto rect = ClipByLayerBounds(
+        mapper.MapVisualRect(chunk.raster_invalidation_rects[i]));
     if (rect.IsEmpty())
       continue;
     raster_invalidation_function_(rect);
@@ -300,26 +268,34 @@
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
     EnsureTracking();
 
-  bool layer_bounds_was_empty = layer_bounds_.IsEmpty();
-  layer_bounds_ = layer_bounds;
-
-  Vector<PaintChunkInfo> new_chunks_info;
-  new_chunks_info.ReserveCapacity(paint_chunks.size());
-  for (const auto* chunk : paint_chunks) {
-    new_chunks_info.push_back(PaintChunkInfo(
-        MapRectFromChunkToLayer(chunk->bounds, *chunk, layer_state),
-        ChunkToLayerTransform(*chunk, layer_state),
-        ChunkToLayerClip(*chunk, layer_state), *chunk));
-    if (tracking_info_) {
+  if (tracking_info_) {
+    for (const auto* chunk : paint_chunks) {
       tracking_info_->new_client_debug_names.insert(
           &chunk->id.client, chunk->id.client.DebugName());
     }
   }
 
-  if (!layer_bounds_was_empty && !layer_bounds_.IsEmpty())
-    GenerateRasterInvalidations(paint_chunks, new_chunks_info, layer_state);
+  bool layer_bounds_was_empty = layer_bounds_.IsEmpty();
+  layer_bounds_ = layer_bounds;
+
+  Vector<PaintChunkInfo> new_chunks_info;
+  new_chunks_info.ReserveCapacity(paint_chunks.size());
+
+  if (layer_bounds_was_empty || layer_bounds_.IsEmpty()) {
+    // No raster invalidation is needed if either the old bounds or the new
+    // bounds is empty, but we still need to update new_chunks_info for the
+    // next cycle.
+    ChunkToLayerMapper mapper(layer_state, layer_bounds.OffsetFromOrigin());
+    for (const auto* chunk : paint_chunks) {
+      mapper.SwitchToChunk(*chunk);
+      new_chunks_info.emplace_back(*this, mapper, *chunk);
+    }
+  } else {
+    GenerateRasterInvalidations(paint_chunks, layer_state, new_chunks_info);
+  }
 
   paint_chunks_info_ = std::move(new_chunks_info);
+
   if (tracking_info_) {
     tracking_info_->old_client_debug_names =
         std::move(tracking_info_->new_client_debug_names);
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.h b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.h
index 240b39aa..75b5f1c 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.h
+++ b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidator.h
@@ -5,6 +5,7 @@
 #ifndef CompositedLayerRasterInvalidator_h
 #define CompositedLayerRasterInvalidator_h
 
+#include "platform/graphics/compositing/ChunkToLayerMapper.h"
 #include "platform/graphics/paint/FloatClipRect.h"
 #include "platform/graphics/paint/PaintChunk.h"
 #include "platform/graphics/paint/RasterInvalidationTracking.h"
@@ -48,13 +49,13 @@
   friend class CompositedLayerRasterInvalidatorTest;
 
   struct PaintChunkInfo {
-    PaintChunkInfo(const IntRect& bounds,
-                   const TransformationMatrix& chunk_to_layer_transform,
-                   const FloatClipRect& chunk_to_layer_clip,
+    PaintChunkInfo(const CompositedLayerRasterInvalidator& invalidator,
+                   const ChunkToLayerMapper& mapper,
                    const PaintChunk& chunk)
-        : bounds_in_layer(bounds),
-          chunk_to_layer_transform(chunk_to_layer_transform),
-          chunk_to_layer_clip(chunk_to_layer_clip),
+        : bounds_in_layer(invalidator.ClipByLayerBounds(
+              mapper.MapVisualRect(chunk.bounds))),
+          chunk_to_layer_transform(mapper.Transform()),
+          chunk_to_layer_clip(mapper.ClipRect()),
           id(chunk.id),
           is_cacheable(chunk.is_cacheable),
           properties(chunk.properties) {}
@@ -71,22 +72,12 @@
     PaintChunkProperties properties;
   };
 
-  IntRect MapRectFromChunkToLayer(const FloatRect&,
-                                  const PaintChunk&,
-                                  const PropertyTreeState& layer_state) const;
-  TransformationMatrix ChunkToLayerTransform(
-      const PaintChunk&,
-      const PropertyTreeState& layer_state) const;
-  FloatClipRect ChunkToLayerClip(const PaintChunk&,
-                                 const PropertyTreeState& layer_state) const;
-
-  void GenerateRasterInvalidations(
-      const Vector<const PaintChunk*>& new_chunks,
-      const Vector<PaintChunkInfo>& new_chunks_info,
-      const PropertyTreeState& layer_state);
+  void GenerateRasterInvalidations(const Vector<const PaintChunk*>& new_chunks,
+                                   const PropertyTreeState& layer_state,
+                                   Vector<PaintChunkInfo>& new_chunks_info);
   size_t MatchNewChunkToOldChunk(const PaintChunk& new_chunk, size_t old_index);
   void AddDisplayItemRasterInvalidations(const PaintChunk&,
-                                         const PropertyTreeState& layer_state);
+                                         const ChunkToLayerMapper&);
   void IncrementallyInvalidateChunk(const PaintChunkInfo& old_chunk,
                                     const PaintChunkInfo& new_chunk);
   void FullyInvalidateChunk(const PaintChunkInfo& old_chunk,
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidatorTest.cpp b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidatorTest.cpp
index 7624036..8927dca 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidatorTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/CompositedLayerRasterInvalidatorTest.cpp
@@ -294,10 +294,8 @@
   EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
 
   // Change both clip0 and clip2.
-  LOG(ERROR) << "22222222222222222222222222222222222222222222";
   CHUNKS(new_chunks, Chunk(0), Chunk(1), Chunk(2));
   FloatRoundedRect new_clip_rect(FloatRect(-2000, -2000, 4000, 4000), radii);
-  LOG(ERROR) << "new_clip_rect: " << new_clip_rect.ToString();
   clip0->Update(clip0->Parent(), clip0->LocalTransformSpace(), new_clip_rect);
   clip2->Update(clip2->Parent(), clip2->LocalTransformSpace(), new_clip_rect);
   new_chunks_array[0].properties = chunks[0]->properties;
@@ -314,7 +312,6 @@
                             PaintInvalidationReason::kPaintProperty);
   invalidator.SetTracksRasterInvalidations(false);
   clip2->ClearChangedToRoot();
-  LOG(ERROR) << "333333333333333333333333333333333333333333333";
 
   // Change chunk1's properties to use a different property tree state.
   CHUNKS(new_chunks1, Chunk(0), Chunk(1), Chunk(2));
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
index 7f939b5..fd78bd0 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.cpp
@@ -8,6 +8,7 @@
 #include "cc/paint/paint_op_buffer.h"
 #include "cc/paint/render_surface_filters.h"
 #include "platform/graphics/GraphicsContext.h"
+#include "platform/graphics/compositing/ChunkToLayerMapper.h"
 #include "platform/graphics/paint/DisplayItemList.h"
 #include "platform/graphics/paint/DrawingDisplayItem.h"
 #include "platform/graphics/paint/GeometryMapper.h"
@@ -424,6 +425,8 @@
     translated = true;
   };
 
+  ChunkToLayerMapper mapper(layer_state_, layer_offset_);
+
   for (auto chunk_it = paint_chunks.begin(); chunk_it != paint_chunks.end();
        chunk_it++) {
     const PaintChunk& chunk = **chunk_it;
@@ -450,6 +453,8 @@
       properties_adjusted = true;
     };
 
+    mapper.SwitchToChunk(chunk);
+
     for (const auto& item : display_items.ItemsInPaintChunk(chunk)) {
       DCHECK(item.IsDrawing());
       auto record =
@@ -469,8 +474,7 @@
       cc_list_.StartPaint();
       if (record && record->size() != 0)
         cc_list_.push<cc::DrawRecordOp>(std::move(record));
-      cc_list_.EndPaintOfUnpaired(PaintChunksToCcLayer::MapRectFromChunkToLayer(
-          item.VisualRect(), chunk, layer_state_, layer_offset_));
+      cc_list_.EndPaintOfUnpaired(mapper.MapVisualRect(item.VisualRect()));
     }
     if (transformed)
       AppendRestore(1);
@@ -528,23 +532,4 @@
   return cc_list;
 }
 
-IntRect PaintChunksToCcLayer::MapRectFromChunkToLayer(
-    const FloatRect& r,
-    const PaintChunk& chunk,
-    const PropertyTreeState& layer_state,
-    const gfx::Vector2dF& layer_offset) {
-  FloatClipRect rect(r);
-  GeometryMapper::LocalToAncestorVisualRect(
-      chunk.properties.property_tree_state.GetPropertyTreeState(), layer_state,
-      rect);
-  if (rect.Rect().IsEmpty())
-    return IntRect();
-
-  // Now rect is in the space of the containing transform node of layer,
-  // so need to subtract off the layer offset.
-  rect.Rect().Move(-layer_offset.x(), -layer_offset.y());
-  rect.Rect().Inflate(chunk.outset_for_raster_effects);
-  return EnclosingIntRect(rect.Rect());
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.h b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.h
index cc29ae0..4c56db6 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.h
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintChunksToCcLayer.h
@@ -69,11 +69,6 @@
       const DisplayItemList&,
       cc::DisplayItemList::UsageHint,
       RasterUnderInvalidationCheckingParams* = nullptr);
-
-  static IntRect MapRectFromChunkToLayer(const FloatRect&,
-                                         const PaintChunk&,
-                                         const PropertyTreeState& layer_state,
-                                         const gfx::Vector2dF& layer_offset);
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.cpp b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.cpp
index 82b4f80..34d3d37 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.cpp
@@ -250,6 +250,11 @@
       local_state.Clip(), ancestor_state.Clip(), ancestor_state.Transform(),
       clip_behavior, success);
   DCHECK(success);
+
+  // Many effects (e.g. filters, clip-paths) can make a clip rect not tight.
+  if (local_state.Effect() != ancestor_state.Effect())
+    result.ClearIsTight();
+
   return result;
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapperTest.cpp b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapperTest.cpp
index a7c4195..8a4db2c 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapperTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapperTest.cpp
@@ -649,6 +649,7 @@
   expected_visual_rect = FloatClipRect(output);
   expected_visual_rect.ClearIsTight();
   expected_clip = FloatClipRect(FloatRect(50, 60, 90, 90));
+  expected_clip.ClearIsTight();
   CHECK_MAPPINGS();
 }
 
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
index d31d8ca..cfcb00f 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
@@ -6,15 +6,12 @@
 
 #include <memory>
 #include "platform/graphics/GraphicsLayer.h"
+#include "platform/graphics/LoggingCanvas.h"
 #include "platform/graphics/paint/DrawingDisplayItem.h"
 #include "platform/instrumentation/tracing/TraceEvent.h"
 #include "platform/wtf/AutoReset.h"
 #include "platform/wtf/text/StringBuilder.h"
 
-#ifndef NDEBUG
-#include "platform/graphics/LoggingCanvas.h"
-#endif
-
 namespace blink {
 
 void PaintController::SetTracksRasterInvalidations(bool value) {
@@ -1019,12 +1016,8 @@
   LOG(ERROR) << "New display item: " << new_item.AsDebugString();
   LOG(ERROR) << "Old display item: "
              << (old_item ? old_item->AsDebugString() : "None");
-#else
-  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
-#endif
   LOG(ERROR) << "See http://crbug.com/619103.";
 
-#ifndef NDEBUG
   const PaintRecord* new_record = nullptr;
   if (new_item.IsDrawing()) {
     new_record =
@@ -1044,7 +1037,10 @@
                            : "None");
 
   ShowDebugData();
-#endif  // NDEBUG
+#else
+  LOG(ERROR) << "Run a build with DCHECK on to get more details.";
+  LOG(ERROR) << "See http://crbug.com/619103.";
+#endif
 }
 
 void PaintController::ShowSequenceUnderInvalidationError(
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.cc
index deea3d1..cf60ad2 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.cc
@@ -96,6 +96,10 @@
           DURATION_PER_QUEUE_TYPE_METRIC_NAME ".HiddenMusic"),
       per_frame_status_duration_reporter(DURATION_PER_FRAME_TYPE_METRIC_NAME),
       per_task_type_duration_reporter(DURATION_PER_TASK_TYPE_METRIC_NAME),
+      foreground_per_task_type_duration_reporter(
+          DURATION_PER_TASK_TYPE_METRIC_NAME ".Foreground"),
+      background_per_task_type_duration_reporter(
+          DURATION_PER_TASK_TYPE_METRIC_NAME ".Background"),
       main_thread_task_load_state(MainThreadTaskLoadState::kUnknown) {
   main_thread_load_tracker.Resume(now);
   if (renderer_backgrounded) {
@@ -230,6 +234,9 @@
 
   per_queue_type_task_duration_reporter.RecordTask(queue_type, duration);
 
+  TaskType task_type = static_cast<TaskType>(task.task_type());
+  per_task_type_duration_reporter.RecordTask(task_type, duration);
+
   if (renderer_scheduler_->main_thread_only().renderer_backgrounded) {
     background_per_queue_type_task_duration_reporter.RecordTask(queue_type,
                                                                 duration);
@@ -276,6 +283,8 @@
                 backgrounded_at + base::TimeDelta::FromMinutes(5),
                 std::max(backgrounded_at + base::TimeDelta::FromMinutes(5),
                          end_time)));
+
+    background_per_task_type_duration_reporter.RecordTask(task_type, duration);
   } else {
     foreground_per_queue_type_task_duration_reporter.RecordTask(queue_type,
                                                                 duration);
@@ -310,6 +319,8 @@
                 foregrounded_at + base::TimeDelta::FromMinutes(3),
                 std::max(foregrounded_at + base::TimeDelta::FromMinutes(3),
                          end_time)));
+
+    foreground_per_task_type_duration_reporter.RecordTask(task_type, duration);
   }
 
   if (renderer_scheduler_->main_thread_only().renderer_hidden) {
@@ -354,9 +365,6 @@
     UMA_HISTOGRAM_ENUMERATION(COUNT_PER_FRAME_METRIC_NAME ".LongerThan1s",
                               frame_status, FrameStatus::kCount);
   }
-
-  per_task_type_duration_reporter.RecordTask(
-      static_cast<TaskType>(task.task_type()), duration);
 }
 
 void RendererMetricsHelper::RecordMainThreadTaskLoad(base::TimeTicks time,
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.h
index d5f057e..49c7ac3 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.h
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_metrics_helper.h
@@ -119,6 +119,10 @@
   using TaskDurationPerTaskTypeMetricReporter =
       TaskDurationMetricReporter<TaskType>;
   TaskDurationPerTaskTypeMetricReporter per_task_type_duration_reporter;
+  TaskDurationPerTaskTypeMetricReporter
+      foreground_per_task_type_duration_reporter;
+  TaskDurationPerTaskTypeMetricReporter
+      background_per_task_type_duration_reporter;
 
   MainThreadTaskLoadState main_thread_task_load_state;
 
diff --git a/third_party/WebKit/Source/platform/testing/FakeDisplayItemClient.h b/third_party/WebKit/Source/platform/testing/FakeDisplayItemClient.h
index 3742758..d67d5fe 100644
--- a/third_party/WebKit/Source/platform/testing/FakeDisplayItemClient.h
+++ b/third_party/WebKit/Source/platform/testing/FakeDisplayItemClient.h
@@ -6,6 +6,7 @@
 #define FakeDisplayItemClient_h
 
 #include "platform/geometry/LayoutRect.h"
+#include "platform/graphics/paint/DisplayItemClient.h"
 #include "platform/wtf/Forward.h"
 
 namespace blink {
diff --git a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h
index d77dc0c..5be4efa 100644
--- a/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h
+++ b/third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h
@@ -144,7 +144,7 @@
       PolicyAreas = kPolicyAreaAll);
 
   // Schemes which bypass Secure Context checks defined in
-  // https://w3c.github.io/webappsec/specs/powerfulfeatures/#is-origin-trustworthy.
+  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
   static void RegisterURLSchemeBypassingSecureContextCheck(
       const String& scheme);
   static bool SchemeShouldBypassSecureContextCheck(const String& scheme);
diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
index bc1179d..3640e65 100644
--- a/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
+++ b/third_party/WebKit/Source/platform/weborigin/SecurityOrigin.h
@@ -123,7 +123,7 @@
   // Returns true if the origin loads resources either from the local
   // machine or over the network from a
   // cryptographically-authenticated origin, as described in
-  // https://w3c.github.io/webappsec/specs/powerfulfeatures/#is-origin-trustworthy.
+  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
   bool IsPotentiallyTrustworthy() const;
 
   // Returns a human-readable error message describing that a non-secure
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info.py
index cb3b427c..dab67d1 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info.py
@@ -74,7 +74,7 @@
         if self.is_mac():
             output = self._executive.run_command(['system_profiler', 'SPDisplaysDataType'],
                                                  error_handler=self._executive.ignore_error)
-            if output and 'Retina: Yes' in output:
+            if output and re.search(r'Resolution:.*Retina$', output, re.MULTILINE):
                 return True
         return False
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py
index 8ca5a7d..819c953 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py
@@ -40,12 +40,12 @@
     SUPPORTED_VERSIONS = ('mac10.10', 'mac10.11', 'mac10.12', 'mac10.13', 'retina')
     port_name = 'mac'
 
-    # FIXME: We treat Retina (High-DPI) devices as if they are running
-    # a different operating system version. This is lame and should be fixed.
-    # Note that the retina versions fallback to the non-retina versions and so no
-    # baselines are shared between retina versions; this keeps the fallback graph as a tree
-    # and maximizes the number of baselines we can share that way.
-    # We also currently only support Retina on 10.11.
+    # FIXME: We treat Retina (High-DPI) devices as if they are running a
+    # different operating system version. This is lame and should be fixed.
+    # Note that the retina versions fallback to the non-retina versions and so
+    # no baselines are shared between retina versions; this keeps the fallback
+    # graph as a tree and maximizes the number of baselines we can share that
+    # way. We also currently only support Retina on 10.12.
 
     FALLBACK_PATHS = {}
 
@@ -53,7 +53,7 @@
     FALLBACK_PATHS['mac10.12'] = ['mac-mac10.12'] + FALLBACK_PATHS['mac10.13']
     FALLBACK_PATHS['mac10.11'] = ['mac-mac10.11'] + FALLBACK_PATHS['mac10.12']
     FALLBACK_PATHS['mac10.10'] = ['mac-mac10.10'] + FALLBACK_PATHS['mac10.11']
-    FALLBACK_PATHS['retina'] = ['mac-retina'] + FALLBACK_PATHS['mac10.11']
+    FALLBACK_PATHS['retina'] = ['mac-retina'] + FALLBACK_PATHS['mac10.12']
 
     CONTENT_SHELL_NAME = 'Content Shell'
 
diff --git a/third_party/WebKit/public/platform/WebSecurityOrigin.h b/third_party/WebKit/public/platform/WebSecurityOrigin.h
index 798ade33..6bbf812 100644
--- a/third_party/WebKit/public/platform/WebSecurityOrigin.h
+++ b/third_party/WebKit/public/platform/WebSecurityOrigin.h
@@ -92,7 +92,7 @@
   // Returns true if the origin loads resources either from the local
   // machine or over the network from a
   // cryptographically-authenticated origin, as described in
-  // https://w3c.github.io/webappsec/specs/powerfulfeatures/#is-origin-trustworthy.
+  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy.
   BLINK_PLATFORM_EXPORT bool IsPotentiallyTrustworthy() const;
 
   // Returns a string representation of the WebSecurityOrigin.  The empty
diff --git a/third_party/WebKit/public/platform/web_feature.mojom b/third_party/WebKit/public/platform/web_feature.mojom
index 716b0536..4476c4b6 100644
--- a/third_party/WebKit/public/platform/web_feature.mojom
+++ b/third_party/WebKit/public/platform/web_feature.mojom
@@ -1876,6 +1876,8 @@
   kRtcPeerConnectionId = 2384,
   kV8PaintWorkletGlobalScope_RegisterPaint_Method = 2385,
   kV8PaintWorkletGlobalScope_DevicePixelRatio_AttributeGetter = 2386,
+  kCSSSelectorPseudoFocus = 2387,
+  kCSSSelectorPseudoFocusVisible = 2388,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/WebKit/public/web/WebView.h b/third_party/WebKit/public/web/WebView.h
index 8fc376c..69a9beca 100644
--- a/third_party/WebKit/public/web/WebView.h
+++ b/third_party/WebKit/public/web/WebView.h
@@ -428,6 +428,11 @@
   // to call the WebViewClient::acceptLanguages().
   virtual void AcceptLanguagesChanged() = 0;
 
+  // Lifecycle state ------------------------------------------------------
+
+  // Freeze the page and all the local frames.
+  virtual void FreezePage() = 0;
+
   // Testing functionality for TestRunner ---------------------------------
 
   // Force the webgl context to fail so that webglcontextcreationerror
diff --git a/third_party/webrtc_overrides/DEPS b/third_party/webrtc_overrides/DEPS
index 529c21d..a191f6d 100644
--- a/third_party/webrtc_overrides/DEPS
+++ b/third_party/webrtc_overrides/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   '+base',
+  '+build',
   '+net/base',
   '+third_party/webrtc',
   '+third_party/webrtc_overrides',
diff --git a/third_party/webrtc_overrides/rtc_base/task_queue.cc b/third_party/webrtc_overrides/rtc_base/task_queue.cc
index fd811b6..74f16803 100644
--- a/third_party/webrtc_overrides/rtc_base/task_queue.cc
+++ b/third_party/webrtc_overrides/rtc_base/task_queue.cc
@@ -13,104 +13,191 @@
 #include "base/bind.h"
 #include "base/lazy_instance.h"
 #include "base/memory/ref_counted.h"
-#include "base/threading/thread.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_local.h"
+#include "build/build_config.h"
 #include "third_party/webrtc/rtc_base/refcount.h"
 #include "third_party/webrtc/rtc_base/refcountedobject.h"
 
-// Intentionally outside of the "namespace rtc { ... }" block, because
-// here, scoped_refptr should *not* be resolved as rtc::scoped_refptr.
-namespace {
-
-void RunTask(std::unique_ptr<rtc::QueuedTask> task) {
-  if (!task->Run())
-    task.release();
-}
-
-class PostAndReplyTask : public rtc::QueuedTask {
- public:
-  PostAndReplyTask(
-      std::unique_ptr<rtc::QueuedTask> task,
-      std::unique_ptr<rtc::QueuedTask> reply,
-      const scoped_refptr<base::SingleThreadTaskRunner>& reply_task_runner)
-      : task_(std::move(task)),
-        reply_(std::move(reply)),
-        reply_task_runner_(reply_task_runner) {}
-
-  ~PostAndReplyTask() override {}
-
- private:
-  bool Run() override {
-    if (!task_->Run())
-      task_.release();
-
-    reply_task_runner_->PostTask(FROM_HERE,
-                                 base::Bind(&RunTask, base::Passed(&reply_)));
-    return true;
-  }
-
-  std::unique_ptr<rtc::QueuedTask> task_;
-  std::unique_ptr<rtc::QueuedTask> reply_;
-  scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner_;
-};
-
-// A lazily created thread local storage for quick access to a TaskQueue.
-base::LazyInstance<base::ThreadLocalPointer<rtc::TaskQueue>>::Leaky
-    lazy_tls_ptr = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
+using base::WaitableEvent;
 
 namespace rtc {
+namespace {
+
+// A lazily created thread local storage for quick access to a TaskQueue.
+base::LazyInstance<base::ThreadLocalPointer<TaskQueue>>::Leaky lazy_tls_ptr =
+    LAZY_INSTANCE_INITIALIZER;
+
+base::TaskTraits TaskQueuePriority2Traits(TaskQueue::Priority priority) {
+  // The content/renderer/media/webrtc/rtc_video_encoder.* code
+  // employs a PostTask/Wait pattern that uses TQ in a way that makes it
+  // blocking and synchronous, which is why we allow WithBaseSyncPrimitives()
+  // for OS_ANDROID.
+  switch (priority) {
+    case TaskQueue::Priority::HIGH:
+#if defined(OS_ANDROID)
+      return {base::WithBaseSyncPrimitives(), base::TaskPriority::HIGHEST};
+#else
+      return {base::TaskPriority::HIGHEST};
+#endif
+      break;
+    case TaskQueue::Priority::LOW:
+      return {base::MayBlock(), base::TaskPriority::BACKGROUND};
+    case TaskQueue::Priority::NORMAL:
+    default:
+#if defined(OS_ANDROID)
+      return {base::WithBaseSyncPrimitives()};
+#else
+      return {};
+#endif
+  }
+}
+
+}  // namespace
 
 bool TaskQueue::IsCurrent() const {
   return Current() == this;
 }
 
-class TaskQueue::Impl : public RefCountInterface, public base::Thread {
+class TaskQueue::Impl : public RefCountInterface {
  public:
-  Impl(const char* queue_name, TaskQueue* queue);
+  Impl(const char* queue_name,
+       TaskQueue* queue,
+       const base::TaskTraits& traits);
   ~Impl() override;
 
+  // To maintain functional compatibility with WebRTC's TaskQueue, we flush
+  // and deactivate the task queue here, synchronously.
+  // This has some drawbacks and will likely change in the future, but for now
+  // is necessary.
+  void Stop();
+
+  void PostTask(std::unique_ptr<QueuedTask> task);
+  void PostDelayedTask(std::unique_ptr<QueuedTask> task, uint32_t milliseconds);
+  void PostTaskAndReply(std::unique_ptr<QueuedTask> task,
+                        std::unique_ptr<QueuedTask> reply,
+                        TaskQueue* reply_queue);
+  void PostTaskAndReply(std::unique_ptr<QueuedTask> task,
+                        std::unique_ptr<QueuedTask> reply);
+
  private:
-  virtual void Init() override;
+  void RunTask(std::unique_ptr<QueuedTask> task);
+  void Deactivate(WaitableEvent* event);
+
+  class PostAndReplyTask : public QueuedTask {
+   public:
+    PostAndReplyTask(std::unique_ptr<QueuedTask> task,
+                     ::scoped_refptr<TaskQueue::Impl> target_queue,
+                     std::unique_ptr<QueuedTask> reply,
+                     ::scoped_refptr<TaskQueue::Impl> reply_queue)
+        : task_(std::move(task)),
+          target_queue_(std::move(target_queue)),
+          reply_(std::move(reply)),
+          reply_queue_(std::move(reply_queue)) {}
+
+    ~PostAndReplyTask() override {}
+
+   private:
+    bool Run() override {
+      if (task_) {
+        target_queue_->RunTask(std::move(task_));
+        std::unique_ptr<QueuedTask> t = std::unique_ptr<QueuedTask>(this);
+        reply_queue_->PostTask(std::move(t));
+        return false;  // Don't delete, ownership lies with reply_queue_.
+      }
+
+      reply_queue_->RunTask(std::move(reply_));
+
+      return true;  // OK to delete.
+    }
+
+    std::unique_ptr<QueuedTask> task_;
+    ::scoped_refptr<TaskQueue::Impl> target_queue_;
+    std::unique_ptr<QueuedTask> reply_;
+    ::scoped_refptr<TaskQueue::Impl> reply_queue_;
+  };
 
   TaskQueue* const queue_;
+  const ::scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  bool is_active_ = true;  // Checked and set on |task_runner_|.
 };
 
-TaskQueue::Impl::Impl(const char* queue_name, TaskQueue* queue)
-    : base::Thread(queue_name), queue_(queue) {}
+// TaskQueue::Impl.
 
-void TaskQueue::Impl::Init() {
+TaskQueue::Impl::Impl(const char* queue_name,
+                      TaskQueue* queue,
+                      const base::TaskTraits& traits)
+    : queue_(queue),
+      task_runner_(base::CreateSequencedTaskRunnerWithTraits(traits)) {
+  DCHECK(task_runner_);
+}
+
+TaskQueue::Impl::~Impl() {}
+
+void TaskQueue::Impl::Stop() {
+  WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+                      WaitableEvent::InitialState::NOT_SIGNALED);
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&TaskQueue::Impl::Deactivate, this, &event));
+  event.Wait();
+}
+
+void TaskQueue::Impl::PostTask(std::unique_ptr<QueuedTask> task) {
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(&TaskQueue::Impl::RunTask,
+                                                   this, base::Passed(&task)));
+}
+
+void TaskQueue::Impl::PostDelayedTask(std::unique_ptr<QueuedTask> task,
+                                      uint32_t milliseconds) {
+  task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&TaskQueue::Impl::RunTask, this, base::Passed(&task)),
+      base::TimeDelta::FromMilliseconds(milliseconds));
+}
+
+void TaskQueue::Impl::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
+                                       std::unique_ptr<QueuedTask> reply,
+                                       TaskQueue* reply_queue) {
+  std::unique_ptr<QueuedTask> t =
+      std::unique_ptr<QueuedTask>(new PostAndReplyTask(
+          std::move(task), this, std::move(reply), reply_queue->impl_.get()));
+  PostTask(std::move(t));
+}
+
+void TaskQueue::Impl::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
+                                       std::unique_ptr<QueuedTask> reply) {
+  PostTaskAndReply(std::move(task), std::move(reply), queue_);
+}
+
+void TaskQueue::Impl::RunTask(std::unique_ptr<QueuedTask> task) {
+  if (!is_active_)
+    return;
+
+  auto* prev = lazy_tls_ptr.Pointer()->Get();
   lazy_tls_ptr.Pointer()->Set(queue_);
+  if (!task->Run())
+    task.release();
+  lazy_tls_ptr.Pointer()->Set(prev);
 }
 
-TaskQueue::Impl::~Impl() {
-  DCHECK(!Thread::IsRunning());
+void TaskQueue::Impl::Deactivate(WaitableEvent* event) {
+  is_active_ = false;
+  event->Signal();
 }
 
+// TaskQueue.
+
 TaskQueue::TaskQueue(const char* queue_name,
                      Priority priority /*= Priority::NORMAL*/)
-    : impl_(new RefCountedObject<Impl>(queue_name, this)) {
+    : impl_(new RefCountedObject<Impl>(queue_name,
+                                       this,
+                                       TaskQueuePriority2Traits(priority))) {
   DCHECK(queue_name);
-  base::Thread::Options options;
-  switch (priority) {
-    case Priority::HIGH:
-      options.priority = base::ThreadPriority::REALTIME_AUDIO;
-      break;
-    case Priority::LOW:
-      options.priority = base::ThreadPriority::BACKGROUND;
-      break;
-    case Priority::NORMAL:
-    default:
-      options.priority = base::ThreadPriority::NORMAL;
-      break;
-  }
-  CHECK(impl_->StartWithOptions(options));
 }
 
 TaskQueue::~TaskQueue() {
   DCHECK(!IsCurrent());
-  impl_->Stop();
 }
 
 // static
@@ -119,29 +206,23 @@
 }
 
 void TaskQueue::PostTask(std::unique_ptr<QueuedTask> task) {
-  impl_->task_runner()->PostTask(FROM_HERE,
-                                 base::Bind(&RunTask, base::Passed(&task)));
+  impl_->PostTask(std::move(task));
 }
 
 void TaskQueue::PostDelayedTask(std::unique_ptr<QueuedTask> task,
                                 uint32_t milliseconds) {
-  impl_->task_runner()->PostDelayedTask(
-      FROM_HERE, base::Bind(&RunTask, base::Passed(&task)),
-      base::TimeDelta::FromMilliseconds(milliseconds));
+  impl_->PostDelayedTask(std::move(task), milliseconds);
 }
 
 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
                                  std::unique_ptr<QueuedTask> reply,
                                  TaskQueue* reply_queue) {
-  PostTask(std::unique_ptr<QueuedTask>(new PostAndReplyTask(
-      std::move(task), std::move(reply), reply_queue->impl_->task_runner())));
+  impl_->PostTaskAndReply(std::move(task), std::move(reply), reply_queue);
 }
 
 void TaskQueue::PostTaskAndReply(std::unique_ptr<QueuedTask> task,
                                  std::unique_ptr<QueuedTask> reply) {
-  impl_->task_runner()->PostTaskAndReply(
-      FROM_HERE, base::Bind(&RunTask, base::Passed(&task)),
-      base::Bind(&RunTask, base::Passed(&reply)));
+  impl_->PostTaskAndReply(std::move(task), std::move(reply));
 }
 
 }  // namespace rtc
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9a61624..8890c4a6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -7743,9 +7743,22 @@
   <int value="50" label="0.5: Slowest"/>
   <int value="75" label="0.75: Slow"/>
   <int value="100" label="1.0: Normal"/>
-  <int value="125" label="1.25: Fast"/>
-  <int value="150" label="1.5: Faster"/>
-  <int value="200" label="2.0: Fastest"/>
+  <int value="125" label="1.25"/>
+  <int value="150" label="1.5"/>
+  <int value="175" label="1.75"/>
+  <int value="200" label="2.0"/>
+  <int value="225" label="2.25"/>
+  <int value="250" label="2.5"/>
+  <int value="275" label="2.75"/>
+  <int value="300" label="3.0"/>
+  <int value="325" label="3.25"/>
+  <int value="350" label="3.5"/>
+  <int value="375" label="3.75"/>
+  <int value="400" label="4.0"/>
+  <int value="425" label="4.25"/>
+  <int value="450" label="4.5"/>
+  <int value="475" label="4.75"/>
+  <int value="500" label="5.0: Fastest"/>
 </enum>
 
 <enum name="CrosSelectToSpeakStartSpeechMethod">
@@ -17943,6 +17956,8 @@
   <int value="2385" label="V8PaintWorkletGlobalScope_RegisterPaint_Method"/>
   <int value="2386"
       label="V8PaintWorkletGlobalScope_DevicePixelRatio_AttributeGetter"/>
+  <int value="2387" label="CSSSelectorPseudoFocus"/>
+  <int value="2388" label="CSSSelectorPseudoFocusVisible"/>
 </enum>
 
 <enum name="FeedbackSource">
@@ -26743,6 +26758,7 @@
   <int value="665409384"
       label="AutofillToolkitViewsCreditCardDialogsMac:enabled"/>
   <int value="679931272" label="DcheckIsFatal:enabled"/>
+  <int value="682549212" label="ash-enable-cursor-motion-blur"/>
   <int value="684806628" label="TranslateLanguageByULP:disabled"/>
   <int value="685916283" label="enable-zip-archiver-on-file-manager"/>
   <int value="687838135" label="ThirdPartyDoodles:disabled"/>
@@ -26906,7 +26922,9 @@
   <int value="1075637651" label="disable-tablet-splitview"/>
   <int value="1079032226" label="ParallelDownloading:enabled"/>
   <int value="1081546525" label="ash-enable-docked-windows"/>
+  <int value="1082054180" label="PersistentWindowBounds:disabled"/>
   <int value="1082405724" label="AutofillUpstreamUseGooglePayBranding:enabled"/>
+  <int value="1083201516" label="PersistentWindowBounds:enabled"/>
   <int value="1087235172" label="file-manager-enable-new-audio-player"/>
   <int value="1090377940" label="enable-quic-https"/>
   <int value="1092896354" label="EnableFullscreenAppList:disabled"/>
@@ -27097,6 +27115,7 @@
   <int value="1497924954" label="js-flags"/>
   <int value="1499163193" label="PostScriptPrinting:disabled"/>
   <int value="1505194447" label="disable-transition-compositing"/>
+  <int value="1508761653" label="disable-site-isolation-trials"/>
   <int value="1509901380" label="disable-drive-search-in-app-launcher"/>
   <int value="1510476448" label="disable-prefixed-encrypted-media"/>
   <int value="1510659184" label="MacMDDownloadShelf:enabled"/>
@@ -27857,9 +27876,9 @@
   <int value="175" label="webkit-appearance"/>
   <int value="176" label="webkit-aspect-ratio"/>
   <int value="177" label="alias-webkit-backface-visibility"/>
-  <int value="178" label="webkit-background-clip"/>
+  <int value="178" label="alias-webkit-background-clip"/>
   <int value="179" label="webkit-background-composite"/>
-  <int value="180" label="webkit-background-origin"/>
+  <int value="180" label="alias-webkit-background-origin"/>
   <int value="181" label="alias-webkit-background-size"/>
   <int value="182" label="webkit-border-after"/>
   <int value="183" label="webkit-border-after-color"/>
@@ -28270,6 +28289,8 @@
   <int value="588" label="font-variant-east-asian"/>
   <int value="589" label="text-decoration-skip-ink"/>
   <int value="590" label="scroll-customization"/>
+  <int value="591" label="row-gap"/>
+  <int value="592" label="gap"/>
 </enum>
 
 <enum name="MappedEditingCommands">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 1ee4d94f..d30877e 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -112719,6 +112719,7 @@
   <suffix name="Background"/>
   <suffix name="Foreground"/>
   <affected-histogram name="RendererScheduler.TaskCPUDurationPerThreadType"/>
+  <affected-histogram name="RendererScheduler.TaskDurationPerTaskType"/>
   <affected-histogram name="RendererScheduler.TaskDurationPerThreadType"/>
 </histogram_suffixes>
 
diff --git a/tools/perf/process_perf_results.py b/tools/perf/process_perf_results.py
index 052caca..b66677e 100755
--- a/tools/perf/process_perf_results.py
+++ b/tools/perf/process_perf_results.py
@@ -22,6 +22,9 @@
     build_properties, oauth_file, tmp_dir):
   """Upload the contents of result JSON(s) to the perf dashboard."""
   build_properties = json.loads(build_properties)
+  if not configuration_name:
+    # we are deprecating perf-id crbug.com/817823
+    configuration_name = build_properties['buildername'],
   args = [
       '--tmp-dir', tmp_dir,
       '--buildername', build_properties['buildername'],
diff --git a/ui/app_list/BUILD.gn b/ui/app_list/BUILD.gn
index 2c4586d..0a8a1f8 100644
--- a/ui/app_list/BUILD.gn
+++ b/ui/app_list/BUILD.gn
@@ -51,6 +51,10 @@
     "views/folder_header_view.cc",
     "views/folder_header_view.h",
     "views/folder_header_view_delegate.h",
+    "views/horizontal_page.cc",
+    "views/horizontal_page.h",
+    "views/horizontal_page_container.cc",
+    "views/horizontal_page_container.h",
     "views/image_shadow_animator.cc",
     "views/image_shadow_animator.h",
     "views/indicator_chip_view.cc",
diff --git a/ui/app_list/views/app_list_folder_view.cc b/ui/app_list/views/app_list_folder_view.cc
index d47d243..bbaff79 100644
--- a/ui/app_list/views/app_list_folder_view.cc
+++ b/ui/app_list/views/app_list_folder_view.cc
@@ -15,7 +15,6 @@
 #include "ui/app_list/app_list_util.h"
 #include "ui/app_list/pagination_model.h"
 #include "ui/app_list/views/app_list_item_view.h"
-#include "ui/app_list/views/app_list_main_view.h"
 #include "ui/app_list/views/apps_container_view.h"
 #include "ui/app_list/views/apps_grid_view.h"
 #include "ui/app_list/views/contents_view.h"
@@ -411,9 +410,9 @@
 
 AppListFolderView::AppListFolderView(AppsContainerView* container_view,
                                      AppListModel* model,
-                                     AppListMainView* app_list_main_view)
+                                     ContentsView* contents_view)
     : container_view_(container_view),
-      app_list_main_view_(app_list_main_view),
+      contents_view_(contents_view),
       background_view_(new views::View),
       contents_container_(new views::View),
       folder_header_view_(new FolderHeaderView(this)),
@@ -434,8 +433,7 @@
   AddChildView(contents_container_);
   view_model_->Add(contents_container_, kIndexContentsContainer);
 
-  items_grid_view_ =
-      new AppsGridView(app_list_main_view_->contents_view(), this);
+  items_grid_view_ = new AppsGridView(contents_view_, this);
   items_grid_view_->SetModel(model);
   contents_container_->AddChildView(items_grid_view_);
   view_model_->Add(items_grid_view_, kIndexChildItems);
@@ -732,7 +730,7 @@
 }
 
 void AppListFolderView::GiveBackFocusToSearchBox() {
-  app_list_main_view_->search_box_view()->search_box()->RequestFocus();
+  contents_view_->GetSearchBoxView()->search_box()->RequestFocus();
 }
 
 void AppListFolderView::SetItemName(AppListFolderItem* item,
diff --git a/ui/app_list/views/app_list_folder_view.h b/ui/app_list/views/app_list_folder_view.h
index 939fab6e..7594acf 100644
--- a/ui/app_list/views/app_list_folder_view.h
+++ b/ui/app_list/views/app_list_folder_view.h
@@ -27,7 +27,6 @@
 class AppsGridView;
 class AppListFolderItem;
 class AppListItemView;
-class AppListMainView;
 class AppListModel;
 class FolderHeaderView;
 class PageSwitcher;
@@ -39,7 +38,7 @@
  public:
   AppListFolderView(AppsContainerView* container_view,
                     AppListModel* model,
-                    AppListMainView* app_list_main_view);
+                    ContentsView* contents_view);
   ~AppListFolderView() override;
 
   // An interface for the folder opening and closing animations.
@@ -132,7 +131,7 @@
 
   // Views below are not owned by views hierarchy.
   AppsContainerView* container_view_;
-  AppListMainView* app_list_main_view_;
+  ContentsView* contents_view_;
 
   // The view is used to draw a background with corner radius.
   views::View* background_view_;  // Owned by views hierarchy.
diff --git a/ui/app_list/views/app_list_main_view.cc b/ui/app_list/views/app_list_main_view.cc
index c3c7d7c..944cf27 100644
--- a/ui/app_list/views/app_list_main_view.cc
+++ b/ui/app_list/views/app_list_main_view.cc
@@ -73,7 +73,7 @@
 
 void AppListMainView::AddContentsViews() {
   DCHECK(search_box_view_);
-  contents_view_ = new ContentsView(this, app_list_view_);
+  contents_view_ = new ContentsView(app_list_view_);
   contents_view_->Init(model_);
   AddChildView(contents_view_);
 
@@ -93,7 +93,7 @@
 
 void AppListMainView::ResetForShow() {
   contents_view_->SetActiveState(ash::AppListState::kStateStart);
-  contents_view_->apps_container_view()->ResetForShowApps();
+  contents_view_->GetAppsContainerView()->ResetForShowApps();
   // We clear the search when hiding so when app list appears it is not showing
   // search results.
   search_box_view_->ClearSearch();
@@ -121,7 +121,7 @@
 }
 
 PaginationModel* AppListMainView::GetAppsPaginationModel() {
-  return contents_view_->apps_container_view()
+  return contents_view_->GetAppsContainerView()
       ->apps_grid_view()
       ->pagination_model();
 }
@@ -153,7 +153,7 @@
 }
 
 void AppListMainView::CancelDragInActiveFolder() {
-  contents_view_->apps_container_view()
+  contents_view_->GetAppsContainerView()
       ->app_list_folder_view()
       ->items_grid_view()
       ->EndDrag(true);
diff --git a/ui/app_list/views/app_list_main_view_unittest.cc b/ui/app_list/views/app_list_main_view_unittest.cc
index 5c90a8a4..89cfc730 100644
--- a/ui/app_list/views/app_list_main_view_unittest.cc
+++ b/ui/app_list/views/app_list_main_view_unittest.cc
@@ -176,11 +176,11 @@
   ContentsView* GetContentsView() { return main_view_->contents_view(); }
 
   AppsGridView* RootGridView() {
-    return GetContentsView()->apps_container_view()->apps_grid_view();
+    return GetContentsView()->GetAppsContainerView()->apps_grid_view();
   }
 
   AppListFolderView* FolderView() {
-    return GetContentsView()->apps_container_view()->app_list_folder_view();
+    return GetContentsView()->GetAppsContainerView()->app_list_folder_view();
   }
 
   AppsGridView* FolderGridView() { return FolderView()->items_grid_view(); }
@@ -376,7 +376,7 @@
   // Ensure keyboard selection works on the root grid view after a reparent.
   // This is a regression test for https://crbug.com/466058.
   ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RIGHT, ui::EF_NONE);
-  GetContentsView()->apps_container_view()->OnKeyPressed(key_event);
+  GetContentsView()->GetAppsContainerView()->OnKeyPressed(key_event);
 
   EXPECT_TRUE(RootGridView()->has_selected_view());
   EXPECT_FALSE(FolderGridView()->has_selected_view());
diff --git a/ui/app_list/views/app_list_view.cc b/ui/app_list/views/app_list_view.cc
index 9b76e9f..ad9d0a6 100644
--- a/ui/app_list/views/app_list_view.cc
+++ b/ui/app_list/views/app_list_view.cc
@@ -568,13 +568,6 @@
 }
 
 void AppListView::UpdateDrag(const gfx::Point& location) {
-  if (initial_drag_point_ == gfx::Point()) {
-    // When the app grid view redirects the event to the app list view, we
-    // detect this by seeing that StartDrag was not called. This sets up the
-    // drag.
-    StartDrag(location);
-    return;
-  }
   // Update the widget bounds based on the initial widget bounds and drag delta.
   gfx::Point location_in_screen_coordinates = location;
   ConvertPointToScreen(this, &location_in_screen_coordinates);
@@ -803,7 +796,7 @@
 }
 
 AppsContainerView* AppListView::GetAppsContainerView() {
-  return app_list_main_view_->contents_view()->apps_container_view();
+  return app_list_main_view_->contents_view()->GetAppsContainerView();
 }
 
 AppsGridView* AppListView::GetRootAppsGridView() {
diff --git a/ui/app_list/views/app_list_view_unittest.cc b/ui/app_list/views/app_list_view_unittest.cc
index 2d85247a..2e3caf1 100644
--- a/ui/app_list/views/app_list_view_unittest.cc
+++ b/ui/app_list/views/app_list_view_unittest.cc
@@ -426,14 +426,14 @@
   AppsGridView* apps_grid_view() {
     return main_view()
         ->contents_view()
-        ->apps_container_view()
+        ->GetAppsContainerView()
         ->apps_grid_view();
   }
 
   AppListFolderView* app_list_folder_view() {
     return main_view()
         ->contents_view()
-        ->apps_container_view()
+        ->GetAppsContainerView()
         ->app_list_folder_view();
   }
 
@@ -599,7 +599,7 @@
   SetAppListState(AppListViewState::FULLSCREEN_ALL_APPS);
   folder_item_view()->RequestFocus();
   SimulateKeyPress(ui::VKEY_RETURN, false);
-  EXPECT_TRUE(contents_view()->apps_container_view()->IsInFolderView());
+  EXPECT_TRUE(contents_view()->GetAppsContainerView()->IsInFolderView());
 
   std::vector<views::View*> forward_view_list;
   forward_view_list.push_back(search_box_view()->search_box());
@@ -746,7 +746,7 @@
   SetAppListState(AppListViewState::FULLSCREEN_ALL_APPS);
   folder_item_view()->RequestFocus();
   SimulateKeyPress(ui::VKEY_RETURN, false);
-  EXPECT_TRUE(contents_view()->apps_container_view()->IsInFolderView());
+  EXPECT_TRUE(contents_view()->GetAppsContainerView()->IsInFolderView());
 
   std::vector<views::View*> forward_view_list;
   forward_view_list.push_back(search_box_view()->search_box());
@@ -784,7 +784,7 @@
   SetAppListState(AppListViewState::FULLSCREEN_ALL_APPS);
   folder_item_view()->RequestFocus();
   SimulateKeyPress(ui::VKEY_RETURN, false);
-  EXPECT_TRUE(contents_view()->apps_container_view()->IsInFolderView());
+  EXPECT_TRUE(contents_view()->GetAppsContainerView()->IsInFolderView());
 
   // Select the second page.
   app_list_folder_view()->items_grid_view()->pagination_model()->SelectPage(
@@ -850,7 +850,7 @@
   // Move focus to first suggestion app, then open the folder.
   folder_item_view()->RequestFocus();
   SimulateKeyPress(ui::VKEY_RETURN, false);
-  EXPECT_TRUE(contents_view()->apps_container_view()->IsInFolderView());
+  EXPECT_TRUE(contents_view()->GetAppsContainerView()->IsInFolderView());
   EXPECT_EQ(search_box_view()->search_box(), focused_view());
 
   // Move focus to the first app, then transition to PEEKING state.
@@ -1516,19 +1516,19 @@
   Show();
   AppsGridViewTestApi test_api(view_->app_list_main_view()
                                    ->contents_view()
-                                   ->apps_container_view()
+                                   ->GetAppsContainerView()
                                    ->apps_grid_view());
   test_api.PressItemAt(0);
   EXPECT_TRUE(view_->app_list_main_view()
                   ->contents_view()
-                  ->apps_container_view()
+                  ->GetAppsContainerView()
                   ->IsInFolderView());
 
   view_->SetState(AppListViewState::PEEKING);
 
   EXPECT_FALSE(view_->app_list_main_view()
                    ->contents_view()
-                   ->apps_container_view()
+                   ->GetAppsContainerView()
                    ->IsInFolderView());
 }
 
@@ -1542,7 +1542,7 @@
   view_->SetState(AppListViewState::FULLSCREEN_ALL_APPS);
   EXPECT_EQ(AppListViewState::FULLSCREEN_ALL_APPS, view_->app_list_state());
   ContentsView* contents_view = view_->app_list_main_view()->contents_view();
-  AppsContainerView* container_view = contents_view->apps_container_view();
+  AppsContainerView* container_view = contents_view->GetAppsContainerView();
   const gfx::Rect grid_view_bounds =
       container_view->apps_grid_view()->GetBoundsInScreen();
   gfx::Point target_point = grid_view_bounds.origin();
diff --git a/ui/app_list/views/apps_container_view.cc b/ui/app_list/views/apps_container_view.cc
index c23bfa7..4375f474 100644
--- a/ui/app_list/views/apps_container_view.cc
+++ b/ui/app_list/views/apps_container_view.cc
@@ -28,19 +28,10 @@
 
 namespace app_list {
 
-// Initial search box top padding in shelf mode.
-constexpr int kSearchBoxInitalTopPadding = 12;
-
-// Top padding of search box in peeking state.
-constexpr int kSearchBoxPeekingTopPadding = 24;
-
-// Minimum top padding of search box in fullscreen state.
-constexpr int kSearchBoxMinimumTopPadding = 24;
-
-AppsContainerView::AppsContainerView(AppListMainView* app_list_main_view,
-                                     AppListModel* model) {
-  apps_grid_view_ =
-      new AppsGridView(app_list_main_view->contents_view(), nullptr);
+AppsContainerView::AppsContainerView(ContentsView* contents_view,
+                                     AppListModel* model)
+    : contents_view_(contents_view) {
+  apps_grid_view_ = new AppsGridView(contents_view_, nullptr);
   apps_grid_view_->SetLayout(kPreferredCols, kPreferredRows);
   AddChildView(apps_grid_view_);
 
@@ -49,8 +40,7 @@
                                     true /* vertical */);
   AddChildView(page_switcher_);
 
-  app_list_folder_view_ =
-      new AppListFolderView(this, model, app_list_main_view);
+  app_list_folder_view_ = new AppListFolderView(this, model, contents_view_);
   // The folder view is initially hidden.
   app_list_folder_view_->SetVisible(false);
   folder_background_view_ = new FolderBackgroundView(app_list_folder_view_);
@@ -80,7 +70,7 @@
 
   // Disable all the items behind the folder so that they will not be reached
   // during focus traversal.
-  contents_view()->GetSearchBoxView()->search_box()->RequestFocus();
+  contents_view_->GetSearchBoxView()->search_box()->RequestFocus();
   apps_grid_view_->DisableFocusForShowingActiveFolder(true);
 }
 
@@ -133,7 +123,7 @@
 
   // Updates the opacity of page switcher buttons. The same rule as all apps in
   // AppsGridView.
-  AppListView* app_list_view = contents_view()->app_list_view();
+  AppListView* app_list_view = contents_view_->app_list_view();
   bool should_restore_opacity =
       !app_list_view->is_in_drag() &&
       (app_list_view->app_list_state() != AppListViewState::CLOSED);
@@ -201,11 +191,6 @@
   return "AppsContainerView";
 }
 
-void AppsContainerView::OnWillBeShown() {
-  apps_grid_view()->ClearAnySelectedView();
-  app_list_folder_view()->items_grid_view()->ClearAnySelectedView();
-}
-
 void AppsContainerView::OnWillBeHidden() {
   if (show_state_ == SHOW_APPS || show_state_ == SHOW_ITEM_REPARENT)
     apps_grid_view_->EndDrag(true);
@@ -213,96 +198,6 @@
     app_list_folder_view_->CloseFolderPage();
 }
 
-gfx::Rect AppsContainerView::GetSearchBoxBounds() const {
-  return GetSearchBoxBoundsForState(contents_view()->GetActiveState());
-}
-
-gfx::Rect AppsContainerView::GetSearchBoxBoundsForState(
-    ash::AppListState state) const {
-  gfx::Rect search_box_bounds(contents_view()->GetDefaultSearchBoxBounds());
-  bool is_in_drag = false;
-  if (contents_view()->app_list_view())
-    is_in_drag = contents_view()->app_list_view()->is_in_drag();
-  if (is_in_drag) {
-    search_box_bounds.set_y(GetSearchBoxTopPaddingDuringDragging());
-  } else {
-    if (state == ash::AppListState::kStateStart)
-      search_box_bounds.set_y(kSearchBoxPeekingTopPadding);
-    else
-      search_box_bounds.set_y(GetSearchBoxFinalTopPadding());
-  }
-
-  return search_box_bounds;
-}
-
-gfx::Rect AppsContainerView::GetPageBoundsForState(
-    ash::AppListState state) const {
-  gfx::Rect onscreen_bounds = GetDefaultContentsBounds();
-
-  // Both STATE_START and STATE_APPS are AppsContainerView page.
-  if (state == ash::AppListState::kStateApps ||
-      state == ash::AppListState::kStateStart) {
-    int y = GetSearchBoxBoundsForState(state).bottom();
-    if (state == ash::AppListState::kStateStart)
-      y -= (kSearchBoxBottomPadding - kSearchBoxPeekingBottomPadding);
-    onscreen_bounds.set_y(y);
-    return onscreen_bounds;
-  }
-
-  return GetBelowContentsOffscreenBounds(onscreen_bounds.size());
-}
-
-gfx::Rect AppsContainerView::GetPageBoundsDuringDragging(
-    ash::AppListState state) const {
-  float app_list_y_position_in_screen =
-      contents_view()->app_list_view()->app_list_y_position_in_screen();
-  float drag_amount =
-      std::max(0.f, contents_view()->app_list_view()->GetScreenBottom() -
-                        kShelfSize - app_list_y_position_in_screen);
-
-  float y = 0;
-  float peeking_final_y =
-      kSearchBoxPeekingTopPadding + search_box::kSearchBoxPreferredHeight +
-      kSearchBoxPeekingBottomPadding - kSearchBoxBottomPadding;
-  if (drag_amount <= (kPeekingAppListHeight - kShelfSize)) {
-    // App list is dragged from collapsed to peeking, which moved up at most
-    // |kPeekingAppListHeight - kShelfSize| (272px). The top padding of apps
-    // container view changes from |-kSearchBoxFullscreenBottomPadding| to
-    // |kSearchBoxPeekingTopPadding + kSearchBoxPreferredHeight +
-    // kSearchBoxPeekingBottomPadding - kSearchBoxFullscreenBottomPadding|.
-    y = std::ceil(((peeking_final_y + kSearchBoxBottomPadding) * drag_amount) /
-                      (kPeekingAppListHeight - kShelfSize) -
-                  kSearchBoxBottomPadding);
-  } else {
-    // App list is dragged from peeking to fullscreen, which moved up at most
-    // |peeking_to_fullscreen_height|. The top padding of apps container view
-    // changes from |peeking_final_y| to |final_y|.
-    float final_y =
-        GetSearchBoxFinalTopPadding() + search_box::kSearchBoxPreferredHeight;
-    float peeking_to_fullscreen_height =
-        contents_view()->GetDisplayHeight() - kPeekingAppListHeight;
-    y = std::ceil((final_y - peeking_final_y) *
-                      (drag_amount - (kPeekingAppListHeight - kShelfSize)) /
-                      peeking_to_fullscreen_height +
-                  peeking_final_y);
-    y = std::max(std::min(final_y, y), peeking_final_y);
-  }
-
-  gfx::Rect onscreen_bounds = GetPageBoundsForState(state);
-  // Both STATE_START and STATE_APPS are AppsContainerView page.
-  if (state == ash::AppListState::kStateApps ||
-      state == ash::AppListState::kStateStart)
-    onscreen_bounds.set_y(y);
-
-  return onscreen_bounds;
-}
-
-views::View* AppsContainerView::GetSelectedView() const {
-  return IsInFolderView()
-             ? app_list_folder_view_->items_grid_view()->GetSelectedView()
-             : apps_grid_view_->GetSelectedView();
-}
-
 views::View* AppsContainerView::GetFirstFocusableView() {
   if (IsInFolderView()) {
     // The pagination inside a folder is set horizontally, so focus should be
@@ -315,11 +210,6 @@
       this, GetWidget(), false /* reverse */, false /* dont_loop */);
 }
 
-views::View* AppsContainerView::GetLastFocusableView() {
-  return GetFocusManager()->GetNextFocusableView(
-      this, GetWidget(), true /* reverse */, false /* dont_loop */);
-}
-
 void AppsContainerView::SetShowState(ShowState show_state,
                                      bool show_apps_with_animation) {
   if (show_state_ == show_state)
@@ -353,50 +243,4 @@
   }
 }
 
-int AppsContainerView::GetSearchBoxFinalTopPadding() const {
-  gfx::Rect search_box_bounds(contents_view()->GetDefaultSearchBoxBounds());
-  const int total_height =
-      GetDefaultContentsBounds().bottom() - search_box_bounds.y();
-
-  // Makes search box and content vertically centered in contents_view.
-  int y = std::max(search_box_bounds.y(),
-                   (contents_view()->GetDisplayHeight() - total_height) / 2);
-
-  // Top padding of the searchbox should not be smaller than
-  // |kSearchBoxMinimumTopPadding|
-  return std::max(y, kSearchBoxMinimumTopPadding);
-}
-
-int AppsContainerView::GetSearchBoxTopPaddingDuringDragging() const {
-  float searchbox_final_y = GetSearchBoxFinalTopPadding();
-  float peeking_to_fullscreen_height =
-      contents_view()->GetDisplayHeight() - kPeekingAppListHeight;
-  float drag_amount = std::max(
-      0, contents_view()->app_list_view()->GetScreenBottom() - kShelfSize -
-             contents_view()->app_list_view()->app_list_y_position_in_screen());
-
-  if (drag_amount <= (kPeekingAppListHeight - kShelfSize)) {
-    // App list is dragged from collapsed to peeking, which moved up at most
-    // |kPeekingAppListHeight - kShelfSize| (272px). The top padding of search
-    // box changes from |kSearchBoxInitalTopPadding| to
-    // |kSearchBoxPeekingTopPadding|,
-    return std::ceil(
-        (kSearchBoxPeekingTopPadding - kSearchBoxInitalTopPadding) +
-        ((kSearchBoxPeekingTopPadding - kSearchBoxInitalTopPadding) *
-         drag_amount) /
-            (kPeekingAppListHeight - kShelfSize));
-  } else {
-    // App list is dragged from peeking to fullscreen, which moved up at most
-    // |peeking_to_fullscreen_height|. The top padding of search box changes
-    // from |kSearchBoxPeekingTopPadding| to |searchbox_final_y|.
-    int y = (kSearchBoxPeekingTopPadding +
-             std::ceil((searchbox_final_y - kSearchBoxPeekingTopPadding) *
-                       (drag_amount - (kPeekingAppListHeight - kShelfSize)) /
-                       peeking_to_fullscreen_height));
-    y = std::max(kSearchBoxPeekingTopPadding,
-                 std::min<int>(searchbox_final_y, y));
-    return y;
-  }
-}
-
 }  // namespace app_list
diff --git a/ui/app_list/views/apps_container_view.h b/ui/app_list/views/apps_container_view.h
index a9f5624..a101a511cb 100644
--- a/ui/app_list/views/apps_container_view.h
+++ b/ui/app_list/views/apps_container_view.h
@@ -11,11 +11,7 @@
 
 #include "ash/app_list/model/app_list_folder_item.h"
 #include "base/macros.h"
-#include "ui/app_list/views/app_list_page.h"
-
-namespace gfx {
-class Rect;
-}
+#include "ui/app_list/views/horizontal_page.h"
 
 namespace app_list {
 
@@ -23,17 +19,17 @@
 class ApplicationDragAndDropHost;
 class AppListFolderItem;
 class AppListFolderView;
-class AppListMainView;
 class AppListModel;
+class ContentsView;
 class FolderBackgroundView;
 class PageSwitcher;
 
 // AppsContainerView contains a root level AppsGridView to render the root level
 // app items, and a AppListFolderView to render the app items inside the
 // active folder. Only one if them is visible to user at any time.
-class APP_LIST_EXPORT AppsContainerView : public AppListPage {
+class APP_LIST_EXPORT AppsContainerView : public HorizontalPage {
  public:
-  AppsContainerView(AppListMainView* app_list_main_view, AppListModel* model);
+  AppsContainerView(ContentsView* contents_view, AppListModel* model);
   ~AppsContainerView() override;
 
   // Shows the active folder content specified by |folder_item|.
@@ -78,16 +74,9 @@
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   const char* GetClassName() const override;
 
-  // AppListPage overrides:
-  void OnWillBeShown() override;
+  // HorizontalPage overrides:
   void OnWillBeHidden() override;
-  gfx::Rect GetSearchBoxBounds() const override;
-  gfx::Rect GetSearchBoxBoundsForState(ash::AppListState state) const override;
-  gfx::Rect GetPageBoundsForState(ash::AppListState state) const override;
-  gfx::Rect GetPageBoundsDuringDragging(ash::AppListState state) const override;
-  views::View* GetSelectedView() const override;
   views::View* GetFirstFocusableView() override;
-  views::View* GetLastFocusableView() override;
 
   AppsGridView* apps_grid_view() { return apps_grid_view_; }
   FolderBackgroundView* folder_background_view() {
@@ -105,11 +94,7 @@
 
   void SetShowState(ShowState show_state, bool show_apps_with_animation);
 
-  // Gets the final top padding of search box.
-  int GetSearchBoxFinalTopPadding() const;
-
-  // Gets the top padding of search box during dragging.
-  int GetSearchBoxTopPaddingDuringDragging() const;
+  ContentsView* contents_view_;  // Not owned.
 
   // The views below are owned by views hierarchy.
   AppsGridView* apps_grid_view_ = nullptr;
diff --git a/ui/app_list/views/apps_grid_view.cc b/ui/app_list/views/apps_grid_view.cc
index 2f35065..8554dd23 100644
--- a/ui/app_list/views/apps_grid_view.cc
+++ b/ui/app_list/views/apps_grid_view.cc
@@ -635,7 +635,7 @@
       // An EndDrag can be received during a reparent via a model change. This
       // is always a cancel and needs to be forwarded to the folder.
       DCHECK(cancel);
-      contents_view_->app_list_main_view()->CancelDragInActiveFolder();
+      contents_view_->GetAppListMainView()->CancelDragInActiveFolder();
       return;
     }
 
@@ -720,7 +720,7 @@
   // folder's grid view.
   AppListItemView* view = new AppListItemView(
       this, original_drag_view->item(),
-      contents_view_->app_list_main_view()->view_delegate());
+      contents_view_->GetAppListMainView()->view_delegate());
   AddChildView(view);
   drag_view_ = view;
   drag_view_->SetPaintToLayer();
@@ -966,7 +966,7 @@
 void AppsGridView::UpdateSuggestions() {
   if (!suggestions_container_)
     return;
-  suggestions_container_->SetResults(contents_view_->app_list_main_view()
+  suggestions_container_->SetResults(contents_view_->GetAppListMainView()
                                          ->view_delegate()
                                          ->GetSearchModel()
                                          ->results());
@@ -1029,7 +1029,7 @@
   DCHECK_LE(index, item_list_->item_count());
   AppListItemView* view = new AppListItemView(
       this, item_list_->item_at(index),
-      contents_view_->app_list_main_view()->view_delegate());
+      contents_view_->GetAppListMainView()->view_delegate());
   view->SetPaintToLayer();
   view->layer()->SetFillsBoundsOpaquely(false);
   return view;
@@ -1554,7 +1554,7 @@
     if (arrow_up) {
       contents_view_->GetSearchBoxView()->search_box()->RequestFocus();
     } else {
-      contents_view_->apps_container_view()
+      contents_view_->GetAppsContainerView()
           ->app_list_folder_view()
           ->folder_header_view()
           ->SetTextFocus();
@@ -2225,7 +2225,7 @@
     else
       activated_folder_item_view_ = nullptr;
   }
-  contents_view_->app_list_main_view()->ActivateApp(pressed_item_view->item(),
+  contents_view_->GetAppListMainView()->ActivateApp(pressed_item_view->item(),
                                                     event.flags());
 }
 
diff --git a/ui/app_list/views/apps_grid_view_unittest.cc b/ui/app_list/views/apps_grid_view_unittest.cc
index 9339ed9d..ac3625a 100644
--- a/ui/app_list/views/apps_grid_view_unittest.cc
+++ b/ui/app_list/views/apps_grid_view_unittest.cc
@@ -132,7 +132,7 @@
     params.parent = parent;
     app_list_view_->Initialize(params);
     contents_view_ = app_list_view_->app_list_main_view()->contents_view();
-    apps_grid_view_ = contents_view_->apps_container_view()->apps_grid_view();
+    apps_grid_view_ = contents_view_->GetAppsContainerView()->apps_grid_view();
     app_list_view_->GetWidget()->Show();
 
     model_ = delegate_->GetTestModel();
@@ -181,7 +181,7 @@
   }
 
   AppListFolderView* app_list_folder_view() const {
-    return contents_view_->apps_container_view()->app_list_folder_view();
+    return contents_view_->GetAppsContainerView()->app_list_folder_view();
   }
 
   // Points are in |apps_grid_view_|'s coordinates, and fixed for RTL.
@@ -750,7 +750,7 @@
   model_->PopulateApps(5);
 
   // Select the first suggested app and launch it.
-  contents_view_->app_list_main_view()->ActivateApp(GetItemViewAt(0)->item(),
+  contents_view_->GetAppListMainView()->ActivateApp(GetItemViewAt(0)->item(),
                                                     0);
 
   // Test that histograms recorded that a regular app launched.
@@ -862,7 +862,7 @@
 
 TEST_F(AppsGridViewTest, CloseFolderByClickingBackground) {
   AppsContainerView* apps_container_view =
-      contents_view_->apps_container_view();
+      contents_view_->GetAppsContainerView();
 
   const size_t kTotalItems = kMaxFolderItemsPerPage;
   model_->CreateAndPopulateFolderWithApps(kTotalItems);
diff --git a/ui/app_list/views/contents_view.cc b/ui/app_list/views/contents_view.cc
index c532c165..5145cdd 100644
--- a/ui/app_list/views/contents_view.cc
+++ b/ui/app_list/views/contents_view.cc
@@ -17,6 +17,7 @@
 #include "ui/app_list/views/app_list_view.h"
 #include "ui/app_list/views/apps_container_view.h"
 #include "ui/app_list/views/apps_grid_view.h"
+#include "ui/app_list/views/horizontal_page_container.h"
 #include "ui/app_list/views/search_box_view.h"
 #include "ui/app_list/views/search_result_answer_card_view.h"
 #include "ui/app_list/views/search_result_list_view.h"
@@ -46,9 +47,8 @@
 
 }  // namespace
 
-ContentsView::ContentsView(AppListMainView* app_list_main_view,
-                           AppListView* app_list_view)
-    : app_list_main_view_(app_list_main_view), app_list_view_(app_list_view) {
+ContentsView::ContentsView(AppListView* app_list_view)
+    : app_list_view_(app_list_view) {
   pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
                                            kOverscrollPageTransitionDurationMs);
   pagination_model_.AddObserver(this);
@@ -62,13 +62,13 @@
   DCHECK(model);
   model_ = model;
 
-  AppListViewDelegate* view_delegate = app_list_main_view_->view_delegate();
+  AppListViewDelegate* view_delegate = GetAppListMainView()->view_delegate();
 
-  apps_container_view_ = new AppsContainerView(app_list_main_view_, model);
+  horizontal_page_container_ = new HorizontalPageContainer(this, model);
 
-  // Add |apps_container_view_| as STATE_START corresponding page for
+  // Add |horizontal_page_container_| as STATE_START corresponding page for
   // fullscreen app list.
-  AddLauncherPage(apps_container_view_, ash::AppListState::kStateStart);
+  AddLauncherPage(horizontal_page_container_, ash::AppListState::kStateStart);
 
   // Search results UI.
   search_results_page_view_ = new SearchResultPageView();
@@ -91,14 +91,14 @@
       results, search_result_tile_item_list_view_);
 
   search_result_list_view_ =
-      new SearchResultListView(app_list_main_view_, view_delegate);
+      new SearchResultListView(GetAppListMainView(), view_delegate);
   search_results_page_view_->AddSearchResultContainerView(
       results, search_result_list_view_);
 
   AddLauncherPage(search_results_page_view_,
                   ash::AppListState::kStateSearchResults);
 
-  AddLauncherPage(apps_container_view_, ash::AppListState::kStateApps);
+  AddLauncherPage(horizontal_page_container_, ash::AppListState::kStateApps);
 
   int initial_page_index = GetPageIndexForState(ash::AppListState::kStateStart);
   DCHECK_GE(initial_page_index, 0);
@@ -118,19 +118,21 @@
 }
 
 void ContentsView::CancelDrag() {
-  if (apps_container_view_->apps_grid_view()->has_dragged_view())
-    apps_container_view_->apps_grid_view()->EndDrag(true);
-  if (apps_container_view_->app_list_folder_view()
+  if (GetAppsContainerView()->apps_grid_view()->has_dragged_view())
+    GetAppsContainerView()->apps_grid_view()->EndDrag(true);
+  if (GetAppsContainerView()
+          ->app_list_folder_view()
           ->items_grid_view()
           ->has_dragged_view()) {
-    apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
+    GetAppsContainerView()->app_list_folder_view()->items_grid_view()->EndDrag(
         true);
   }
 }
 
 void ContentsView::SetDragAndDropHostOfCurrentAppList(
     ApplicationDragAndDropHost* drag_and_drop_host) {
-  apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
+  GetAppsContainerView()->SetDragAndDropHostOfCurrentAppList(
+      drag_and_drop_host);
 }
 
 void ContentsView::SetActiveState(ash::AppListState state) {
@@ -182,6 +184,10 @@
   return pagination_model_.total_pages();
 }
 
+AppsContainerView* ContentsView::GetAppsContainerView() {
+  return horizontal_page_container_->apps_container_view();
+}
+
 void ContentsView::SetActiveStateInternal(int page_index,
                                           bool show_search_results,
                                           bool animate) {
@@ -211,7 +217,7 @@
 
   app_list_pages_[GetActivePageIndex()]->OnWillBeShown();
 
-  app_list_main_view_->model()->SetState(state);
+  GetAppListMainView()->model()->SetState(state);
 }
 
 void ContentsView::ShowSearchResults(bool show) {
@@ -284,11 +290,11 @@
 }
 
 PaginationModel* ContentsView::GetAppsPaginationModel() {
-  return apps_container_view_->apps_grid_view()->pagination_model();
+  return GetAppsContainerView()->apps_grid_view()->pagination_model();
 }
 
 void ContentsView::ShowFolderContent(AppListFolderItem* item) {
-  apps_container_view_->ShowActiveFolder(item);
+  GetAppsContainerView()->ShowActiveFolder(item);
 }
 
 AppListPage* ContentsView::GetPageView(int index) const {
@@ -297,7 +303,11 @@
 }
 
 SearchBoxView* ContentsView::GetSearchBoxView() const {
-  return app_list_main_view_->search_box_view();
+  return GetAppListMainView()->search_box_view();
+}
+
+AppListMainView* ContentsView::GetAppListMainView() const {
+  return app_list_view_->app_list_main_view();
 }
 
 int ContentsView::AddLauncherPage(AppListPage* view) {
@@ -359,8 +369,8 @@
       // Close the app list when Back() is called from the start page.
       return false;
     case ash::AppListState::kStateApps:
-      if (apps_container_view_->IsInFolderView()) {
-        apps_container_view_->app_list_folder_view()->CloseFolderPage();
+      if (GetAppsContainerView()->IsInFolderView()) {
+        GetAppsContainerView()->app_list_folder_view()->CloseFolderPage();
       } else {
         app_list_view_->Dismiss();
       }
@@ -379,7 +389,7 @@
 }
 
 gfx::Size ContentsView::GetDefaultContentsSize() const {
-  return apps_container_view_->GetPreferredSize();
+  return horizontal_page_container_->GetPreferredSize();
 }
 
 gfx::Size ContentsView::CalculatePreferredSize() const {
diff --git a/ui/app_list/views/contents_view.h b/ui/app_list/views/contents_view.h
index dfa8082..a2d1d72d2 100644
--- a/ui/app_list/views/contents_view.h
+++ b/ui/app_list/views/contents_view.h
@@ -33,6 +33,7 @@
 class AppListMainView;
 class AppsContainerView;
 class AppsGridView;
+class HorizontalPageContainer;
 class PaginationModel;
 class SearchBoxView;
 class SearchResultAnswerCardView;
@@ -48,7 +49,7 @@
 class APP_LIST_EXPORT ContentsView : public views::View,
                                      public PaginationModelObserver {
  public:
-  ContentsView(AppListMainView* app_list_main_view, AppListView* app_list_view);
+  explicit ContentsView(AppListView* app_list_view);
   ~ContentsView() override;
 
   // Initialize the pages of the launcher. Should be called after
@@ -94,9 +95,8 @@
 
   int NumLauncherPages() const;
 
-  AppsContainerView* apps_container_view() const {
-    return apps_container_view_;
-  }
+  AppsContainerView* GetAppsContainerView();
+
   SearchResultPageView* search_results_page_view() const {
     return search_results_page_view_;
   }
@@ -114,7 +114,7 @@
 
   SearchBoxView* GetSearchBoxView() const;
 
-  AppListMainView* app_list_main_view() const { return app_list_main_view_; }
+  AppListMainView* GetAppListMainView() const;
 
   AppListView* app_list_view() const { return app_list_view_; }
 
@@ -203,7 +203,7 @@
   AppListModel* model_ = nullptr;
 
   // Sub-views of the ContentsView. All owned by the views hierarchy.
-  AppsContainerView* apps_container_view_ = nullptr;
+  HorizontalPageContainer* horizontal_page_container_ = nullptr;
   SearchResultPageView* search_results_page_view_ = nullptr;
   SearchResultAnswerCardView* search_result_answer_card_view_ = nullptr;
   SearchResultTileItemListView* search_result_tile_item_list_view_ = nullptr;
@@ -212,9 +212,6 @@
   // The child page views. Owned by the views hierarchy.
   std::vector<AppListPage*> app_list_pages_;
 
-  // Parent view. Owned by the views hierarchy.
-  AppListMainView* app_list_main_view_;
-
   // Owned by the views hierarchy.
   AppListView* const app_list_view_;
 
diff --git a/ui/app_list/views/horizontal_page.cc b/ui/app_list/views/horizontal_page.cc
new file mode 100644
index 0000000..16de048
--- /dev/null
+++ b/ui/app_list/views/horizontal_page.cc
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/views/horizontal_page.h"
+
+#include "ui/views/focus/focus_manager.h"
+
+namespace app_list {
+
+void HorizontalPage::OnWillBeHidden() {}
+
+views::View* HorizontalPage::GetFirstFocusableView() {
+  return GetFocusManager()->GetNextFocusableView(
+      this, GetWidget(), false /* reverse */, false /* dont_loop */);
+}
+
+views::View* HorizontalPage::GetLastFocusableView() {
+  return GetFocusManager()->GetNextFocusableView(
+      this, GetWidget(), true /* reverse */, false /* dont_loop */);
+}
+
+HorizontalPage::HorizontalPage() = default;
+
+HorizontalPage::~HorizontalPage() = default;
+
+}  // namespace app_list
diff --git a/ui/app_list/views/horizontal_page.h b/ui/app_list/views/horizontal_page.h
new file mode 100644
index 0000000..c107179
--- /dev/null
+++ b/ui/app_list/views/horizontal_page.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_H_
+#define UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_H_
+
+#include "ash/app_list/model/app_list_model.h"
+#include "base/macros.h"
+#include "ui/app_list/app_list_export.h"
+#include "ui/views/view.h"
+
+namespace app_list {
+
+// HorizontalPage is laid out horizontally in HorizontalPageContainer and its
+// visibility is controlled by horizontal gesture scrolling.
+class APP_LIST_EXPORT HorizontalPage : public views::View {
+ public:
+  // Triggered when the page is about to be hidden.
+  virtual void OnWillBeHidden();
+
+  // Gets the first and last focusable view in this page, this will be used in
+  // focus traversal.
+  virtual views::View* GetFirstFocusableView();
+  virtual views::View* GetLastFocusableView();
+
+ protected:
+  HorizontalPage();
+  ~HorizontalPage() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HorizontalPage);
+};
+
+}  // namespace app_list
+
+#endif  // UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_H_
diff --git a/ui/app_list/views/horizontal_page_container.cc b/ui/app_list/views/horizontal_page_container.cc
new file mode 100644
index 0000000..959ea19
--- /dev/null
+++ b/ui/app_list/views/horizontal_page_container.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/views/horizontal_page_container.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "ui/app_list/app_list_constants.h"
+#include "ui/app_list/pagination_controller.h"
+#include "ui/app_list/views/app_list_view.h"
+#include "ui/app_list/views/apps_container_view.h"
+#include "ui/app_list/views/contents_view.h"
+#include "ui/chromeos/search_box/search_box_constants.h"
+#include "ui/views/controls/label.h"
+
+namespace app_list {
+
+// Initial search box top padding in shelf mode.
+constexpr int kSearchBoxInitalTopPadding = 12;
+
+// Top padding of search box in peeking state.
+constexpr int kSearchBoxPeekingTopPadding = 24;
+
+// Minimum top padding of search box in fullscreen state.
+constexpr int kSearchBoxMinimumTopPadding = 24;
+
+HorizontalPageContainer::HorizontalPageContainer(ContentsView* contents_view,
+                                                 AppListModel* model)
+    : contents_view_(contents_view) {
+  pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
+                                           kOverscrollPageTransitionDurationMs);
+  pagination_model_.AddObserver(this);
+  pagination_controller_.reset(new PaginationController(
+      &pagination_model_, PaginationController::SCROLL_AXIS_HORIZONTAL));
+
+  apps_container_view_ = new AppsContainerView(contents_view_, model);
+
+  // Add horizontal pages.
+  AddHorizontalPage(apps_container_view_);
+  pagination_model_.SetTotalPages(horizontal_pages_.size());
+
+  // By default select apps container page.
+  pagination_model_.SelectPage(GetIndexForPage(apps_container_view_), false);
+}
+
+HorizontalPageContainer::~HorizontalPageContainer() {
+  pagination_model_.RemoveObserver(this);
+}
+
+gfx::Size HorizontalPageContainer::CalculatePreferredSize() const {
+  return apps_container_view_->GetPreferredSize();
+}
+
+void HorizontalPageContainer::Layout() {
+  gfx::Rect content_bounds(GetContentsBounds());
+  for (size_t i = 0; i < horizontal_pages_.size(); ++i) {
+    gfx::Rect page_bounds(content_bounds);
+    page_bounds.ClampToCenteredSize(horizontal_pages_[i]->GetPreferredSize());
+    page_bounds.Offset(GetOffsetForPageIndex(i));
+    horizontal_pages_[i]->SetBoundsRect(page_bounds);
+  }
+}
+
+void HorizontalPageContainer::OnGestureEvent(ui::GestureEvent* event) {
+  if (pagination_controller_->OnGestureEvent(*event, GetContentsBounds()))
+    event->SetHandled();
+}
+
+void HorizontalPageContainer::OnWillBeHidden() {
+  GetSelectedPage()->OnWillBeHidden();
+}
+
+gfx::Rect HorizontalPageContainer::GetSearchBoxBounds() const {
+  return GetSearchBoxBoundsForState(contents_view_->GetActiveState());
+}
+
+gfx::Rect HorizontalPageContainer::GetSearchBoxBoundsForState(
+    ash::AppListState state) const {
+  gfx::Rect search_box_bounds(contents_view_->GetDefaultSearchBoxBounds());
+  bool is_in_drag = false;
+  if (contents_view_->app_list_view())
+    is_in_drag = contents_view_->app_list_view()->is_in_drag();
+  if (is_in_drag) {
+    search_box_bounds.set_y(GetSearchBoxTopPaddingDuringDragging());
+  } else {
+    if (state == ash::AppListState::kStateStart)
+      search_box_bounds.set_y(kSearchBoxPeekingTopPadding);
+    else
+      search_box_bounds.set_y(GetSearchBoxFinalTopPadding());
+  }
+
+  return search_box_bounds;
+}
+
+gfx::Rect HorizontalPageContainer::GetPageBoundsForState(
+    ash::AppListState state) const {
+  gfx::Rect onscreen_bounds = GetDefaultContentsBounds();
+
+  // Both STATE_START and STATE_APPS are AppsContainerView page.
+  if (state == ash::AppListState::kStateApps ||
+      state == ash::AppListState::kStateStart) {
+    int y = GetSearchBoxBoundsForState(state).bottom();
+    if (state == ash::AppListState::kStateStart)
+      y -= (kSearchBoxBottomPadding - kSearchBoxPeekingBottomPadding);
+    onscreen_bounds.set_y(y);
+    return onscreen_bounds;
+  }
+
+  return GetBelowContentsOffscreenBounds(onscreen_bounds.size());
+}
+
+gfx::Rect HorizontalPageContainer::GetPageBoundsDuringDragging(
+    ash::AppListState state) const {
+  float app_list_y_position_in_screen =
+      contents_view_->app_list_view()->app_list_y_position_in_screen();
+  float drag_amount =
+      std::max(0.f, contents_view_->app_list_view()->GetScreenBottom() -
+                        kShelfSize - app_list_y_position_in_screen);
+
+  float y = 0;
+  float peeking_final_y =
+      kSearchBoxPeekingTopPadding + search_box::kSearchBoxPreferredHeight +
+      kSearchBoxPeekingBottomPadding - kSearchBoxBottomPadding;
+  if (drag_amount <= (kPeekingAppListHeight - kShelfSize)) {
+    // App list is dragged from collapsed to peeking, which moved up at most
+    // |kPeekingAppListHeight - kShelfSize| (272px). The top padding of apps
+    // container view changes from |-kSearchBoxFullscreenBottomPadding| to
+    // |kSearchBoxPeekingTopPadding + kSearchBoxPreferredHeight +
+    // kSearchBoxPeekingBottomPadding - kSearchBoxFullscreenBottomPadding|.
+    y = std::ceil(((peeking_final_y + kSearchBoxBottomPadding) * drag_amount) /
+                      (kPeekingAppListHeight - kShelfSize) -
+                  kSearchBoxBottomPadding);
+  } else {
+    // App list is dragged from peeking to fullscreen, which moved up at most
+    // |peeking_to_fullscreen_height|. The top padding of apps container view
+    // changes from |peeking_final_y| to |final_y|.
+    float final_y =
+        GetSearchBoxFinalTopPadding() + search_box::kSearchBoxPreferredHeight;
+    float peeking_to_fullscreen_height =
+        contents_view_->GetDisplayHeight() - kPeekingAppListHeight;
+    y = std::ceil((final_y - peeking_final_y) *
+                      (drag_amount - (kPeekingAppListHeight - kShelfSize)) /
+                      peeking_to_fullscreen_height +
+                  peeking_final_y);
+    y = std::max(std::min(final_y, y), peeking_final_y);
+  }
+
+  gfx::Rect onscreen_bounds = GetPageBoundsForState(state);
+  // Both STATE_START and STATE_APPS are AppsContainerView page.
+  if (state == ash::AppListState::kStateApps ||
+      state == ash::AppListState::kStateStart)
+    onscreen_bounds.set_y(y);
+
+  return onscreen_bounds;
+}
+
+views::View* HorizontalPageContainer::GetFirstFocusableView() {
+  return GetSelectedPage()->GetFirstFocusableView();
+}
+
+views::View* HorizontalPageContainer::GetLastFocusableView() {
+  return GetSelectedPage()->GetLastFocusableView();
+}
+
+void HorizontalPageContainer::TotalPagesChanged() {}
+
+void HorizontalPageContainer::SelectedPageChanged(int old_selected,
+                                                  int new_selected) {
+  Layout();
+}
+
+void HorizontalPageContainer::TransitionStarted() {
+  Layout();
+}
+
+void HorizontalPageContainer::TransitionChanged() {
+  Layout();
+}
+
+void HorizontalPageContainer::TransitionEnded() {
+  Layout();
+}
+
+int HorizontalPageContainer::AddHorizontalPage(HorizontalPage* view) {
+  AddChildView(view);
+  horizontal_pages_.emplace_back(view);
+  return horizontal_pages_.size() - 1;
+}
+
+int HorizontalPageContainer::GetIndexForPage(HorizontalPage* view) const {
+  for (size_t i = 0; i < horizontal_pages_.size(); ++i) {
+    if (horizontal_pages_[i] == view)
+      return i;
+  }
+  return -1;
+}
+
+HorizontalPage* HorizontalPageContainer::GetSelectedPage() {
+  const int current_page = pagination_model_.selected_page();
+  DCHECK(pagination_model_.is_valid_page(current_page));
+  return horizontal_pages_[current_page];
+}
+
+gfx::Vector2d HorizontalPageContainer::GetOffsetForPageIndex(int index) const {
+  const int current_page = pagination_model_.selected_page();
+  DCHECK(pagination_model_.is_valid_page(current_page));
+  const PaginationModel::Transition& transition =
+      pagination_model_.transition();
+  const bool is_valid = pagination_model_.is_valid_page(transition.target_page);
+  const int dir = transition.target_page > current_page ? -1 : 1;
+  int x_offset = 0;
+  // TODO(https://crbug.com/820510): figure out the right pagination style.
+  if (index < current_page)
+    x_offset = -width();
+  else if (index > current_page)
+    x_offset = width();
+
+  if (is_valid) {
+    if (index == current_page || index == transition.target_page) {
+      x_offset += transition.progress * width() * dir;
+    }
+  }
+  return gfx::Vector2d(x_offset, 0);
+}
+
+int HorizontalPageContainer::GetSearchBoxFinalTopPadding() const {
+  gfx::Rect search_box_bounds(contents_view_->GetDefaultSearchBoxBounds());
+  const int total_height =
+      GetDefaultContentsBounds().bottom() - search_box_bounds.y();
+
+  // Makes search box and content vertically centered in contents_view.
+  int y = std::max(search_box_bounds.y(),
+                   (contents_view_->GetDisplayHeight() - total_height) / 2);
+
+  // Top padding of the searchbox should not be smaller than
+  // |kSearchBoxMinimumTopPadding|
+  return std::max(y, kSearchBoxMinimumTopPadding);
+}
+
+int HorizontalPageContainer::GetSearchBoxTopPaddingDuringDragging() const {
+  float searchbox_final_y = GetSearchBoxFinalTopPadding();
+  float peeking_to_fullscreen_height =
+      contents_view_->GetDisplayHeight() - kPeekingAppListHeight;
+  float drag_amount = std::max(
+      0, contents_view_->app_list_view()->GetScreenBottom() - kShelfSize -
+             contents_view_->app_list_view()->app_list_y_position_in_screen());
+
+  if (drag_amount <= (kPeekingAppListHeight - kShelfSize)) {
+    // App list is dragged from collapsed to peeking, which moved up at most
+    // |kPeekingAppListHeight - kShelfSize| (272px). The top padding of search
+    // box changes from |kSearchBoxInitalTopPadding| to
+    // |kSearchBoxPeekingTopPadding|,
+    return std::ceil(
+        (kSearchBoxPeekingTopPadding - kSearchBoxInitalTopPadding) +
+        ((kSearchBoxPeekingTopPadding - kSearchBoxInitalTopPadding) *
+         drag_amount) /
+            (kPeekingAppListHeight - kShelfSize));
+  } else {
+    // App list is dragged from peeking to fullscreen, which moved up at most
+    // |peeking_to_fullscreen_height|. The top padding of search box changes
+    // from |kSearchBoxPeekingTopPadding| to |searchbox_final_y|.
+    int y = (kSearchBoxPeekingTopPadding +
+             std::ceil((searchbox_final_y - kSearchBoxPeekingTopPadding) *
+                       (drag_amount - (kPeekingAppListHeight - kShelfSize)) /
+                       peeking_to_fullscreen_height));
+    y = std::max(kSearchBoxPeekingTopPadding,
+                 std::min<int>(searchbox_final_y, y));
+    return y;
+  }
+}
+
+}  // namespace app_list
diff --git a/ui/app_list/views/horizontal_page_container.h b/ui/app_list/views/horizontal_page_container.h
new file mode 100644
index 0000000..3d428f6
--- /dev/null
+++ b/ui/app_list/views/horizontal_page_container.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_CONTAINER_H_
+#define UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_CONTAINER_H_
+
+#include "base/macros.h"
+#include "ui/app_list/app_list_export.h"
+#include "ui/app_list/pagination_model.h"
+#include "ui/app_list/pagination_model_observer.h"
+#include "ui/app_list/views/app_list_page.h"
+
+namespace app_list {
+
+class AppsContainerView;
+class PaginationController;
+class HorizontalPage;
+
+// HorizontalPageContainer contains a list of HorizontalPage that are
+// horizontally laid out. These pages can be switched with gesture scrolling.
+class APP_LIST_EXPORT HorizontalPageContainer : public AppListPage,
+                                                public PaginationModelObserver {
+ public:
+  HorizontalPageContainer(ContentsView* contents_view, AppListModel* model);
+  ~HorizontalPageContainer() override;
+
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+  void Layout() override;
+  void OnGestureEvent(ui::GestureEvent* event) override;
+
+  // AppListPage overrides:
+  void OnWillBeHidden() override;
+  gfx::Rect GetSearchBoxBounds() const override;
+  gfx::Rect GetSearchBoxBoundsForState(ash::AppListState state) const override;
+  gfx::Rect GetPageBoundsForState(ash::AppListState state) const override;
+  gfx::Rect GetPageBoundsDuringDragging(ash::AppListState state) const override;
+  views::View* GetFirstFocusableView() override;
+  views::View* GetLastFocusableView() override;
+
+  AppsContainerView* apps_container_view() { return apps_container_view_; }
+
+ private:
+  // PaginationModelObserver:
+  void TotalPagesChanged() override;
+  void SelectedPageChanged(int old_selected, int new_selected) override;
+  void TransitionStarted() override;
+  void TransitionChanged() override;
+  void TransitionEnded() override;
+
+  // Adds a horizontal page to this view.
+  int AddHorizontalPage(HorizontalPage* view);
+
+  // Gets the index of a horizontal page in |horizontal_pages_|. Returns -1 if
+  // there is no such view.
+  int GetIndexForPage(HorizontalPage* view) const;
+
+  // Gets the currently selected horizontal page.
+  HorizontalPage* GetSelectedPage();
+
+  // Gets the offset for the horizontal page with specified index.
+  gfx::Vector2d GetOffsetForPageIndex(int index) const;
+
+  // Gets the final top padding of search box.
+  int GetSearchBoxFinalTopPadding() const;
+
+  // Gets the top padding of search box during dragging.
+  int GetSearchBoxTopPaddingDuringDragging() const;
+
+  // Manages the pagination for the horizontal pages.
+  PaginationModel pagination_model_;
+
+  // Must appear after |pagination_model_|.
+  std::unique_ptr<PaginationController> pagination_controller_;
+
+  ContentsView* contents_view_;  // Not owned
+
+  // Owned by view hierarchy:
+  AppsContainerView* apps_container_view_ = nullptr;
+
+  // The child page views. Owned by the views hierarchy.
+  std::vector<HorizontalPage*> horizontal_pages_;
+
+  DISALLOW_COPY_AND_ASSIGN(HorizontalPageContainer);
+};
+
+}  // namespace app_list
+
+#endif  // UI_APP_LIST_VIEWS_HORIZONTAL_PAGE_CONTAINER_H_
diff --git a/ui/app_list/views/suggestions_container_view.cc b/ui/app_list/views/suggestions_container_view.cc
index 2259379..f41e92db 100644
--- a/ui/app_list/views/suggestions_container_view.cc
+++ b/ui/app_list/views/suggestions_container_view.cc
@@ -24,7 +24,7 @@
   layer()->SetFillsBoundsOpaquely(false);
 
   DCHECK(contents_view);
-  view_delegate_ = contents_view_->app_list_main_view()->view_delegate();
+  view_delegate_ = contents_view_->GetAppListMainView()->view_delegate();
   SetBackground(views::CreateSolidBackground(kLabelBackgroundColor));
 
   CreateAppsGrid(kNumStartPageTiles);
diff --git a/ui/events/ozone/chromeos/cursor_controller.cc b/ui/events/ozone/chromeos/cursor_controller.cc
index 7b84c6e..91273c6 100644
--- a/ui/events/ozone/chromeos/cursor_controller.cc
+++ b/ui/events/ozone/chromeos/cursor_controller.cc
@@ -44,6 +44,16 @@
   return base::Singleton<CursorController>::get();
 }
 
+void CursorController::AddCursorObserver(CursorObserver* observer) {
+  base::AutoLock lock(cursor_observers_lock_);
+  cursor_observers_.AddObserver(observer);
+}
+
+void CursorController::RemoveCursorObserver(CursorObserver* observer) {
+  base::AutoLock lock(cursor_observers_lock_);
+  cursor_observers_.RemoveObserver(observer);
+}
+
 void CursorController::SetCursorConfigForWindow(
     gfx::AcceleratedWidget widget,
     display::Display::Rotation rotation,
@@ -66,6 +76,12 @@
     TransformCursorMove(it->second.rotation, it->second.scale, delta);
 }
 
+void CursorController::SetCursorLocation(const gfx::PointF& location) {
+  base::AutoLock lock(cursor_observers_lock_);
+  for (auto& observer : cursor_observers_)
+    observer.OnCursorLocationChanged(location);
+}
+
 CursorController::CursorController() {
 }
 
diff --git a/ui/events/ozone/chromeos/cursor_controller.h b/ui/events/ozone/chromeos/cursor_controller.h
index 554ca6f..073f4d573 100644
--- a/ui/events/ozone/chromeos/cursor_controller.h
+++ b/ui/events/ozone/chromeos/cursor_controller.h
@@ -10,9 +10,11 @@
 #include "base/event_types.h"
 #include "base/macros.h"
 #include "base/memory/singleton.h"
+#include "base/observer_list.h"
 #include "base/synchronization/lock.h"
 #include "ui/display/display.h"
 #include "ui/events/ozone/events_ozone_export.h"
+#include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/vector2d_f.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -36,8 +38,20 @@
 // TODO(spang): Don't worry, we have a plan to remove this.
 class EVENTS_OZONE_EXPORT CursorController {
  public:
+  class CursorObserver {
+   public:
+    // Called when cursor location changed.
+    virtual void OnCursorLocationChanged(const gfx::PointF& location) = 0;
+
+   protected:
+    virtual ~CursorObserver() {}
+  };
+
   static CursorController* GetInstance();
 
+  void AddCursorObserver(CursorObserver* observer);
+  void RemoveCursorObserver(CursorObserver* observer);
+
   // Changes the rotation & scale applied for a window.
   void SetCursorConfigForWindow(gfx::AcceleratedWidget widget,
                                 display::Display::Rotation rotation,
@@ -59,6 +73,9 @@
   void ApplyCursorConfigForWindow(gfx::AcceleratedWidget widget,
                                   gfx::Vector2dF* delta) const;
 
+  // Notifies controller of new cursor location.
+  void SetCursorLocation(const gfx::PointF& location);
+
  private:
   CursorController();
   ~CursorController();
@@ -75,6 +92,9 @@
   WindowToCursorConfigurationMap window_to_cursor_configuration_map_;
   mutable base::Lock window_to_cursor_configuration_map_lock_;
 
+  base::ObserverList<CursorObserver> cursor_observers_;
+  mutable base::Lock cursor_observers_lock_;
+
   DISALLOW_COPY_AND_ASSIGN(CursorController);
 };
 
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
index f7597bb4..d4f5af78 100644
--- a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
@@ -211,14 +211,6 @@
     hwstate.buttons_down |= GESTURES_BUTTON_FORWARD;
   }
 
-  // Check if this event has an MSC_TIMESTAMP field
-  if (EvdevBitIsSet(evdev->info.msc_bitmask, MSC_TIMESTAMP)) {
-    hwstate.msc_timestamp = static_cast<stime_t>(Event_Get_Timestamp(evdev)) /
-                            base::Time::kMicrosecondsPerSecond;
-  } else {
-    hwstate.msc_timestamp = 0.0;
-  }
-
   GestureInterpreterPushHardwareState(interpreter_, &hwstate);
 }
 
diff --git a/ui/events/platform/x11/BUILD.gn b/ui/events/platform/x11/BUILD.gn
index d4c7462..31a20f0 100644
--- a/ui/events/platform/x11/BUILD.gn
+++ b/ui/events/platform/x11/BUILD.gn
@@ -38,6 +38,10 @@
     "//base",
   ]
 
+  if (is_chromeos) {
+    deps += [ "//ui/events/ozone:events_ozone" ]
+  }
+
   if (use_glib) {
     sources += [
       "x11_event_source_glib.cc",
diff --git a/ui/events/platform/x11/x11_event_source_libevent.cc b/ui/events/platform/x11/x11_event_source_libevent.cc
index e583157..2183b35 100644
--- a/ui/events/platform/x11/x11_event_source_libevent.cc
+++ b/ui/events/platform/x11/x11_event_source_libevent.cc
@@ -15,6 +15,10 @@
 #include "ui/events/x/events_x_utils.h"
 #include "ui/gfx/x/x11.h"
 
+#if defined(OS_CHROMEOS)
+#include "ui/events/ozone/chromeos/cursor_controller.h"
+#endif
+
 namespace ui {
 
 namespace {
@@ -177,6 +181,12 @@
 void X11EventSourceLibevent::ProcessXEvent(XEvent* xevent) {
   std::unique_ptr<ui::Event> translated_event = TranslateXEventToEvent(*xevent);
   if (translated_event) {
+#if defined(OS_CHROMEOS)
+    if (translated_event->IsLocatedEvent()) {
+      ui::CursorController::GetInstance()->SetCursorLocation(
+          translated_event->AsLocatedEvent()->location_f());
+    }
+#endif
     DispatchPlatformEvent(translated_event.get(), xevent);
   } else {
     // Only if we can't translate XEvent into ui::Event, try to dispatch XEvent
diff --git a/ui/ozone/platform/drm/host/drm_cursor.cc b/ui/ozone/platform/drm/host/drm_cursor.cc
index 0042294..8ffc06b4 100644
--- a/ui/ozone/platform/drm/host/drm_cursor.cc
+++ b/ui/ozone/platform/drm/host/drm_cursor.cc
@@ -226,6 +226,9 @@
       gfx::PointF(confined_bounds_.right() - 1, confined_bounds_.bottom() - 1));
 
   location_ = clamped_location;
+#if defined(OS_CHROMEOS)
+  ui::CursorController::GetInstance()->SetCursorLocation(location_);
+#endif
 }
 
 void DrmCursor::SendCursorShowLocked() {
diff --git a/ui/views/accessibility/native_view_accessibility_base.cc b/ui/views/accessibility/native_view_accessibility_base.cc
index 62455b9a..02e2a37 100644
--- a/ui/views/accessibility/native_view_accessibility_base.cc
+++ b/ui/views/accessibility/native_view_accessibility_base.cc
@@ -72,6 +72,31 @@
   return ui::AXPlatformNode::FromNativeViewAccessible(native_view_accessible);
 }
 
+ui::AXPlatformNode* PlatformNodeFromNodeID(int32_t id) {
+  // Note: For Views, node IDs and unique IDs are the same - but that isn't
+  // necessarily true for all AXPlatformNodes.
+  auto it = g_unique_id_to_ax_platform_node.Get().find(id);
+
+  if (it == g_unique_id_to_ax_platform_node.Get().end())
+    return nullptr;
+
+  return it->second;
+}
+
+void FireEvent(QueuedEvent event) {
+  ui::AXPlatformNode* node = PlatformNodeFromNodeID(event.node_id);
+  if (node)
+    node->NotifyAccessibilityEvent(event.type);
+}
+
+void FlushQueue() {
+  DCHECK(g_is_queueing_events);
+  for (QueuedEvent event : g_event_queue.Get())
+    FireEvent(event);
+  g_is_queueing_events = false;
+  g_event_queue.Get().clear();
+}
+
 }  // namespace
 
 // static
@@ -101,31 +126,6 @@
   return ax_node_->GetNativeViewAccessible();
 }
 
-ui::AXPlatformNode* PlatformNodeFromNodeID(int32_t id) {
-  // Note: For Views, node IDs and unique IDs are the same - but that isn't
-  // necessarily true for all AXPlatformNodes.
-  auto it = g_unique_id_to_ax_platform_node.Get().find(id);
-
-  if (it == g_unique_id_to_ax_platform_node.Get().end())
-    return nullptr;
-
-  return it->second;
-}
-
-void FireEvent(QueuedEvent event) {
-  ui::AXPlatformNode* node = PlatformNodeFromNodeID(event.node_id);
-  if (node)
-    node->NotifyAccessibilityEvent(event.type);
-}
-
-void FlushQueue() {
-  DCHECK(g_is_queueing_events);
-  for (QueuedEvent event : g_event_queue.Get())
-    FireEvent(event);
-  g_is_queueing_events = false;
-  g_event_queue.Get().clear();
-}
-
 void NativeViewAccessibilityBase::NotifyAccessibilityEvent(
     ax::mojom::Event event_type) {
   if (g_is_queueing_events) {
@@ -353,10 +353,6 @@
   return ViewAccessibility::GetUniqueId();
 }
 
-gfx::RectF NativeViewAccessibilityBase::GetBoundsInScreen() const {
-  return gfx::RectF(view()->GetBoundsInScreen());
-}
-
 void NativeViewAccessibilityBase::PopulateChildWidgetVector(
     std::vector<Widget*>* result_child_widgets) {
   // Only attach child widgets to the root view.
diff --git a/ui/views/accessibility/native_view_accessibility_base.h b/ui/views/accessibility/native_view_accessibility_base.h
index 637708b2..cb3d15a 100644
--- a/ui/views/accessibility/native_view_accessibility_base.h
+++ b/ui/views/accessibility/native_view_accessibility_base.h
@@ -67,9 +67,6 @@
  protected:
   explicit NativeViewAccessibilityBase(View* view);
 
- protected:
-  virtual gfx::RectF GetBoundsInScreen() const;
-
  private:
   void PopulateChildWidgetVector(std::vector<Widget*>* result_child_widgets);
 
diff --git a/ui/views/accessibility/native_view_accessibility_win.cc b/ui/views/accessibility/native_view_accessibility_win.cc
index 86cb139a..def94a1 100644
--- a/ui/views/accessibility/native_view_accessibility_win.cc
+++ b/ui/views/accessibility/native_view_accessibility_win.cc
@@ -23,6 +23,7 @@
 #include "ui/base/layout.h"
 #include "ui/base/win/accessibility_misc_utils.h"
 #include "ui/base/win/atl_module.h"
+#include "ui/display/win/screen_win.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/win/hwnd_util.h"
@@ -100,12 +101,14 @@
   return HWNDForView(view());
 }
 
-gfx::RectF NativeViewAccessibilityWin::GetBoundsInScreen() const {
-  gfx::RectF bounds = gfx::RectF(view()->GetBoundsInScreen());
-  gfx::NativeView native_view = view()->GetWidget()->GetNativeView();
-  float device_scale = ui::GetScaleFactorForNativeView(native_view);
-  bounds.Scale(device_scale);
-  return bounds;
+gfx::Rect NativeViewAccessibilityWin::GetClippedScreenBoundsRect() const {
+  // We could optionally add clipping here if ever needed.
+  return GetUnclippedScreenBoundsRect();
+}
+
+gfx::Rect NativeViewAccessibilityWin::GetUnclippedScreenBoundsRect() const {
+  gfx::Rect bounds = view()->GetBoundsInScreen();
+  return display::win::ScreenWin::DIPToScreenRect(HWNDForView(view()), bounds);
 }
 
 }  // namespace views
diff --git a/ui/views/accessibility/native_view_accessibility_win.h b/ui/views/accessibility/native_view_accessibility_win.h
index dde0c906..8f64f688 100644
--- a/ui/views/accessibility/native_view_accessibility_win.h
+++ b/ui/views/accessibility/native_view_accessibility_win.h
@@ -19,7 +19,8 @@
   // NativeViewAccessibilityBase:
   gfx::NativeViewAccessible GetParent() override;
   gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override;
-  gfx::RectF GetBoundsInScreen() const override;
+  gfx::Rect GetClippedScreenBoundsRect() const override;
+  gfx::Rect GetUnclippedScreenBoundsRect() const override;
 
   DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin);
 };
diff --git a/ui/views/animation/ink_drop_host_view.cc b/ui/views/animation/ink_drop_host_view.cc
index 5a769f05..d27e0ed1 100644
--- a/ui/views/animation/ink_drop_host_view.cc
+++ b/ui/views/animation/ink_drop_host_view.cc
@@ -168,6 +168,10 @@
       gfx::RectF(GetMirroredRect(GetContentsBounds())).CenterPoint());
 }
 
+std::unique_ptr<views::InkDropMask> InkDropHostView::CreateInkDropMask() const {
+  return nullptr;
+}
+
 std::unique_ptr<InkDropRipple> InkDropHostView::CreateDefaultInkDropRipple(
     const gfx::Point& center_point,
     const gfx::Size& size) const {
@@ -278,10 +282,6 @@
   return gfx::kPlaceholderColor;
 }
 
-std::unique_ptr<views::InkDropMask> InkDropHostView::CreateInkDropMask() const {
-  return nullptr;
-}
-
 bool InkDropHostView::HasInkDrop() const {
   return !!ink_drop_;
 }
diff --git a/ui/views/animation/ink_drop_host_view.h b/ui/views/animation/ink_drop_host_view.h
index 34ea050d..81d0182 100644
--- a/ui/views/animation/ink_drop_host_view.h
+++ b/ui/views/animation/ink_drop_host_view.h
@@ -45,6 +45,12 @@
   std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<InkDropHighlight> CreateInkDropHighlight() const override;
 
+  // Subclasses can override to return a mask for the ink drop. By default,
+  // returns nullptr (i.e no mask).
+  // TODO(bruthig): InkDropMasks do not currently work on Windows. See
+  // https://crbug.com/713359.
+  virtual std::unique_ptr<views::InkDropMask> CreateInkDropMask() const;
+
   // Toggle to enable/disable an InkDrop on this View.  Descendants can override
   // CreateInkDropHighlight() and CreateInkDropRipple() to change the look/feel
   // of the InkDrop.
@@ -107,12 +113,6 @@
   // ink drop.
   virtual SkColor GetInkDropBaseColor() const;
 
-  // Subclasses can override to return a mask for the ink drop. By default,
-  // returns nullptr (i.e no mask).
-  // TODO(bruthig): InkDropMasks do not currently work on Windows. See
-  // crbug.com/713359.
-  virtual std::unique_ptr<views::InkDropMask> CreateInkDropMask() const;
-
   // Called after a new InkDrop instance is created.
   virtual void OnInkDropCreated() {}
 
diff --git a/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js b/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
index 4cf3d472..d4d38a7 100644
--- a/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
+++ b/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
@@ -5,7 +5,7 @@
 /**
  * @fileoverview 'cr-toggle' is a component for showing an on/off switch. It
  * fires a 'change' event *only* when its state changes as a result of a user
- * interaction. Besides just tapping the element, its state can be changed by
+ * interaction. Besides just clicking the element, its state can be changed by
  * dragging (pointerdown+pointermove) the element towards the desired direction.
  */
 Polymer({
@@ -40,7 +40,7 @@
   listeners: {
     'pointerdown': 'onPointerDown_',
     'pointerup': 'onPointerUp_',
-    'tap': 'onTap_',
+    'click': 'onTap_',
     'keypress': 'onKeyPress_',
     'focus': 'onFocus_',
     'blur': 'onBlur_',
@@ -58,7 +58,7 @@
 
   /**
    * Whether the state of the toggle has already taken into account by
-   * |pointeremove| handlers. Used in the 'tap' handler.
+   * |pointeremove| handlers. Used in the 'click' handler.
    * @private {boolean}
    */
   handledInPointerMove_: false,
@@ -136,28 +136,29 @@
 
   /** @private */
   onTap_: function(e) {
-    // Prevent |tap| event from bubbling. It can cause parents of this elements
-    // to erroneously re-toggle this control.
+    // Prevent |click| event from bubbling. It can cause parents of this
+    // elements to erroneously re-toggle this control.
     e.stopPropagation();
+    e.preventDefault();
 
     // User gesture has already been taken care of inside |pointermove|
     // handlers, Do nothing here.
     if (this.handledInPointerMove_)
       return;
 
-    // If no pointermove event fired, then user just tapped on the
+    // If no pointermove event fired, then user just clicked on the
     // toggle button and therefore it should be toggled.
     this.toggleState_(false);
   },
 
   /**
-   * Whether the host of this element should handle a 'tap' event it received,
-   * to be used when tapping on the parent is supposed to toggle the cr-toggle.
+   * Whether the host of this element should handle a 'click' event it received,
+   * to be used when clicking on the parent is supposed to toggle the cr-toggle.
    *
    * This is necessary to avoid a corner case when pointerdown is initiated
    * in cr-toggle, but pointerup happens outside the bounds of cr-toggle, which
-   * ends up firing a 'tap' event on the parent (see context at crbug.com/689158
-   * and crbug.com/768555).
+   * ends up firing a 'click' event on the parent (see context at
+   * crbug.com/689158 and crbug.com/768555).
    * @param {!Event} e
    * @return {boolean}
    */