diff --git a/DEPS b/DEPS
index 3c539105..e1b89dd2 100644
--- a/DEPS
+++ b/DEPS
@@ -105,11 +105,11 @@
   # 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': 'ba651682ae856b40f5b79187025d13faab844183',
+  'skia_revision': '9b7bfd09106fa136721b229265f5d5eb2b397e57',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '9f7f390a79ded307b7050dadf1e939c5e901e21d',
+  'v8_revision': '9c646035e144006c5ede9e537e66aeedd117c690',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -181,7 +181,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'feed_revision': 'e291161061d18d222bc1b546225fe0567386cbf1',
+  'feed_revision': '2cefe05b77f7039db95a63804edf284e68055dda',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling android_sdk_build-tools_version
   # and whatever else without interference from each other.
@@ -595,7 +595,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '39a66e9fc8df234f9ce48c7b7a431fc5e59aad00',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'bef4a89b1e9fb152a3a1e44285772500016f7a68',
       'condition': 'checkout_linux',
   },
 
@@ -813,7 +813,7 @@
     Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'd7ed8e2f3f35ce9a3aafdfdc48745ceab66e7229',
 
   'src/third_party/libaom/source/libaom': {
-    'url': Var('aomedia_git') + '/aom.git' + '@' +  'f866f5ebb34b22afc2244789dc8551b0c8d99a13',
+    'url': Var('aomedia_git') + '/aom.git' + '@' +  'a5078bf8d0e7c01eab670cfc1cfe7b9fb065e931',
     'condition': 'checkout_libaom',
   },
 
@@ -853,7 +853,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '2beb5c9f91e7166c2c9d01c94bf84767815121e4',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  'ecc31d28781c490f5fb18a3e6873692a1b8e6cea',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '01c1d1d76f139345c442bfc8e61b4e1cba809059',
@@ -1106,7 +1106,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6d2f3f4cb8bac1f7c4a945c73d07a33df74f22f9',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '3a7423909164538a047a0bd93ae0dfbb575ba2dd',
+    Var('webrtc_git') + '/src.git' + '@' + 'ab09039d2ab6d66d3a57c6caa626034d9effb62e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1137,7 +1137,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3c569553009589659019b3909c270727d450c02a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@be97d2b8d9afd08cf06a9b33b42d42c6ace4700a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index cca5da8..7539ed8 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -71,9 +71,9 @@
     "wm/mru_window_tracker.h",
     "wm/overview/window_selector_controller.h",
     "wm/splitview/split_view_controller.h",
-    "wm/tablet_mode/tablet_mode_app_window_drag_controller.h",
     "wm/tablet_mode/tablet_mode_controller.h",
     "wm/tablet_mode/tablet_mode_observer.h",
+    "wm/tablet_mode/tablet_mode_window_drag_delegate.h",
     "wm/window_finder.h",
     "wm/window_positioner.h",
     "wm/window_positioning_utils.h",
@@ -1246,7 +1246,6 @@
     "wm/video_detector.h",
     "wm/widget_finder.cc",
     "wm/widget_finder.h",
-    "wm/window_animation_types.h",
     "wm/window_animations.cc",
     "wm/window_animations.h",
     "wm/window_cycle_controller.cc",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 7ac04ef0..2cbc31de 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -724,7 +724,7 @@
     case ui::ET_SCROLL_FLING_START:
     case ui::ET_GESTURE_SCROLL_BEGIN:
       return home_launcher_gesture_handler_->OnPressEvent(
-          HomeLauncherGestureHandler::Mode::kSwipeDownToHide);
+          HomeLauncherGestureHandler::Mode::kSlideDownToHide, screen_location);
     case ui::ET_GESTURE_SCROLL_UPDATE:
       return home_launcher_gesture_handler_->OnScrollEvent(screen_location);
     case ui::ET_GESTURE_END:
@@ -742,7 +742,7 @@
     return false;
 
   return home_launcher_gesture_handler_->mode() ==
-         HomeLauncherGestureHandler::Mode::kSwipeUpToShow;
+         HomeLauncherGestureHandler::Mode::kSlideUpToShow;
 }
 
 ws::WindowService* AppListControllerImpl::GetWindowService() {
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index b1bac41..12f4940 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -1135,7 +1135,7 @@
         ->shelf_controller()
         ->model()
         ->GetShelfItemDelegate(ShelfID(kAppListId))
-        ->ItemSelected(std::move(event), display::kInvalidDisplayId,
+        ->ItemSelected(std::move(event), GetPrimaryDisplayId(),
                        ash::LAUNCH_FROM_UNKNOWN, base::DoNothing());
     GetAppListTestHelper()->WaitUntilIdle();
   }
diff --git a/ash/app_list/home_launcher_gesture_handler.cc b/ash/app_list/home_launcher_gesture_handler.cc
index 47b3fb0..537448b6 100644
--- a/ash/app_list/home_launcher_gesture_handler.cc
+++ b/ash/app_list/home_launcher_gesture_handler.cc
@@ -16,6 +16,7 @@
 #include "ash/wm/splitview/split_view_divider.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
+#include "ash/wm/window_util.h"
 #include "ash/wm/workspace/backdrop_controller.h"
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
@@ -45,19 +46,20 @@
 // Checks if |window| can be hidden or shown with a gesture.
 bool CanProcessWindow(aura::Window* window,
                       HomeLauncherGestureHandler::Mode mode) {
-  DCHECK(window);
+  if (!window)
+    return false;
 
   // Window should not be fullscreen as we do not allow swiping up when auto
   // hide for shelf.
   DCHECK(!wm::GetWindowState(window)->IsFullscreen());
 
   if (!window->IsVisible() &&
-      mode == HomeLauncherGestureHandler::Mode::kSwipeUpToShow) {
+      mode == HomeLauncherGestureHandler::Mode::kSlideUpToShow) {
     return false;
   }
 
   if (window->IsVisible() &&
-      mode == HomeLauncherGestureHandler::Mode::kSwipeDownToHide) {
+      mode == HomeLauncherGestureHandler::Mode::kSlideDownToHide) {
     return false;
   }
 
@@ -110,10 +112,8 @@
 
 // Given a |location_in_screen|, find out where it lies as a ratio in the
 // work area, where the top of the work area is 0.f and the bottom is 1.f.
-double GetHeightInWorkAreaAsRatio(const gfx::Point& location_in_screen) {
-  gfx::Rect work_area = display::Screen::GetScreen()
-                            ->GetDisplayNearestPoint(location_in_screen)
-                            .work_area();
+double GetHeightInWorkAreaAsRatio(const gfx::Point& location_in_screen,
+                                  const gfx::Rect& work_area) {
   int clamped_y = base::ClampToRange(location_in_screen.y(), work_area.y(),
                                      work_area.bottom());
   double ratio =
@@ -121,8 +121,9 @@
   return 1.0 - ratio;
 }
 
-bool IsLastEventInTopHalf(const gfx::Point& location_in_screen) {
-  return GetHeightInWorkAreaAsRatio(location_in_screen) > 0.5;
+bool IsLastEventInTopHalf(const gfx::Point& location_in_screen,
+                          const gfx::Rect& work_area) {
+  return GetHeightInWorkAreaAsRatio(location_in_screen, work_area) > 0.5;
 }
 
 // Returns the window of the widget which contains the workspace backdrop. May
@@ -184,50 +185,438 @@
   StopObservingImplicitAnimations();
 }
 
-bool HomeLauncherGestureHandler::OnPressEvent(Mode mode) {
+bool HomeLauncherGestureHandler::OnPressEvent(Mode mode,
+                                              const gfx::Point& location) {
   // Do not start a new session if a window is currently being processed.
-  if (last_event_location_)
+  if (!IsIdle())
     return false;
 
+  display_ = display::Screen::GetScreen()->GetDisplayNearestPoint(location);
+  if (!display_.is_valid())
+    return false;
+
+  if (!SetUpWindows(mode, nullptr /* window */))
+    return false;
+
+  mode_ = mode;
+  last_event_location_ = base::make_optional(location);
+
+  UpdateWindows(0.0, /*animate=*/false);
+  return true;
+}
+
+bool HomeLauncherGestureHandler::OnScrollEvent(const gfx::Point& location) {
+  if (!IsDragInProgress())
+    return false;
+
+  last_event_location_ = base::make_optional(location);
+  DCHECK(display_.is_valid());
+  UpdateWindows(GetHeightInWorkAreaAsRatio(location, display_.work_area()),
+                /*animate=*/false);
+  return true;
+}
+
+bool HomeLauncherGestureHandler::OnReleaseEvent(const gfx::Point& location) {
+  if (!IsDragInProgress())
+    return false;
+
+  last_event_location_ = base::make_optional(location);
+  AnimateToFinalState();
+  return true;
+}
+
+void HomeLauncherGestureHandler::Cancel() {
+  if (!IsDragInProgress())
+    return;
+
+  AnimateToFinalState();
+  return;
+}
+
+bool HomeLauncherGestureHandler::ShowHomeLauncher(
+    const display::Display& display) {
+  if (!IsIdle())
+    return false;
+
+  if (!display.is_valid())
+    return false;
+
+  if (!SetUpWindows(Mode::kSlideUpToShow, nullptr /* window */))
+    return false;
+
+  display_ = display;
+  mode_ = Mode::kSlideUpToShow;
+
+  UpdateWindows(0.0, /*animate=*/false);
+  AnimateToFinalState();
+  return true;
+}
+
+bool HomeLauncherGestureHandler::HideHomeLauncherForWindow(
+    const display::Display& display,
+    aura::Window* window) {
+  if (!IsIdle())
+    return false;
+
+  if (!display.is_valid())
+    return false;
+
+  if (!SetUpWindows(Mode::kSlideDownToHide, window))
+    return false;
+
+  display_ = display;
+  mode_ = Mode::kSlideDownToHide;
+
+  UpdateWindows(1.0, /*animate=*/false);
+  AnimateToFinalState();
+  return true;
+}
+
+void HomeLauncherGestureHandler::OnWindowDestroying(aura::Window* window) {
+  if (window == window_) {
+    for (auto* hidden_window : hidden_windows_)
+      hidden_window->Show();
+
+    RemoveObserversAndStopTracking();
+    return;
+  }
+
+  if (window == window2_) {
+    DCHECK(window_);
+    window->RemoveObserver(this);
+    window2_ = nullptr;
+    return;
+  }
+
+  if (transient_descendants_values_.find(window) !=
+      transient_descendants_values_.end()) {
+    window->RemoveObserver(this);
+    transient_descendants_values_.erase(window);
+    return;
+  }
+
+  if (transient_descendants_values2_.find(window) !=
+      transient_descendants_values2_.end()) {
+    window->RemoveObserver(this);
+    transient_descendants_values2_.erase(window);
+    return;
+  }
+
+  DCHECK(base::ContainsValue(hidden_windows_, window));
+  window->RemoveObserver(this);
+  hidden_windows_.erase(
+      std::find(hidden_windows_.begin(), hidden_windows_.end(), window));
+}
+
+void HomeLauncherGestureHandler::OnTabletModeEnded() {
+  if (IsIdle())
+    return;
+
+  // When leaving tablet mode advance to the end of the in progress scroll
+  // session or animation.
+  StopObservingImplicitAnimations();
+  if (window_)
+    window_->layer()->GetAnimator()->StopAnimating();
+  if (window2_)
+    window2_->layer()->GetAnimator()->StopAnimating();
+  for (const auto& descendant : transient_descendants_values_)
+    descendant.first->layer()->GetAnimator()->StopAnimating();
+  for (const auto& descendant : transient_descendants_values2_)
+    descendant.first->layer()->GetAnimator()->StopAnimating();
+  UpdateWindows(IsFinalStateShow() ? 1.0 : 0.0,
+                /*animate=*/false);
+  OnImplicitAnimationsCompleted();
+}
+
+void HomeLauncherGestureHandler::OnImplicitAnimationsCompleted() {
+  float app_list_opacity = 1.f;
+  const bool is_final_state_show = IsFinalStateShow();
+  if (Shell::Get()->window_selector_controller()->IsSelecting()) {
+    if (is_final_state_show) {
+      // Exit overview if event is released on the top half. This will also end
+      // splitview if it is active as SplitViewController observes overview mode
+      // ends.
+      Shell::Get()->window_selector_controller()->ToggleOverview(
+          WindowSelector::EnterExitOverviewType::kSwipeFromShelf);
+    } else {
+      app_list_opacity = 0.f;
+    }
+  }
+
+  // Return the app list to its original opacity and transform without
+  // animation.
+  DCHECK(display_.is_valid());
+  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
+      display_.work_area().y(), app_list_opacity, base::NullCallback());
+
+  if (!window_) {
+    RemoveObserversAndStopTracking();
+    return;
+  }
+
+  // Explicitly exit split view if two windows are snapped.
+  if (is_final_state_show && Shell::Get()->split_view_controller()->state() ==
+                                 SplitViewController::BOTH_SNAPPED) {
+    Shell::Get()->split_view_controller()->EndSplitView();
+  }
+
+  window_->SetTransform(window_values_.initial_transform);
+  window_->layer()->SetOpacity(window_values_.initial_opacity);
+  if (window2_) {
+    window2_->SetTransform(window_values2_.initial_transform);
+    window2_->layer()->SetOpacity(window_values2_.initial_opacity);
+  }
+
+  if (is_final_state_show) {
+    ScopedAnimationDisabler disable(window_);
+    wm::GetWindowState(window_)->Minimize();
+
+    if (window2_) {
+      ScopedAnimationDisabler disable(window2_);
+      wm::GetWindowState(window2_)->Minimize();
+    }
+
+    // Minimize the hidden windows so they can be used normally with alt+tab
+    // and overview. Minimize in reverse order to preserve mru ordering.
+    std::reverse(hidden_windows_.begin(), hidden_windows_.end());
+    for (auto* window : hidden_windows_) {
+      ScopedAnimationDisabler disable(window);
+      wm::GetWindowState(window)->Minimize();
+    }
+  } else {
+    // Reshow all windows previously hidden.
+    for (auto* window : hidden_windows_) {
+      ScopedAnimationDisabler disable(window);
+      window->Show();
+    }
+  }
+
+  // Update the backdrop last as the backdrop controller listens for some state
+  // changes like minimizing above which may also alter the backdrop.
+  aura::Window* backdrop_window = GetBackdropWindow(window_);
+  if (backdrop_window) {
+    backdrop_window->SetTransform(gfx::Transform());
+    backdrop_window->layer()->SetOpacity(1.f);
+  }
+
+  RemoveObserversAndStopTracking();
+}
+
+void HomeLauncherGestureHandler::AnimateToFinalState() {
+  const bool is_final_state_show = IsFinalStateShow();
+  UpdateWindows(is_final_state_show ? 1.0 : 0.0, /*animate=*/true);
+
+  if (!is_final_state_show && mode_ == Mode::kSlideDownToHide) {
+    base::RecordAction(
+        base::UserMetricsAction("AppList_HomeLauncherToMRUWindow"));
+  } else if (is_final_state_show && mode_ == Mode::kSlideUpToShow) {
+    base::RecordAction(
+        base::UserMetricsAction("AppList_CurrentWindowToHomeLauncher"));
+  }
+}
+
+void HomeLauncherGestureHandler::UpdateSettings(
+    ui::ScopedLayerAnimationSettings* settings,
+    bool observe) {
+  // TODO(sammiequon): The animation should change based on the distance to the
+  // end.
+  settings->SetTransitionDuration(kAnimationDurationMs);
+  settings->SetTweenType(gfx::Tween::LINEAR);
+  settings->SetPreemptionStrategy(
+      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+
+  if (observe)
+    settings->AddObserver(this);
+}
+
+void HomeLauncherGestureHandler::UpdateWindows(double progress, bool animate) {
+  // Update full screen applist.
+  DCHECK(display_.is_valid());
+  const gfx::Rect work_area = display_.work_area();
+  const int y_position =
+      gfx::Tween::IntValueBetween(progress, work_area.bottom(), work_area.y());
+  const float opacity = gfx::Tween::FloatValueBetween(progress, 0.f, 1.f);
+  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
+      y_position, opacity,
+      animate ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings,
+                                    base::Unretained(this))
+              : base::NullCallback());
+
+  // Update the overview grid if needed.
+  WindowSelectorController* controller =
+      Shell::Get()->window_selector_controller();
+  if (controller->IsSelecting()) {
+    DCHECK_EQ(mode_, Mode::kSlideUpToShow);
+    controller->window_selector()->UpdateGridAtLocationYPositionAndOpacity(
+        display_.id(), y_position - work_area.height(), 1.f - opacity,
+        work_area,
+        animate
+            ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings,
+                                  base::Unretained(this))
+            : base::NullCallback());
+  }
+
+  if (!window_)
+    return;
+
+  // Helper to update a single windows opacity and transform based on by
+  // calculating the in between values using |value| and |values|.
+  auto update_windows_helper = [this](double progress, bool animate,
+                                      aura::Window* window,
+                                      const WindowValues& values) {
+    float opacity = gfx::Tween::FloatValueBetween(
+        progress, values.initial_opacity, values.target_opacity);
+    gfx::Transform transform = gfx::Tween::TransformValueBetween(
+        progress, values.initial_transform, values.target_transform);
+
+    std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
+    if (animate) {
+      settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
+          window->layer()->GetAnimator());
+      // There are multiple animations run on a release event (app list,
+      // overview and the stored windows). We only want to act on one animation
+      // end, so only observe one of the animations. If overview is active,
+      // observe the shield widget of the grid, else observe |window_|.
+      UpdateSettings(
+          settings.get(),
+          this->window_ == window &&
+              !Shell::Get()->window_selector_controller()->IsSelecting());
+    }
+    window->layer()->SetOpacity(opacity);
+    window->SetTransform(transform);
+  };
+
+  aura::Window* backdrop_window = GetBackdropWindow(window_);
+  if (backdrop_window && backdrop_values_) {
+    update_windows_helper(progress, animate, backdrop_window,
+                          *backdrop_values_);
+  }
+
+  aura::Window* divider_window = GetDividerWindow();
+  if (divider_window && divider_values_) {
+    update_windows_helper(progress, animate, divider_window, *divider_values_);
+  }
+
+  for (const auto& descendant : transient_descendants_values_) {
+    update_windows_helper(progress, animate, descendant.first,
+                          descendant.second);
+  }
+
+  for (const auto& descendant : transient_descendants_values2_) {
+    update_windows_helper(progress, animate, descendant.first,
+                          descendant.second);
+  }
+
+  if (window2_)
+    update_windows_helper(progress, animate, window2_, window_values2_);
+  update_windows_helper(progress, animate, window_, window_values_);
+}
+
+void HomeLauncherGestureHandler::RemoveObserversAndStopTracking() {
+  display_.set_id(display::kInvalidDisplayId);
+  backdrop_values_ = base::nullopt;
+  divider_values_ = base::nullopt;
+  last_event_location_ = base::nullopt;
+  mode_ = Mode::kNone;
+
+  for (auto* window : hidden_windows_)
+    window->RemoveObserver(this);
+  hidden_windows_.clear();
+
+  for (const auto& descendant : transient_descendants_values_)
+    descendant.first->RemoveObserver(this);
+  transient_descendants_values_.clear();
+
+  if (window_)
+    window_->RemoveObserver(this);
+  window_ = nullptr;
+
+  for (const auto& descendant : transient_descendants_values2_)
+    descendant.first->RemoveObserver(this);
+  transient_descendants_values2_.clear();
+
+  if (window2_)
+    window2_->RemoveObserver(this);
+  window2_ = nullptr;
+}
+
+bool HomeLauncherGestureHandler::IsIdle() {
+  if (IsDragInProgress())
+    return false;
+
+  if (window_ && window_->layer()->GetAnimator()->is_animating())
+    return false;
+
+  if (window2_ && window2_->layer()->GetAnimator()->is_animating())
+    return false;
+
+  for (const auto& descendant : transient_descendants_values_) {
+    if (descendant.first->layer()->GetAnimator()->is_animating())
+      return false;
+  }
+
+  for (const auto& descendant : transient_descendants_values2_) {
+    if (descendant.first->layer()->GetAnimator()->is_animating())
+      return false;
+  }
+
+  return true;
+}
+
+bool HomeLauncherGestureHandler::IsFinalStateShow() {
+  DCHECK_NE(Mode::kNone, mode_);
+  DCHECK(display_.is_valid());
+  return last_event_location_
+             ? IsLastEventInTopHalf(*last_event_location_, display_.work_area())
+             : mode_ == Mode::kSlideUpToShow;
+}
+
+bool HomeLauncherGestureHandler::SetUpWindows(Mode mode, aura::Window* window) {
   SplitViewController* split_view_controller =
       Shell::Get()->split_view_controller();
+  const bool overview_active =
+      Shell::Get()->window_selector_controller()->IsSelecting();
   const bool split_view_active = split_view_controller->IsSplitViewModeActive();
+  if (window && (mode != Mode::kSlideDownToHide || overview_active ||
+                 split_view_active)) {
+    window_ = nullptr;
+    return false;
+  }
+
   if (Shell::Get()
           ->app_list_controller()
           ->IsHomeLauncherEnabledInTabletMode() &&
-      Shell::Get()->window_selector_controller()->IsSelecting() &&
-      !split_view_active) {
-    DCHECK_EQ(Mode::kSwipeUpToShow, mode);
-    last_event_location_ = base::make_optional(gfx::Point());
-    mode_ = mode;
+      overview_active && !split_view_active) {
+    DCHECK_EQ(Mode::kSlideUpToShow, mode);
     window_ = nullptr;
     return true;
   }
 
-  // We want the first window in the mru list, if it exists and is usable.
+  // Always hide split view windows if they exist. Otherwise, hide the specified
+  // window if it is not null. If non of above is true, we want the first window
+  // in the mru list, if it exists and is usable.
   auto windows = Shell::Get()->mru_window_tracker()->BuildWindowForCycleList();
-  if (windows.empty() || !CanProcessWindow(windows[0], mode)) {
+  window_ = split_view_active
+                ? split_view_controller->GetDefaultSnappedWindow()
+                : (window ? window : (windows.empty() ? nullptr : windows[0]));
+  if (!CanProcessWindow(window_, mode)) {
     window_ = nullptr;
     return false;
   }
 
+  DCHECK(base::ContainsValue(windows, window_));
   DCHECK_NE(Mode::kNone, mode);
-  last_event_location_ = base::make_optional(gfx::Point());
-  mode_ = mode;
   base::RecordAction(base::UserMetricsAction(
-      mode_ == Mode::kSwipeDownToHide
+      mode == Mode::kSlideDownToHide
           ? "AppList_HomeLauncherToMRUWindowAttempt"
           : "AppList_CurrentWindowToHomeLauncherAttempt"));
-  window_ = split_view_active ? split_view_controller->GetDefaultSnappedWindow()
-                              : windows[0];
-  DCHECK(base::ContainsValue(windows, window_));
   window_->AddObserver(this);
   base::EraseIf(windows,
                 [this](aura::Window* elem) { return elem == this->window_; });
 
   // Alter a second window if we are in split view mode with two windows
   // snapped.
-  if (mode == Mode::kSwipeUpToShow &&
+  if (mode == Mode::kSlideUpToShow &&
       split_view_controller->state() == SplitViewController::BOTH_SNAPPED) {
     DCHECK_GT(windows.size(), 0u);
     window2_ = split_view_controller->default_snap_position() ==
@@ -241,9 +630,10 @@
   }
 
   // Show |window_| if we are swiping down to hide.
-  if (mode == Mode::kSwipeDownToHide) {
+  if (mode == Mode::kSlideDownToHide) {
     ScopedAnimationDisabler disable(window_);
     window_->Show();
+    wm::ActivateWindow(window_);
     window_->layer()->SetOpacity(1.f);
   }
 
@@ -319,8 +709,7 @@
   // scroll, the home launcher will be visible. This is only needed when swiping
   // up, and not when overview mode is active.
   hidden_windows_.clear();
-  if (mode_ == Mode::kSwipeUpToShow &&
-      !Shell::Get()->window_selector_controller()->IsSelecting()) {
+  if (mode == Mode::kSlideUpToShow && !overview_active) {
     for (auto* window : windows) {
       if (window->IsVisible()) {
         hidden_windows_.push_back(window);
@@ -331,310 +720,7 @@
     }
   }
 
-  UpdateWindows(0.0, /*animate=*/false);
   return true;
 }
 
-bool HomeLauncherGestureHandler::OnScrollEvent(const gfx::Point& location) {
-  if (!last_event_location_)
-    return false;
-
-  last_event_location_ = base::make_optional(location);
-  double progress = GetHeightInWorkAreaAsRatio(location);
-  UpdateWindows(progress, /*animate=*/false);
-  return true;
-}
-
-bool HomeLauncherGestureHandler::OnReleaseEvent(const gfx::Point& location) {
-  if (!last_event_location_)
-    return false;
-
-  last_event_location_ = base::make_optional(location);
-  AnimateToFinalState();
-  return true;
-}
-
-void HomeLauncherGestureHandler::Cancel() {
-  if (!last_event_location_)
-    return;
-
-  AnimateToFinalState();
-  return;
-}
-
-void HomeLauncherGestureHandler::OnWindowDestroying(aura::Window* window) {
-  if (window == window_) {
-    for (auto* hidden_window : hidden_windows_)
-      hidden_window->Show();
-
-    last_event_location_ = base::nullopt;
-    RemoveObserversAndStopTracking();
-    return;
-  }
-
-  if (window == window2_) {
-    DCHECK(window_);
-    window->RemoveObserver(this);
-    window2_ = nullptr;
-    return;
-  }
-
-  if (transient_descendants_values_.find(window) !=
-      transient_descendants_values_.end()) {
-    window->RemoveObserver(this);
-    transient_descendants_values_.erase(window);
-    return;
-  }
-
-  if (transient_descendants_values2_.find(window) !=
-      transient_descendants_values2_.end()) {
-    window->RemoveObserver(this);
-    transient_descendants_values2_.erase(window);
-    return;
-  }
-
-  DCHECK(base::ContainsValue(hidden_windows_, window));
-  window->RemoveObserver(this);
-  hidden_windows_.erase(
-      std::find(hidden_windows_.begin(), hidden_windows_.end(), window));
-}
-
-void HomeLauncherGestureHandler::OnTabletModeEnded() {
-  if (!last_event_location_)
-    return;
-
-  // When leaving tablet mode advance to the end of the in progress scroll
-  // session or animation.
-  StopObservingImplicitAnimations();
-  if (window_)
-    window_->layer()->GetAnimator()->StopAnimating();
-  if (window2_)
-    window2_->layer()->GetAnimator()->StopAnimating();
-  for (const auto& descendant : transient_descendants_values_)
-    descendant.first->layer()->GetAnimator()->StopAnimating();
-  for (const auto& descendant : transient_descendants_values2_)
-    descendant.first->layer()->GetAnimator()->StopAnimating();
-  UpdateWindows(IsLastEventInTopHalf(*last_event_location_) ? 1.0 : 0.0,
-                /*animate=*/false);
-  OnImplicitAnimationsCompleted();
-}
-
-void HomeLauncherGestureHandler::OnImplicitAnimationsCompleted() {
-  DCHECK(last_event_location_);
-
-  float app_list_opacity = 1.f;
-  const bool last_event_top_half = IsLastEventInTopHalf(*last_event_location_);
-  if (Shell::Get()->window_selector_controller()->IsSelecting()) {
-    if (last_event_top_half) {
-      // Exit overview if event is released on the top half. This will also end
-      // splitview if it is active as SplitViewController observes overview mode
-      // ends.
-      Shell::Get()->window_selector_controller()->ToggleOverview(
-          WindowSelector::EnterExitOverviewType::kSwipeFromShelf);
-    } else {
-      app_list_opacity = 0.f;
-    }
-  }
-
-  // Return the app list to its original opacity and transform without
-  // animation.
-  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
-      display::Screen::GetScreen()
-          ->GetDisplayNearestPoint(*last_event_location_)
-          .work_area()
-          .y(),
-      app_list_opacity, base::NullCallback());
-
-  if (!window_) {
-    RemoveObserversAndStopTracking();
-    return;
-  }
-
-  // Explicitly exit split view if two windows are snapped.
-  if (last_event_top_half && Shell::Get()->split_view_controller()->state() ==
-                                 SplitViewController::BOTH_SNAPPED) {
-    Shell::Get()->split_view_controller()->EndSplitView();
-  }
-
-  window_->SetTransform(window_values_.initial_transform);
-  window_->layer()->SetOpacity(window_values_.initial_opacity);
-  if (window2_) {
-    window2_->SetTransform(window_values2_.initial_transform);
-    window2_->layer()->SetOpacity(window_values2_.initial_opacity);
-  }
-
-  if (last_event_top_half) {
-    ScopedAnimationDisabler disable(window_);
-    wm::GetWindowState(window_)->Minimize();
-
-    if (window2_) {
-      ScopedAnimationDisabler disable(window2_);
-      wm::GetWindowState(window2_)->Minimize();
-    }
-
-    // Minimize the hidden windows so they can be used normally with alt+tab
-    // and overview. Minimize in reverse order to preserve mru ordering.
-    std::reverse(hidden_windows_.begin(), hidden_windows_.end());
-    for (auto* window : hidden_windows_) {
-      ScopedAnimationDisabler disable(window);
-      wm::GetWindowState(window)->Minimize();
-    }
-  } else {
-    // Reshow all windows previously hidden.
-    for (auto* window : hidden_windows_) {
-      ScopedAnimationDisabler disable(window);
-      window->Show();
-    }
-  }
-
-  // Update the backdrop last as the backdrop controller listens for some state
-  // changes like minimizing above which may also alter the backdrop.
-  aura::Window* backdrop_window = GetBackdropWindow(window_);
-  if (backdrop_window) {
-    backdrop_window->SetTransform(gfx::Transform());
-    backdrop_window->layer()->SetOpacity(1.f);
-  }
-
-  RemoveObserversAndStopTracking();
-}
-
-void HomeLauncherGestureHandler::AnimateToFinalState() {
-  const bool hide_window = IsLastEventInTopHalf(*last_event_location_);
-  UpdateWindows(hide_window ? 1.0 : 0.0, /*animate=*/true);
-
-  if (!hide_window && mode_ == Mode::kSwipeDownToHide) {
-    base::RecordAction(
-        base::UserMetricsAction("AppList_HomeLauncherToMRUWindow"));
-  } else if (hide_window && mode_ == Mode::kSwipeUpToShow) {
-    base::RecordAction(
-        base::UserMetricsAction("AppList_CurrentWindowToHomeLauncher"));
-  }
-}
-
-void HomeLauncherGestureHandler::UpdateSettings(
-    ui::ScopedLayerAnimationSettings* settings,
-    bool observe) {
-  // TODO(sammiequon): The animation should change based on the distance to the
-  // end.
-  settings->SetTransitionDuration(kAnimationDurationMs);
-  settings->SetTweenType(gfx::Tween::LINEAR);
-  settings->SetPreemptionStrategy(
-      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
-
-  if (observe)
-    settings->AddObserver(this);
-}
-
-void HomeLauncherGestureHandler::UpdateWindows(double progress, bool animate) {
-  // Update full screen applist.
-  const gfx::Rect work_area =
-      display::Screen::GetScreen()
-          ->GetDisplayNearestPoint(*last_event_location_)
-          .work_area();
-  const int y_position =
-      gfx::Tween::IntValueBetween(progress, work_area.bottom(), work_area.y());
-  const float opacity = gfx::Tween::FloatValueBetween(progress, 0.f, 1.f);
-  app_list_controller_->presenter()->UpdateYPositionAndOpacityForHomeLauncher(
-      y_position, opacity,
-      animate ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings,
-                                    base::Unretained(this))
-              : base::NullCallback());
-
-  // Update the overview grid if needed.
-  WindowSelectorController* controller =
-      Shell::Get()->window_selector_controller();
-  if (controller->IsSelecting()) {
-    DCHECK_EQ(mode_, Mode::kSwipeUpToShow);
-    controller->window_selector()->UpdateGridAtLocationYPositionAndOpacity(
-        *last_event_location_, y_position - work_area.height(), 1.f - opacity,
-        work_area,
-        animate
-            ? base::BindRepeating(&HomeLauncherGestureHandler::UpdateSettings,
-                                  base::Unretained(this))
-            : base::NullCallback());
-  }
-
-  if (!window_)
-    return;
-
-  // Helper to update a single windows opacity and transform based on by
-  // calculating the in between values using |value| and |values|.
-  auto update_windows_helper = [this](double progress, bool animate,
-                                      aura::Window* window,
-                                      const WindowValues& values) {
-    float opacity = gfx::Tween::FloatValueBetween(
-        progress, values.initial_opacity, values.target_opacity);
-    gfx::Transform transform = gfx::Tween::TransformValueBetween(
-        progress, values.initial_transform, values.target_transform);
-
-    std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
-    if (animate) {
-      settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
-          window->layer()->GetAnimator());
-      // There are multiple animations run on a release event (app list,
-      // overview and the stored windows). We only want to act on one animation
-      // end, so only observe one of the animations. If overview is active,
-      // observe the shield widget of the grid, else observe |window_|.
-      UpdateSettings(
-          settings.get(),
-          this->window_ == window &&
-              !Shell::Get()->window_selector_controller()->IsSelecting());
-    }
-    window->layer()->SetOpacity(opacity);
-    window->SetTransform(transform);
-  };
-
-  aura::Window* backdrop_window = GetBackdropWindow(window_);
-  if (backdrop_window && backdrop_values_) {
-    update_windows_helper(progress, animate, backdrop_window,
-                          *backdrop_values_);
-  }
-
-  aura::Window* divider_window = GetDividerWindow();
-  if (divider_window && divider_values_) {
-    update_windows_helper(progress, animate, divider_window, *divider_values_);
-  }
-
-  for (const auto& descendant : transient_descendants_values_) {
-    update_windows_helper(progress, animate, descendant.first,
-                          descendant.second);
-  }
-
-  for (const auto& descendant : transient_descendants_values2_) {
-    update_windows_helper(progress, animate, descendant.first,
-                          descendant.second);
-  }
-
-  if (window2_)
-    update_windows_helper(progress, animate, window2_, window_values2_);
-  update_windows_helper(progress, animate, window_, window_values_);
-}
-
-void HomeLauncherGestureHandler::RemoveObserversAndStopTracking() {
-  backdrop_values_ = base::nullopt;
-  divider_values_ = base::nullopt;
-  last_event_location_ = base::nullopt;
-  mode_ = Mode::kNone;
-
-  for (auto* window : hidden_windows_)
-    window->RemoveObserver(this);
-  hidden_windows_.clear();
-
-  for (const auto& descendant : transient_descendants_values_)
-    descendant.first->RemoveObserver(this);
-  transient_descendants_values_.clear();
-
-  if (window_)
-    window_->RemoveObserver(this);
-  window_ = nullptr;
-
-  for (const auto& descendant : transient_descendants_values2_)
-    descendant.first->RemoveObserver(this);
-  transient_descendants_values2_.clear();
-
-  if (window2_)
-    window2_->RemoveObserver(this);
-  window2_ = nullptr;
-}
-
 }  // namespace ash
diff --git a/ash/app_list/home_launcher_gesture_handler.h b/ash/app_list/home_launcher_gesture_handler.h
index 15edc6f..44cf8c47 100644
--- a/ash/app_list/home_launcher_gesture_handler.h
+++ b/ash/app_list/home_launcher_gesture_handler.h
@@ -37,11 +37,11 @@
   enum class Mode {
     // There is no current scroll process.
     kNone,
-    // Swiping away the MRU window to display launcher. If in overview mode,
-    // swipes away overview mode as well.
-    kSwipeUpToShow,
-    // Swiping down the MRU window to hide launcher.
-    kSwipeDownToHide,
+    // Sliding up the MRU window to display launcher. If in overview mode,
+    // slides up overview mode as well.
+    kSlideUpToShow,
+    // Sliding down the MRU window to hide launcher.
+    kSlideDownToHide,
   };
 
   explicit HomeLauncherGestureHandler(
@@ -51,7 +51,7 @@
   // Called by owner of this object when a gesture event is received. |location|
   // should be in screen coordinates. Returns false if the the gesture event
   // was not processed.
-  bool OnPressEvent(Mode mode);
+  bool OnPressEvent(Mode mode, const gfx::Point& location);
   bool OnScrollEvent(const gfx::Point& location);
   bool OnReleaseEvent(const gfx::Point& location);
 
@@ -59,6 +59,13 @@
   // |last_event_location_|.
   void Cancel();
 
+  // Hide MRU window and show home launcher on specified display.
+  bool ShowHomeLauncher(const display::Display& display);
+
+  // Hide home launcher and show MRU window on specified display.
+  bool HideHomeLauncherForWindow(const display::Display& display,
+                                 aura::Window* window);
+
   bool IsDragInProgress() const { return last_event_location_.has_value(); }
 
   // TODO(sammiequon): Investigate if it is needed to observe potential window
@@ -88,7 +95,7 @@
     gfx::Transform target_transform;
   };
 
-  // Animates the items based on |last_event_location_|.
+  // Animates the items based on IsFinalStateShow().
   void AnimateToFinalState();
 
   // Updates |settings| based on what we want for this class. This will listen
@@ -105,6 +112,19 @@
   // Stop observing all windows and remove their local pointers.
   void RemoveObserversAndStopTracking();
 
+  // Returns true if there's no gesture dragging and animation.
+  bool IsIdle();
+
+  // Returns true if home launcher should run animation to show. Otherwise,
+  // returns false.
+  bool IsFinalStateShow();
+
+  // Sets up windows that will be used in dragging and animation. If |window| is
+  // not null for kSlideDownToHide mode, it will be set as the window to run
+  // slide down animation. |window| is not used for kSlideUpToShow mode. Returns
+  // true if windows are successfully set up.
+  bool SetUpWindows(Mode mode, aura::Window* window);
+
   Mode mode_ = Mode::kNone;
 
   // The windows we are tracking. They are null if a drag is not underway, or if
@@ -145,6 +165,9 @@
   // Unowned and guaranteed to be non null for the lifetime of this.
   AppListControllerImpl* app_list_controller_;
 
+  // The display where the windows are being processed.
+  display::Display display_;
+
   DISALLOW_COPY_AND_ASSIGN(HomeLauncherGestureHandler);
 };
 
diff --git a/ash/app_list/home_launcher_gesture_handler_unittest.cc b/ash/app_list/home_launcher_gesture_handler_unittest.cc
index 7333e5f..e8f2e58 100644
--- a/ash/app_list/home_launcher_gesture_handler_unittest.cc
+++ b/ash/app_list/home_launcher_gesture_handler_unittest.cc
@@ -54,26 +54,26 @@
 // Tests that the gesture handler will not have a window to act on if there are
 // none in the mru list.
 TEST_F(HomeLauncherGestureHandlerTest, NeedsOneWindowToShow) {
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_FALSE(GetGestureHandler()->window());
 
   auto window = CreateWindowForTesting();
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_TRUE(GetGestureHandler()->window());
 }
 
 // Tests that the gesture handler will not have a window to act on if there are
 // none in the mru list, or if they are not minimized.
 TEST_F(HomeLauncherGestureHandlerTest, NeedsOneMinimizedWindowToHide) {
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeDownToHide);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideDownToHide, gfx::Point());
   EXPECT_FALSE(GetGestureHandler()->window());
 
   auto window = CreateWindowForTesting();
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeDownToHide);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideDownToHide, gfx::Point());
   EXPECT_FALSE(GetGestureHandler()->window());
 
   wm::GetWindowState(window.get())->Minimize();
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeDownToHide);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideDownToHide, gfx::Point());
   EXPECT_TRUE(GetGestureHandler()->window());
 }
 
@@ -90,7 +90,7 @@
   // Test that the most recently activated window is visible, but the others are
   // not.
   ::wm::ActivateWindow(window1.get());
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_TRUE(window1->IsVisible());
   EXPECT_FALSE(window2->IsVisible());
   EXPECT_FALSE(window3->IsVisible());
@@ -104,14 +104,14 @@
 
   // Tests that when cancelling a scroll that was on the bottom half, the window
   // is still visible.
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   GetGestureHandler()->OnScrollEvent(gfx::Point(0, 300));
   GetGestureHandler()->Cancel();
   EXPECT_TRUE(window->IsVisible());
 
   // Tests that when cancelling a scroll that was on the top half, the window is
   // now invisible.
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   GetGestureHandler()->OnScrollEvent(gfx::Point(0, 100));
   GetGestureHandler()->Cancel();
   EXPECT_FALSE(window->IsVisible());
@@ -133,7 +133,7 @@
       window1->transform().To2dTranslation().y();
   const int window2_initial_translation =
       window2->transform().To2dTranslation().y();
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_FALSE(GetGestureHandler()->window());
 
   // Tests that while scrolling the window transform changes.
@@ -154,7 +154,7 @@
 
   // Tests that after releasing on the bottom half, overview mode has been
   // exited, and the two windows have been minimized to show the home launcher.
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   GetGestureHandler()->OnReleaseEvent(gfx::Point(0, 100));
   EXPECT_FALSE(controller->IsSelecting());
   EXPECT_TRUE(wm::GetWindowState(window1.get())->IsMinimized());
@@ -181,7 +181,7 @@
 
   const int window2_initial_translation =
       window2->transform().To2dTranslation().y();
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_EQ(window1.get(), GetGestureHandler()->window());
 
   // Tests that while scrolling the window transforms change.
@@ -201,7 +201,7 @@
 
   // Tests that after releasing on the bottom half, overivew and splitview have
   // both been exited, and both windows are minimized to show the home launcher.
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   GetGestureHandler()->OnReleaseEvent(gfx::Point(0, 100));
   EXPECT_FALSE(window_selector_controller->IsSelecting());
   EXPECT_FALSE(split_view_controller->IsSplitViewModeActive());
@@ -227,7 +227,7 @@
   // Make |window1| the most recent used window. It should be the main window in
   // HomeLauncherGestureHandler.
   ::wm::ActivateWindow(window1.get());
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   EXPECT_EQ(window1.get(), GetGestureHandler()->window());
   EXPECT_EQ(window2.get(), GetGestureHandler()->window2());
 
@@ -245,7 +245,7 @@
 
   // Tests that after releasing on the bottom half, splitview has been ended,
   // and the two windows have been minimized to show the home launcher.
-  GetGestureHandler()->OnPressEvent(Mode::kSwipeUpToShow);
+  GetGestureHandler()->OnPressEvent(Mode::kSlideUpToShow, gfx::Point());
   GetGestureHandler()->OnReleaseEvent(gfx::Point(0, 100));
   EXPECT_FALSE(split_view_controller->IsSplitViewModeActive());
   EXPECT_TRUE(wm::GetWindowState(window1.get())->IsMinimized());
@@ -263,7 +263,7 @@
   std::unique_ptr<aura::Window> CreateWindowForTesting() override {
     std::unique_ptr<aura::Window> window =
         HomeLauncherGestureHandlerTest::CreateWindowForTesting();
-    if (mode_ == Mode::kSwipeDownToHide)
+    if (mode_ == Mode::kSlideDownToHide)
       wm::GetWindowState(window.get())->Minimize();
     return window;
   }
@@ -277,14 +277,14 @@
 
 INSTANTIATE_TEST_CASE_P(,
                         HomeLauncherModeGestureHandlerTest,
-                        testing::Values(Mode::kSwipeDownToHide,
-                                        Mode::kSwipeUpToShow));
+                        testing::Values(Mode::kSlideDownToHide,
+                                        Mode::kSlideUpToShow));
 
 // Tests that the window transform and opacity changes as we scroll.
 TEST_P(HomeLauncherModeGestureHandlerTest, TransformAndOpacityChangesOnScroll) {
   auto window = CreateWindowForTesting();
 
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   ASSERT_TRUE(GetGestureHandler()->window());
 
   // Test that on scrolling to a point on the top half of the work area, the
@@ -312,7 +312,7 @@
   auto window2 = CreateWindowForTesting();
   auto window1 = CreateWindowForTesting();
 
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   ASSERT_TRUE(GetGestureHandler()->window());
   ASSERT_FALSE(window2->IsVisible());
   ASSERT_FALSE(window3->IsVisible());
@@ -327,7 +327,7 @@
   EXPECT_EQ(gfx::Transform(), window1->transform());
   EXPECT_EQ(1.f, window1->layer()->opacity());
 
-  if (mode_ == Mode::kSwipeDownToHide)
+  if (mode_ == Mode::kSlideDownToHide)
     return;
 
   // The other windows return to their original visibility if mode is swiping
@@ -344,7 +344,7 @@
   auto window2 = CreateWindowForTesting();
   auto window1 = CreateWindowForTesting();
 
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   ASSERT_TRUE(GetGestureHandler()->window());
   ASSERT_FALSE(window2->IsVisible());
   ASSERT_FALSE(window3->IsVisible());
@@ -372,7 +372,7 @@
   ::wm::AddTransientChild(parent.get(), child.get());
 
   // |parent| should be the window that is getting hidden.
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   ASSERT_EQ(parent.get(), GetGestureHandler()->window());
 
   // Tests that after scrolling to the halfway point, the transient child's
@@ -394,7 +394,7 @@
 TEST_P(HomeLauncherModeGestureHandlerTest, EndScrollOnTabletModeEnd) {
   auto window = CreateWindowForTesting();
 
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   ASSERT_TRUE(GetGestureHandler()->window());
 
   // Scroll to a point above the halfway mark of the work area.
@@ -421,19 +421,19 @@
   ::wm::ActivateWindow(window1.get());
 
   // For swipe down to hide launcher, all windows must be minimized.
-  if (mode_ == Mode::kSwipeDownToHide) {
+  if (mode_ == Mode::kSlideDownToHide) {
     wm::GetWindowState(window2.get())->Minimize();
     wm::GetWindowState(window1.get())->Minimize();
   }
 
   // Tests that the variables which change when dragging are as expected.
-  GetGestureHandler()->OnPressEvent(mode_);
+  GetGestureHandler()->OnPressEvent(mode_, gfx::Point());
   EXPECT_EQ(window1.get(), GetGestureHandler()->window());
   EXPECT_TRUE(GetGestureHandler()->last_event_location_);
   EXPECT_EQ(mode_, GetGestureHandler()->mode_);
   // We only need to hide windows when swiping up, so this will only be non
   // empty in that case.
-  if (mode_ == Mode::kSwipeUpToShow)
+  if (mode_ == Mode::kSlideUpToShow)
     EXPECT_FALSE(GetGestureHandler()->hidden_windows_.empty());
   EXPECT_FALSE(GetGestureHandler()->transient_descendants_values_.empty());
 
diff --git a/ash/ash_service.cc b/ash/ash_service.cc
index 8d20620..826e810 100644
--- a/ash/ash_service.cc
+++ b/ash/ash_service.cc
@@ -118,7 +118,7 @@
   discardable_shared_memory_manager_ =
       std::make_unique<discardable_memory::DiscardableSharedMemoryManager>();
 
-  gpu_host_ = std::make_unique<ws::gpu_host::DefaultGpuHost>(
+  gpu_host_ = std::make_unique<ws::gpu_host::GpuHost>(
       this, context()->connector(), discardable_shared_memory_manager_.get());
 
   host_frame_sink_manager_ = std::make_unique<viz::HostFrameSinkManager>();
diff --git a/ash/ash_service.h b/ash/ash_service.h
index 800465b5..0a256f1 100644
--- a/ash/ash_service.h
+++ b/ash/ash_service.h
@@ -53,7 +53,7 @@
 class HostContextFactory;
 class InputDeviceController;
 namespace gpu_host {
-class DefaultGpuHost;
+class GpuHost;
 }  // namespace gpu_host
 }  // namespace ws
 
@@ -108,7 +108,7 @@
   std::unique_ptr<discardable_memory::DiscardableSharedMemoryManager>
       discardable_shared_memory_manager_;
 
-  std::unique_ptr<ws::gpu_host::DefaultGpuHost> gpu_host_;
+  std::unique_ptr<ws::gpu_host::GpuHost> gpu_host_;
 
   std::unique_ptr<viz::HostFrameSinkManager> host_frame_sink_manager_;
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 9617ec40..726113e 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -111,6 +111,7 @@
     "tablet_mode.cc",
     "tablet_mode.h",
     "wallpaper_types.h",
+    "window_animation_types.h",
     "window_pin_type.cc",
     "window_pin_type.h",
     "window_properties.cc",
diff --git a/ash/wm/window_animation_types.h b/ash/public/cpp/window_animation_types.h
similarity index 63%
rename from ash/wm/window_animation_types.h
rename to ash/public/cpp/window_animation_types.h
index eb30c35..f5a4f5a 100644
--- a/ash/wm/window_animation_types.h
+++ b/ash/public/cpp/window_animation_types.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_WM_WINDOW_ANIMATION_TYPES_H_
-#define ASH_WM_WINDOW_ANIMATION_TYPES_H_
+#ifndef ASH_PUBLIC_CPP_WINDOW_ANIMATION_TYPES_H_
+#define ASH_PUBLIC_CPP_WINDOW_ANIMATION_TYPES_H_
 
 #include "ui/wm/core/window_animations.h"
 
@@ -17,10 +17,13 @@
   WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE =
       ::wm::WINDOW_VISIBILITY_ANIMATION_MAX,
   // Fade in/out using brightness and grayscale web filters.
-  WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE
+  WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE,
+  // Window slides down from above screen to show and, meanwhile, home launcher
+  // slides down off screen.
+  WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN
 };
 
 }  // namespace wm
 }  // namespace ash
 
-#endif  // ASH_WM_WINDOW_ANIMATION_TYPES_H_
+#endif  // ASH_PUBLIC_CPP_WINDOW_ANIMATION_TYPES_H_
diff --git a/ash/shelf/app_list_shelf_item_delegate.cc b/ash/shelf/app_list_shelf_item_delegate.cc
index 1ca93e52..1c77f646 100644
--- a/ash/shelf/app_list_shelf_item_delegate.cc
+++ b/ash/shelf/app_list_shelf_item_delegate.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "ash/app_list/app_list_controller_impl.h"
+#include "ash/app_list/home_launcher_gesture_handler.h"
 #include "ash/public/cpp/app_list/app_list_constants.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/shelf_model.h"
@@ -18,6 +19,7 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
+#include "ui/display/manager/display_manager.h"
 
 namespace ash {
 
@@ -39,41 +41,51 @@
     return;
   }
 
-  // Whether to perform the "back" action for the app list. It will only be
-  // performed if other actions are not performed.
-  bool back_action = true;
+  // Whether the this action is handled.
+  bool handled = false;
 
-  // End overview mode.
-  if (Shell::Get()->window_selector_controller()->IsSelecting()) {
-    Shell::Get()->window_selector_controller()->ToggleOverview(
-        WindowSelector::EnterExitOverviewType::kWindowsMinimized);
-    back_action = false;
+  HomeLauncherGestureHandler* home_launcher_gesture_handler =
+      Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
+  if (home_launcher_gesture_handler) {
+    handled = home_launcher_gesture_handler->ShowHomeLauncher(
+        Shell::Get()->display_manager()->GetDisplayForId(display_id));
   }
 
-  // End split view mode.
-  if (Shell::Get()->split_view_controller()->IsSplitViewModeActive()) {
-    Shell::Get()->split_view_controller()->EndSplitView(
-        SplitViewController::EndReason::kHomeLauncherPressed);
-    back_action = false;
-  }
-
-  // Minimize all windows that aren't the app list in reverse order to preserve
-  // the mru ordering.
-  aura::Window* app_list_container =
-      Shell::Get()->GetPrimaryRootWindow()->GetChildById(
-          kShellWindowId_AppListTabletModeContainer);
-  aura::Window::Windows windows =
-      Shell::Get()->mru_window_tracker()->BuildWindowForCycleList();
-  std::reverse(windows.begin(), windows.end());
-  for (auto* window : windows) {
-    if (!app_list_container->Contains(window) &&
-        !wm::GetWindowState(window)->IsMinimized()) {
-      wm::GetWindowState(window)->Minimize();
-      back_action = false;
+  if (!handled) {
+    if (Shell::Get()->window_selector_controller()->IsSelecting()) {
+      // End overview mode.
+      Shell::Get()->window_selector_controller()->ToggleOverview(
+          WindowSelector::EnterExitOverviewType::kWindowsMinimized);
+      handled = true;
+    }
+    if (Shell::Get()->split_view_controller()->IsSplitViewModeActive()) {
+      // End split view mode.
+      Shell::Get()->split_view_controller()->EndSplitView(
+          SplitViewController::EndReason::kHomeLauncherPressed);
+      handled = true;
     }
   }
 
-  if (back_action)
+  if (!handled) {
+    // Minimize all windows that aren't the app list in reverse order to
+    // preserve the mru ordering.
+    aura::Window* app_list_container =
+        Shell::Get()->GetPrimaryRootWindow()->GetChildById(
+            kShellWindowId_AppListTabletModeContainer);
+    aura::Window::Windows windows =
+        Shell::Get()->mru_window_tracker()->BuildWindowForCycleList();
+    std::reverse(windows.begin(), windows.end());
+    for (auto* window : windows) {
+      if (!app_list_container->Contains(window) &&
+          !wm::GetWindowState(window)->IsMinimized()) {
+        wm::GetWindowState(window)->Minimize();
+        handled = true;
+      }
+    }
+  }
+
+  // Perform the "back" action for the app list.
+  if (!handled)
     Shell::Get()->app_list_controller()->Back();
 
   std::move(callback).Run(SHELF_ACTION_APP_LIST_SHOWN, base::nullopt);
diff --git a/ash/shelf/app_list_shelf_item_delegate_unittest.cc b/ash/shelf/app_list_shelf_item_delegate_unittest.cc
index 691fdd1..076d330a 100644
--- a/ash/shelf/app_list_shelf_item_delegate_unittest.cc
+++ b/ash/shelf/app_list_shelf_item_delegate_unittest.cc
@@ -49,8 +49,8 @@
   std::unique_ptr<ui::Event> test_event = std::make_unique<ui::KeyEvent>(
       ui::EventType::ET_MOUSE_PRESSED, ui::VKEY_UNKNOWN, ui::EF_NONE);
   delegate()->ItemSelected(
-      std::move(test_event),
-      /*display_id=*/0, ShelfLaunchSource::LAUNCH_FROM_UNKNOWN,
+      std::move(test_event), GetPrimaryDisplay().id(),
+      ShelfLaunchSource::LAUNCH_FROM_UNKNOWN,
       base::BindOnce(
           [](ash::ShelfAction, base::Optional<ash::MenuItemList>) {}));
   ASSERT_TRUE(wm::GetWindowState(w1.get())->IsMinimized());
diff --git a/ash/shelf/shelf.cc b/ash/shelf/shelf.cc
index 19adaee..ab65fc36 100644
--- a/ash/shelf/shelf.cc
+++ b/ash/shelf/shelf.cc
@@ -209,7 +209,7 @@
              : 0;
 }
 
-gfx::Rect Shelf::GetIdealBounds() const {
+gfx::Rect Shelf::GetIdealBounds() {
   return shelf_layout_manager_->GetIdealBounds();
 }
 
@@ -298,21 +298,6 @@
   return GetStatusAreaWidget()->GetSystemTrayAnchor();
 }
 
-gfx::Rect Shelf::GetSystemTrayAnchorRect() const {
-  gfx::Rect shelf_bounds = GetIdealBounds();
-  switch (alignment_) {
-    case SHELF_ALIGNMENT_BOTTOM:
-    case SHELF_ALIGNMENT_BOTTOM_LOCKED:
-      return gfx::Rect(shelf_bounds.right(), shelf_bounds.y(), 0, 0);
-    case SHELF_ALIGNMENT_LEFT:
-      return gfx::Rect(shelf_bounds.right(), shelf_bounds.bottom(), 0, 0);
-    case SHELF_ALIGNMENT_RIGHT:
-      return gfx::Rect(shelf_bounds.x(), shelf_bounds.bottom(), 0, 0);
-  }
-  NOTREACHED();
-  return gfx::Rect();
-}
-
 bool Shelf::ShouldHideOnSecondaryDisplay(session_manager::SessionState state) {
   if (Shell::GetPrimaryRootWindowController()->shelf() == this)
     return false;
diff --git a/ash/shelf/shelf.h b/ash/shelf/shelf.h
index 1bd44f4..9a6418d 100644
--- a/ash/shelf/shelf.h
+++ b/ash/shelf/shelf.h
@@ -102,7 +102,7 @@
   int GetDockedMagnifierHeight() const;
 
   // Returns the ideal bounds of the shelf assuming it is visible.
-  gfx::Rect GetIdealBounds() const;
+  gfx::Rect GetIdealBounds();
 
   gfx::Rect GetUserWorkAreaBounds() const;
 
@@ -140,12 +140,6 @@
   // bubble will be anchored. See also: StatusAreaWidget::GetSystemTrayAnchor()
   TrayBackgroundView* GetSystemTrayAnchor() const;
 
-  // Get the anchor rect that the system tray bubble and the notification center
-  // bubble will be anchored.
-  // x() and y() designates anchor point, but width() and height() are dummy.
-  // See also: BubbleDialogDelegateView::GetBubbleBounds()
-  gfx::Rect GetSystemTrayAnchorRect() const;
-
   void set_is_tablet_mode_animation_running(bool value) {
     is_tablet_mode_animation_running_ = value;
   }
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 9e84ac4..59eef685 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -209,7 +209,7 @@
            state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
 }
 
-gfx::Rect ShelfLayoutManager::GetIdealBounds() const {
+gfx::Rect ShelfLayoutManager::GetIdealBounds() {
   const int shelf_size = ShelfConstants::shelf_size();
   aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
   gfx::Rect rect(screen_util::GetDisplayBoundsInParent(shelf_window));
@@ -1214,7 +1214,8 @@
         Shell::Get()->app_list_controller()->home_launcher_gesture_handler();
     if (home_launcher_handler && visibility_state() == SHELF_VISIBLE &&
         home_launcher_handler->OnPressEvent(
-            HomeLauncherGestureHandler::Mode::kSwipeUpToShow)) {
+            HomeLauncherGestureHandler::Mode::kSlideUpToShow,
+            gesture_in_screen.location())) {
       gesture_drag_status_ = GESTURE_DRAG_APPLIST_IN_PROGRESS;
       return;
     }
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 7bf88b9f..10b7de5 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -80,7 +80,7 @@
   bool IsVisible() const;
 
   // Returns the ideal bounds of the shelf assuming it is visible.
-  gfx::Rect GetIdealBounds() const;
+  gfx::Rect GetIdealBounds();
 
   // Returns the preferred size of the shelf for the target visibility state.
   gfx::Size GetPreferredSize();
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index 275207ee..162dfc02d 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -231,11 +231,6 @@
   auto layout = std::make_unique<BottomAlignedBoxLayout>(this);
   layout->SetDefaultFlex(1);
   layout_ = SetLayoutManager(std::move(layout));
-
-  if (init_params.anchor_mode == AnchorMode::kRect) {
-    SetAnchorView(nullptr);
-    SetAnchorRect(init_params.anchor_rect);
-  }
 }
 
 TrayBubbleView::~TrayBubbleView() {
@@ -311,20 +306,9 @@
 }
 
 void TrayBubbleView::ChangeAnchorView(views::View* anchor_view) {
-  DCHECK(params_.anchor_mode == AnchorMode::kView);
   BubbleDialogDelegateView::SetAnchorView(anchor_view);
 }
 
-void TrayBubbleView::ChangeAnchorRect(const gfx::Rect& rect) {
-  DCHECK(params_.anchor_mode == AnchorMode::kRect);
-  BubbleDialogDelegateView::SetAnchorRect(rect);
-}
-
-void TrayBubbleView::ChangeAnchorAlignment(
-    TrayBubbleView::AnchorAlignment alignment) {
-  SetArrow(GetArrowAlignment(alignment));
-}
-
 int TrayBubbleView::GetDialogButtons() const {
   return ui::DIALOG_BUTTON_NONE;
 }
diff --git a/ash/system/tray/tray_bubble_view.h b/ash/system/tray/tray_bubble_view.h
index f3c78eabf..22954c9 100644
--- a/ash/system/tray/tray_bubble_view.h
+++ b/ash/system/tray/tray_bubble_view.h
@@ -12,7 +12,6 @@
 #include "base/optional.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/events/event.h"
-#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/mouse_watcher.h"
@@ -86,23 +85,12 @@
     DISALLOW_COPY_AND_ASSIGN(Delegate);
   };
 
-  // Anchor mode being set at creation.
-  enum class AnchorMode {
-    // Anchor to |anchor_view|. This is the default.
-    kView,
-    // Anchor to |anchor_rect|. Used for anchoring to the shelf.
-    kRect
-  };
-
   struct ASH_EXPORT InitParams {
     InitParams();
     InitParams(const InitParams& other);
     Delegate* delegate = nullptr;
     gfx::NativeWindow parent_window = nullptr;
     View* anchor_view = nullptr;
-    AnchorMode anchor_mode = AnchorMode::kView;
-    // Only used if anchor_mode == AnchorMode::kRect.
-    gfx::Rect anchor_rect;
     AnchorAlignment anchor_alignment = ANCHOR_ALIGNMENT_BOTTOM;
     int min_width = 0;
     int max_width = 0;
@@ -147,16 +135,8 @@
   void ResetDelegate();
 
   // Anchors the bubble to |anchor_view|.
-  // Only eligible if anchor_mode == AnchorMode::kView.
   void ChangeAnchorView(views::View* anchor_view);
 
-  // Anchors the bubble to |anchor_rect|. Exclusive with ChangeAnchorView().
-  // Only eligible if anchor_mode == AnchorMode::kRect.
-  void ChangeAnchorRect(const gfx::Rect& anchor_rect);
-
-  // Change anchor alignment mode when anchoring either the rect or view.
-  void ChangeAnchorAlignment(AnchorAlignment alignment);
-
   Delegate* delegate() { return delegate_; }
 
   void set_gesture_dragging(bool dragging) { is_gesture_dragging_ = dragging; }
diff --git a/ash/system/unified/unified_system_tray_bubble.cc b/ash/system/unified/unified_system_tray_bubble.cc
index 1b6fe33..a6ca4b62 100644
--- a/ash/system/unified/unified_system_tray_bubble.cc
+++ b/ash/system/unified/unified_system_tray_bubble.cc
@@ -94,8 +94,6 @@
   init_params.parent_window = tray->GetBubbleWindowContainer();
   init_params.anchor_view =
       tray->shelf()->GetSystemTrayAnchor()->GetBubbleAnchor();
-  init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
-  init_params.anchor_rect = tray->shelf()->GetSystemTrayAnchorRect();
   init_params.corner_radius = kUnifiedTrayCornerRadius;
   init_params.has_shadow = false;
   init_params.show_by_click = show_by_click;
@@ -303,8 +301,22 @@
   int max_height = CalculateMaxHeight();
   unified_view_->SetMaxHeight(max_height);
   bubble_view_->SetMaxHeight(max_height);
-  bubble_view_->ChangeAnchorAlignment(tray_->GetAnchorAlignment());
-  bubble_view_->ChangeAnchorRect(tray_->shelf()->GetSystemTrayAnchorRect());
+  // If the bubble is open while switching to and from tablet mode, change the
+  // bubble anchor if needed. The new anchor view may also have a translation
+  // applied to it so shift the bubble bounds so that it appears in the correct
+  // location.
+  bubble_view_->ChangeAnchorView(
+      tray_->shelf()->GetSystemTrayAnchor()->GetBubbleAnchor());
+  gfx::Rect bounds =
+      bubble_view_->GetWidget()->GetNativeWindow()->GetBoundsInScreen();
+  const gfx::Vector2dF translation = tray_->shelf()
+                                         ->GetSystemTrayAnchor()
+                                         ->layer()
+                                         ->transform()
+                                         .To2dTranslation();
+  bounds.set_x(bounds.x() - translation.x());
+  bounds.set_y(bounds.y() - translation.y());
+  bubble_view_->GetWidget()->GetNativeWindow()->SetBounds(bounds);
 }
 
 void UnifiedSystemTrayBubble::CreateBlurLayerForAnimation() {
diff --git a/ash/wallpaper/wallpaper_view.cc b/ash/wallpaper/wallpaper_view.cc
index 190eaa8..fa9b2468 100644
--- a/ash/wallpaper/wallpaper_view.cc
+++ b/ash/wallpaper/wallpaper_view.cc
@@ -6,13 +6,13 @@
 
 #include "ash/public/cpp/login_constants.h"
 #include "ash/public/cpp/wallpaper_types.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/overview/window_selector_controller.h"
-#include "ash/wm/window_animation_types.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
diff --git a/ash/wm/base_state.cc b/ash/wm/base_state.cc
index 6c9fab23..512d3e3 100644
--- a/ash/wm/base_state.cc
+++ b/ash/wm/base_state.cc
@@ -4,11 +4,11 @@
 
 #include "ash/wm/base_state.h"
 
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/wm/splitview/split_view_controller.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/wm_event.h"
 #include "ui/aura/client/aura_constants.h"
diff --git a/ash/wm/client_controlled_state.cc b/ash/wm/client_controlled_state.cc
index 1d0843cd..8bfc26c 100644
--- a/ash/wm/client_controlled_state.cc
+++ b/ash/wm/client_controlled_state.cc
@@ -5,12 +5,12 @@
 #include "ash/wm/client_controlled_state.h"
 
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/wm/screen_pinning_controller.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_parenting_utils.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
diff --git a/ash/wm/default_state.cc b/ash/wm/default_state.cc
index bedaf47..757c4bd2 100644
--- a/ash/wm/default_state.cc
+++ b/ash/wm/default_state.cc
@@ -5,12 +5,12 @@
 #include "ash/wm/default_state.h"
 
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/wm/screen_pinning_controller.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_parenting_utils.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
diff --git a/ash/wm/lock_window_state.cc b/ash/wm/lock_window_state.cc
index 14b3689..2946794 100644
--- a/ash/wm/lock_window_state.cc
+++ b/ash/wm/lock_window_state.cc
@@ -7,10 +7,10 @@
 #include <memory>
 #include <utility>
 
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/screen_util.h"
 #include "ash/shelf/shelf.h"
 #include "ash/wm/lock_layout_manager.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_state_delegate.h"
 #include "ash/wm/window_state_util.h"
diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc
index 92b29f4..d14557d 100644
--- a/ash/wm/overview/window_selector.cc
+++ b/ash/wm/overview/window_selector.cc
@@ -728,15 +728,13 @@
 }
 
 void WindowSelector::UpdateGridAtLocationYPositionAndOpacity(
-    const gfx::Point& location,
+    int64_t display_id,
     int new_y,
     float opacity,
     const gfx::Rect& work_area,
     UpdateAnimationSettingsCallback callback) {
-  WindowGrid* grid =
-      GetGridWithRootWindow(display::Screen::GetScreen()
-                                ->GetWindowAtScreenPoint(location)
-                                ->GetRootWindow());
+  WindowGrid* grid = GetGridWithRootWindow(
+      ash::Shell::Get()->GetRootWindowForDisplayId(display_id));
   if (grid)
     grid->UpdateYPositionAndOpacity(new_y, opacity, work_area, callback);
 }
diff --git a/ash/wm/overview/window_selector.h b/ash/wm/overview/window_selector.h
index abd7427..46be49f 100644
--- a/ash/wm/overview/window_selector.h
+++ b/ash/wm/overview/window_selector.h
@@ -208,7 +208,7 @@
 
   // Shifts and fades the grid in |grid_list_| associated with |location|.
   void UpdateGridAtLocationYPositionAndOpacity(
-      const gfx::Point& location,
+      int64_t display_id,
       int new_y,
       float opacity,
       const gfx::Rect& work_area,
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index e6f3615..f27e2328 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -3202,7 +3202,7 @@
   EndScrollSequence(
       start, 10, timestamp, window.get(), /*is_fling=*/true,
       /*velocity_y=*/
-      TabletModeAppWindowDragController::kFlingToOverviewThreshold - 10.f);
+      TabletModeWindowDragDelegate::kFlingToOverviewThreshold - 10.f);
   EXPECT_FALSE(window_selector_controller->IsSelecting());
 
   // FLING the window with large veloicty (larger than
@@ -3214,7 +3214,7 @@
   EndScrollSequence(
       start, 10, timestamp, window.get(), /*is_fling=*/true,
       /*velocity_y=*/
-      TabletModeAppWindowDragController::kFlingToOverviewThreshold + 10.f);
+      TabletModeWindowDragDelegate::kFlingToOverviewThreshold + 10.f);
   EXPECT_TRUE(window_selector_controller->IsSelecting());
 }
 
@@ -3341,12 +3341,12 @@
       split_view_controller()->GetDisplayWorkAreaBoundsInScreen(window.get());
 
   const float long_scroll_delta = display_bounds.height() / 4 + 5;
-  float large_velocity = TabletModeAppWindowDragController::
-                             kFlingToOverviewFromSnappingAreaThreshold +
-                         10.f;
-  float small_velocity = TabletModeAppWindowDragController::
-                             kFlingToOverviewFromSnappingAreaThreshold -
-                         10.f;
+  float large_velocity =
+      TabletModeWindowDragDelegate::kFlingToOverviewFromSnappingAreaThreshold +
+      10.f;
+  float small_velocity =
+      TabletModeWindowDragDelegate::kFlingToOverviewFromSnappingAreaThreshold -
+      10.f;
   gfx::Point start;
   base::TimeTicks timestamp = base::TimeTicks::Now();
 
@@ -3412,9 +3412,9 @@
   gfx::Rect display_bounds =
       split_view_controller()->GetDisplayWorkAreaBoundsInScreen(window1.get());
   const float long_scroll_y = display_bounds.bottom() - 10;
-  float large_velocity = TabletModeAppWindowDragController::
-                             kFlingToOverviewFromSnappingAreaThreshold +
-                         10.f;
+  float large_velocity =
+      TabletModeWindowDragDelegate::kFlingToOverviewFromSnappingAreaThreshold +
+      10.f;
   const gfx::Point start;
 
   // Fling the window in left snapping area to left should still snap the
diff --git a/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.cc b/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.cc
index 6a05e8f..5e69034 100644
--- a/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.cc
@@ -5,27 +5,17 @@
 #include "ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h"
 
 #include "ash/shell.h"
-#include "ash/wm/overview/window_grid.h"
-#include "ash/wm/overview/window_selector_controller.h"
-#include "ash/wm/overview/window_selector_item.h"
 #include "ash/wm/splitview/split_view_drag_indicators.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h"
 #include "ash/wm/window_state.h"
 #include "ui/base/hit_test.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
 
 namespace {
 
-// Return the location of |event| in screen coordinates.
-gfx::Point GetEventLocationInScreen(const ui::GestureEvent* event) {
-  gfx::Point location_in_screen(event->location());
-  ::wm::ConvertPointToScreen(static_cast<aura::Window*>(event->target()),
-                             &location_in_screen);
-  return location_in_screen;
-}
-
 // The drag delegate for app windows. It not only includes the logic in
 // TabletModeWindowDragDelegate, but also has special logic for app windows.
 class TabletModeAppWindowDragDelegate : public TabletModeWindowDragDelegate {
@@ -63,7 +53,8 @@
 
 bool TabletModeAppWindowDragController::DragWindowFromTop(
     ui::GestureEvent* event) {
-  previous_location_in_screen_ = GetEventLocationInScreen(event);
+  previous_location_in_screen_ =
+      drag_delegate_->GetEventLocationInScreen(event);
   if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN)
     return StartWindowDrag(event);
 
@@ -75,12 +66,16 @@
     return true;
   }
 
-  if (event->type() == ui::ET_GESTURE_SCROLL_END ||
-      event->type() == ui::ET_SCROLL_FLING_START) {
+  if (event->type() == ui::ET_GESTURE_SCROLL_END) {
     EndWindowDrag(event, wm::WmToplevelWindowEventHandler::DragResult::SUCCESS);
     return true;
   }
 
+  if (event->type() == ui::ET_SCROLL_FLING_START) {
+    FlingOrSwipe(event);
+    return true;
+  }
+
   EndWindowDrag(event, wm::WmToplevelWindowEventHandler::DragResult::REVERT);
   return false;
 }
@@ -92,8 +87,9 @@
   if (!widget)
     return false;
 
-  drag_delegate_->StartWindowDrag(widget->GetNativeWindow(),
-                                  GetEventLocationInScreen(event));
+  drag_delegate_->StartWindowDrag(
+      widget->GetNativeWindow(),
+      drag_delegate_->GetEventLocationInScreen(event));
   return true;
 }
 
@@ -101,68 +97,19 @@
     ui::GestureEvent* event) {
   // Update the dragged window's tranform during dragging.
   drag_delegate_->ContinueWindowDrag(
-      GetEventLocationInScreen(event),
+      drag_delegate_->GetEventLocationInScreen(event),
       TabletModeWindowDragDelegate::UpdateDraggedWindowType::UPDATE_TRANSFORM);
 }
 
-bool TabletModeAppWindowDragController::ShouldFlingIntoOverview(
-    ui::GestureEvent* event) {
-  if (event->type() != ui::ET_SCROLL_FLING_START)
-    return false;
-
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
-  const gfx::Point location_in_screen = GetEventLocationInScreen(event);
-  const IndicatorState indicator_state =
-      drag_delegate_->GetIndicatorState(location_in_screen);
-  const bool is_landscape =
-      split_view_controller->IsCurrentScreenOrientationLandscape();
-  const float velocity = is_landscape ? event->details().velocity_x()
-                                      : event->details().velocity_y();
-
-  // Drop the window into overview if fling with large enough velocity to the
-  // opposite snap position when preview area is shown.
-  if (split_view_controller->IsCurrentScreenOrientationPrimary()) {
-    if (indicator_state == IndicatorState::kPreviewAreaLeft)
-      return velocity > kFlingToOverviewFromSnappingAreaThreshold;
-    else if (indicator_state == IndicatorState::kPreviewAreaRight)
-      return -velocity > kFlingToOverviewFromSnappingAreaThreshold;
-  } else {
-    if (indicator_state == IndicatorState::kPreviewAreaLeft)
-      return -velocity > kFlingToOverviewFromSnappingAreaThreshold;
-    else if (indicator_state == IndicatorState::kPreviewAreaRight)
-      return velocity > kFlingToOverviewFromSnappingAreaThreshold;
-  }
-
-  const SplitViewController::State snap_state = split_view_controller->state();
-  const int end_position =
-      is_landscape ? location_in_screen.x() : location_in_screen.y();
-  // Fling the window when splitview is active. Since each snapping area in
-  // splitview has a corresponding snap position. Fling the window to the
-  // opposite position of the area's snap position with large enough velocity
-  // should drop the window into overview grid.
-  if (snap_state == SplitViewController::LEFT_SNAPPED ||
-      snap_state == SplitViewController::RIGHT_SNAPPED) {
-    return end_position > split_view_controller->divider_position()
-               ? -velocity > kFlingToOverviewFromSnappingAreaThreshold
-               : velocity > kFlingToOverviewFromSnappingAreaThreshold;
-  }
-
-  // Consider only the velocity_y if splitview is not active and preview area is
-  // not shown.
-  return event->details().velocity_y() > kFlingToOverviewThreshold;
-}
-
 void TabletModeAppWindowDragController::EndWindowDrag(
     ui::GestureEvent* event,
     wm::WmToplevelWindowEventHandler::DragResult result) {
-  if (ShouldFlingIntoOverview(event)) {
-    DCHECK(Shell::Get()->window_selector_controller()->IsSelecting());
-    Shell::Get()->window_selector_controller()->window_selector()->AddItem(
-        drag_delegate_->dragged_window(), /*reposition=*/true,
-        /*animate=*/false);
-  }
-  drag_delegate_->EndWindowDrag(result, GetEventLocationInScreen(event));
+  drag_delegate_->EndWindowDrag(
+      result, drag_delegate_->GetEventLocationInScreen(event));
+}
+
+void TabletModeAppWindowDragController::FlingOrSwipe(ui::GestureEvent* event) {
+  drag_delegate_->FlingOrSwipe(event);
 }
 
 void TabletModeAppWindowDragController::OnDisplayMetricsChanged(
diff --git a/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h b/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h
index 21d3ce3..6875cf83 100644
--- a/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h
@@ -21,14 +21,6 @@
 class ASH_EXPORT TabletModeAppWindowDragController
     : public display::DisplayObserver {
  public:
-  // Threshold of the fling velocity to drop the dragged window into overview if
-  // fling from the top of the display.
-  static constexpr float kFlingToOverviewThreshold = 2000.f;
-
-  // Threshold of the fling velocity to drop the dragged window into overview if
-  // fling inside preview area or when splitview is active.
-  static constexpr float kFlingToOverviewFromSnappingAreaThreshold = 1000.f;
-
   TabletModeAppWindowDragController();
   ~TabletModeAppWindowDragController() override;
 
@@ -48,9 +40,7 @@
   void UpdateWindowDrag(ui::GestureEvent* event);
   void EndWindowDrag(ui::GestureEvent* event,
                      wm::WmToplevelWindowEventHandler::DragResult result);
-
-  // Returns true if fling event should drop the window into overview grid.
-  bool ShouldFlingIntoOverview(ui::GestureEvent* event);
+  void FlingOrSwipe(ui::GestureEvent* event);
 
   // display::DisplayObserver:
   void OnDisplayMetricsChanged(const display::Display& display,
diff --git a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_controller.cc b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_controller.cc
index 8c16b95..06afd86 100644
--- a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_controller.cc
@@ -80,10 +80,7 @@
 
 void TabletModeBrowserWindowDragController::FlingOrSwipe(
     ui::GestureEvent* event) {
-  // TODO(xdai): Add special logic for fling events.
-  drag_delegate_->EndWindowDrag(
-      wm::WmToplevelWindowEventHandler::DragResult::SUCCESS,
-      previous_location_in_screen_);
+  drag_delegate_->FlingOrSwipe(event);
 }
 
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index f168d093..66b42bfc 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -216,6 +216,24 @@
   did_move_ = false;
 }
 
+void TabletModeWindowDragDelegate::FlingOrSwipe(ui::GestureEvent* event) {
+  if (ShouldFlingIntoOverview(event)) {
+    DCHECK(Shell::Get()->window_selector_controller()->IsSelecting());
+    Shell::Get()->window_selector_controller()->window_selector()->AddItem(
+        dragged_window_, /*reposition=*/true, /*animate=*/false);
+  }
+  EndWindowDrag(wm::WmToplevelWindowEventHandler::DragResult::SUCCESS,
+                GetEventLocationInScreen(event));
+}
+
+gfx::Point TabletModeWindowDragDelegate::GetEventLocationInScreen(
+    const ui::GestureEvent* event) const {
+  gfx::Point location_in_screen(event->location());
+  ::wm::ConvertPointToScreen(static_cast<aura::Window*>(event->target()),
+                             &location_in_screen);
+  return location_in_screen;
+}
+
 IndicatorState TabletModeWindowDragDelegate::GetIndicatorState(
     const gfx::Point& location_in_screen) const {
   SplitViewController::SnapPosition snap_position =
@@ -353,4 +371,49 @@
   SetTransform(dragged_window_, transform);
 }
 
+bool TabletModeWindowDragDelegate::ShouldFlingIntoOverview(
+    const ui::GestureEvent* event) const {
+  if (event->type() != ui::ET_SCROLL_FLING_START)
+    return false;
+
+  const gfx::Point location_in_screen = GetEventLocationInScreen(event);
+  const IndicatorState indicator_state = GetIndicatorState(location_in_screen);
+  const bool is_landscape =
+      split_view_controller_->IsCurrentScreenOrientationLandscape();
+  const float velocity = is_landscape ? event->details().velocity_x()
+                                      : event->details().velocity_y();
+
+  // Drop the window into overview if fling with large enough velocity to the
+  // opposite snap position when preview area is shown.
+  if (split_view_controller_->IsCurrentScreenOrientationPrimary()) {
+    if (indicator_state == IndicatorState::kPreviewAreaLeft)
+      return velocity > kFlingToOverviewFromSnappingAreaThreshold;
+    else if (indicator_state == IndicatorState::kPreviewAreaRight)
+      return -velocity > kFlingToOverviewFromSnappingAreaThreshold;
+  } else {
+    if (indicator_state == IndicatorState::kPreviewAreaLeft)
+      return -velocity > kFlingToOverviewFromSnappingAreaThreshold;
+    else if (indicator_state == IndicatorState::kPreviewAreaRight)
+      return velocity > kFlingToOverviewFromSnappingAreaThreshold;
+  }
+
+  const SplitViewController::State snap_state = split_view_controller_->state();
+  const int end_position =
+      is_landscape ? location_in_screen.x() : location_in_screen.y();
+  // Fling the window when splitview is active. Since each snapping area in
+  // splitview has a corresponding snap position. Fling the window to the
+  // opposite position of the area's snap position with large enough velocity
+  // should drop the window into overview grid.
+  if (snap_state == SplitViewController::LEFT_SNAPPED ||
+      snap_state == SplitViewController::RIGHT_SNAPPED) {
+    return end_position > split_view_controller_->divider_position()
+               ? -velocity > kFlingToOverviewFromSnappingAreaThreshold
+               : velocity > kFlingToOverviewFromSnappingAreaThreshold;
+  }
+
+  // Consider only the velocity_y if splitview is not active and preview area is
+  // not shown.
+  return event->details().velocity_y() > kFlingToOverviewThreshold;
+}
+
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
index 44e27b8f4..4369b81 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
@@ -30,6 +30,14 @@
   // into overview.
   static constexpr float kDragPositionToOverviewRatio = 0.5f;
 
+  // Threshold of the fling velocity to drop the dragged window into overview if
+  // fling from the top of the display or from the caption area of the window.
+  static constexpr float kFlingToOverviewThreshold = 2000.f;
+
+  // Threshold of the fling velocity to drop the dragged window into overview if
+  // fling inside preview area or when splitview is active.
+  static constexpr float kFlingToOverviewFromSnappingAreaThreshold = 1000.f;
+
   enum class UpdateDraggedWindowType {
     UPDATE_BOUNDS,
     UPDATE_TRANSFORM,
@@ -54,6 +62,12 @@
   void EndWindowDrag(wm::WmToplevelWindowEventHandler::DragResult result,
                      const gfx::Point& location_in_screen);
 
+  // Calls when a window ends dragging because of fling or swipe.
+  void FlingOrSwipe(ui::GestureEvent* event);
+
+  // Return the location of |event| in screen coordinates.
+  gfx::Point GetEventLocationInScreen(const ui::GestureEvent* event) const;
+
   // Returns the IndicatorState according to |location_in_screen|.
   IndicatorState GetIndicatorState(const gfx::Point& location_in_screen) const;
 
@@ -95,6 +109,9 @@
       SplitViewController::SnapPosition snap_position,
       int end_y_position_in_screen) const;
 
+  // Returns true if fling event should drop the window into overview grid.
+  bool ShouldFlingIntoOverview(const ui::GestureEvent* event) const;
+
   SplitViewController* const split_view_controller_;
 
   // A widget to display the drag indicators and preview window.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index 5998d3ad..a3153f5 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/screen_util.h"
@@ -15,7 +16,6 @@
 #include "ash/wm/screen_pinning_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_properties.h"
 #include "ash/wm/window_state_util.h"
 #include "ash/wm/window_util.h"
diff --git a/ash/wm/window_animations.cc b/ash/wm/window_animations.cc
index 232a16e..7f6defd 100644
--- a/ash/wm/window_animations.cc
+++ b/ash/wm/window_animations.cc
@@ -10,8 +10,11 @@
 #include <utility>
 #include <vector>
 
+#include "ash/app_list/app_list_controller_impl.h"
+#include "ash/app_list/home_launcher_gesture_handler.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/shelf/shelf.h"
-#include "ash/wm/window_animation_types.h"
+#include "ash/shell.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/workspace_controller.h"
 #include "base/i18n/rtl.h"
@@ -228,6 +231,38 @@
   AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
 }
 
+void AnimateShowWindow_SlideDown(aura::Window* window) {
+  AppListControllerImpl* app_list_controller =
+      Shell::Get()->app_list_controller();
+
+  if (app_list_controller &&
+      app_list_controller->IsHomeLauncherEnabledInTabletMode()) {
+    // Slide down the window from above screen to show and, meanwhile, slide
+    // down the home launcher off screen.
+    HomeLauncherGestureHandler* handler =
+        app_list_controller->home_launcher_gesture_handler();
+    if (handler &&
+        handler->HideHomeLauncherForWindow(
+            display::Screen::GetScreen()->GetDisplayNearestView(window),
+            window)) {
+      // Now that the window has been restored, we need to clear its animation
+      // style to default so that normal animation applies.
+      ::wm::SetWindowVisibilityAnimationType(
+          window, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
+      return;
+    }
+  }
+
+  // Fallback to use minimize animation.
+  AnimateShowWindow_Minimize(window);
+}
+
+void AnimateHideWindow_SlideDown(aura::Window* window) {
+  // The hide animation should be handled in HomeLauncherGestureHandler, so
+  // fallback to use minimize animation here.
+  AnimateHideWindow_Minimize(window);
+}
+
 bool AnimateShowWindow(aura::Window* window) {
   if (!::wm::HasWindowVisibilityAnimationTransition(window,
                                                     ::wm::ANIMATE_SHOW)) {
@@ -241,6 +276,9 @@
     case wm::WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
       AnimateShowWindow_BrightnessGrayscale(window);
       return true;
+    case wm::WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN:
+      AnimateShowWindow_SlideDown(window);
+      return true;
     default:
       NOTREACHED();
       return false;
@@ -260,6 +298,9 @@
     case wm::WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
       AnimateHideWindow_BrightnessGrayscale(window);
       return true;
+    case wm::WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN:
+      AnimateHideWindow_SlideDown(window);
+      return true;
     default:
       NOTREACHED();
       return false;
diff --git a/ash/wm/window_animations_unittest.cc b/ash/wm/window_animations_unittest.cc
index 212de69..5617b9b 100644
--- a/ash/wm/window_animations_unittest.cc
+++ b/ash/wm/window_animations_unittest.cc
@@ -5,8 +5,8 @@
 #include "ash/wm/window_animations.h"
 
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/window_animation_types.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/workspace_controller.h"
 #include "base/time/time.h"
diff --git a/base/containers/README.md b/base/containers/README.md
index e5218815..e788262b 100644
--- a/base/containers/README.md
+++ b/base/containers/README.md
@@ -65,7 +65,7 @@
 | `base::flat_map`, `base::flat_set`         | 24 bytes              | 0 (see notes)     | No                |
 | `base::small_map`                          | 24 bytes (see notes)  | 32 bytes          | No                |
 
-**Takeaways:** `std::unordered_map` and `std::unordered_map` have high
+**Takeaways:** `std::unordered_map` and `std::unordered_set` have high
 overhead for small container sizes, so prefer these only for larger workloads.
 
 Code size comparisons for a block of code (see appendix) on Windows using
diff --git a/base/mac/sdk_forward_declarations.h b/base/mac/sdk_forward_declarations.h
index a4549e1..86de4d83 100644
--- a/base/mac/sdk_forward_declarations.h
+++ b/base/mac/sdk_forward_declarations.h
@@ -360,10 +360,4 @@
 // ----------------------------------------------------------------------------
 BASE_EXPORT extern "C" NSString* const kCWSSIDDidChangeNotification;
 
-// Once Chrome is built with at least the macOS 10.13 SDK, everything within
-// this preprocessor block can be removed.
-#if !defined(MAC_OS_X_VERSION_10_13)
-typedef NSString* NSTextCheckingOptionKey;
-#endif
-
 #endif  // BASE_MAC_SDK_FORWARD_DECLARATIONS_H_
diff --git a/base/macros.h b/base/macros.h
index 3064a1b5..96067008 100644
--- a/base/macros.h
+++ b/base/macros.h
@@ -82,6 +82,9 @@
 //   static base::NoDestructor<Factory> instance;
 //   return *instance;
 // }
+//
+// Removal of this macro is tracked in https://crbug.com/893317.
+//
 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 #define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
   static type& name = *new type arguments
diff --git a/base/sys_info_posix.cc b/base/sys_info_posix.cc
index 32db410..f720f65 100644
--- a/base/sys_info_posix.cc
+++ b/base/sys_info_posix.cc
@@ -178,7 +178,7 @@
 }
 #endif
 
-#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !(OS_CHROMEOS)
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 // static
 std::string SysInfo::OperatingSystemVersion() {
   struct utsname info;
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 1226030..d485837 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-89d8d1c05501a15b2b023f01966c0a47e67e385f
\ No newline at end of file
+b94593097edc8946187a1222783e0060a67f512d
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index ade366f7..82725a6 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-59e5c6849d5f25d893424e1ec1e12326a50eded0
\ No newline at end of file
+6ed08743105154bd5427e21c867b5f0f1dfb28a1
\ No newline at end of file
diff --git a/cc/input/scroll_snap_data.h b/cc/input/scroll_snap_data.h
index e44b9d8..c18f737 100644
--- a/cc/input/scroll_snap_data.h
+++ b/cc/input/scroll_snap_data.h
@@ -63,26 +63,26 @@
 
 struct ScrollSnapAlign {
   ScrollSnapAlign()
-      : alignment_inline(SnapAlignment::kNone),
-        alignment_block(SnapAlignment::kNone) {}
+      : alignment_block(SnapAlignment::kNone),
+        alignment_inline(SnapAlignment::kNone) {}
 
   explicit ScrollSnapAlign(SnapAlignment alignment)
-      : alignment_inline(alignment), alignment_block(alignment) {}
+      : alignment_block(alignment), alignment_inline(alignment) {}
 
-  ScrollSnapAlign(SnapAlignment i, SnapAlignment b)
-      : alignment_inline(i), alignment_block(b) {}
+  ScrollSnapAlign(SnapAlignment b, SnapAlignment i)
+      : alignment_block(b), alignment_inline(i) {}
 
   bool operator==(const ScrollSnapAlign& other) const {
-    return alignment_inline == other.alignment_inline &&
-           alignment_block == other.alignment_block;
+    return alignment_block == other.alignment_block &&
+           alignment_inline == other.alignment_inline;
   }
 
   bool operator!=(const ScrollSnapAlign& other) const {
     return !(*this == other);
   }
 
-  SnapAlignment alignment_inline;
   SnapAlignment alignment_block;
+  SnapAlignment alignment_inline;
 };
 
 // We should really use gfx::RangeF. However, it includes windows.h which would
diff --git a/cc/input/scroll_snap_data_unittest.cc b/cc/input/scroll_snap_data_unittest.cc
index a688448..d2347148 100644
--- a/cc/input/scroll_snap_data_unittest.cc
+++ b/cc/input/scroll_snap_data_unittest.cc
@@ -59,7 +59,7 @@
   SnapContainerData container(
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(100, 100));
-  SnapAreaData area(ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kEnd),
+  SnapAreaData area(ScrollSnapAlign(SnapAlignment::kEnd, SnapAlignment::kStart),
                     gfx::RectF(200, 0, 100, 100), false);
   container.AddSnapAreaData(area);
   gfx::ScrollOffset current_position(50, 50);
@@ -78,10 +78,10 @@
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800));
   SnapAreaData snap_x_only(
-      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
+      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
       gfx::RectF(80, 0, 150, 150), false);
   SnapAreaData snap_y_only(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(0, 70, 150, 150), false);
   SnapAreaData snap_on_both(ScrollSnapAlign(SnapAlignment::kStart),
                             gfx::RectF(50, 150, 150, 150), false);
@@ -101,10 +101,10 @@
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800));
   SnapAreaData snap_x_only(
-      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
+      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
       gfx::RectF(80, 0, 150, 150), false);
   SnapAreaData snap_y_only(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(0, 70, 150, 150), false);
   SnapAreaData snap_on_both(ScrollSnapAlign(SnapAlignment::kStart),
                             gfx::RectF(50, 150, 150, 150), false);
@@ -124,10 +124,10 @@
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800));
   SnapAreaData snap_x_only(
-      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
+      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
       gfx::RectF(80, 0, 150, 150), false);
   SnapAreaData snap_y_only(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(0, 70, 150, 150), false);
   gfx::ScrollOffset current_position(100, 100);
   container.AddSnapAreaData(snap_x_only);
@@ -144,10 +144,10 @@
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(600, 800));
   SnapAreaData snap_x_only(
-      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
+      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
       gfx::RectF(300, 400, 100, 100), false);
   SnapAreaData snap_y_only(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(400, 300, 100, 100), false);
   gfx::ScrollOffset current_position(0, 0);
   container.AddSnapAreaData(snap_x_only);
@@ -169,13 +169,13 @@
   // After that, we look for another snap point on y axis which does not
   // conflict with the snap point on x.
   SnapAreaData snap_x(
-      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
+      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
       gfx::RectF(150, 0, 100, 100), false);
   SnapAreaData snap_y1(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(0, 180, 100, 100), false);
   SnapAreaData snap_y2(
-      ScrollSnapAlign(SnapAlignment::kNone, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kNone),
       gfx::RectF(250, 80, 100, 100), false);
   container.AddSnapAreaData(snap_x);
   container.AddSnapAreaData(snap_y1);
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index fc1451b..1ef88c7c 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -871,6 +871,12 @@
 
 void Layer::UpdateScrollOffset(const gfx::ScrollOffset& scroll_offset) {
   DCHECK(scrollable());
+
+  // This function updates the property tree scroll offsets but in layer list
+  // mode this should occur during the main -> cc property tree push.
+  if (layer_tree_host_->IsUsingLayerLists())
+    return;
+
   if (scroll_tree_index() == ScrollTree::kInvalidNodeId) {
     // Ensure the property trees just have not been built yet but are marked for
     // being built which will set the correct scroll offset values.
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index 93725588..020bcee 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -727,10 +727,10 @@
           1.f - layer_tree_impl()->CurrentBrowserControlsShownRatio();
 
       viewport_rect_for_tile_priority_in_content_space_.Inset(
-          0,                                                   // left
-          0,                                                   // top,
-          0,                                                   // right,
-          std::ceilf(-total_controls_height * hidden_ratio));  // bottom
+          0,                                                  // left
+          0,                                                  // top,
+          0,                                                  // right,
+          std::ceil(-total_controls_height * hidden_ratio));  // bottom
     }
   }
 }
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 283e364..fc2282c5 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -433,14 +433,8 @@
 
   void UpdateLayerTreeHost() override { test_hooks_->UpdateLayerTreeHost(); }
 
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override {
-    test_hooks_->ApplyViewportDeltas(inner_delta, outer_delta,
-                                     elastic_overscroll_delta, page_scale,
-                                     top_controls_delta);
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
+    test_hooks_->ApplyViewportChanges(args);
   }
 
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index fa24c0fa..bb4078f73 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -21,11 +21,7 @@
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override {}
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override {}
+  void ApplyViewportChanges(const ApplyViewportChangesArgs&) override {}
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override {}
   void RequestNewLayerTreeFrameSink() override {}
diff --git a/cc/test/test_hooks.h b/cc/test/test_hooks.h
index 48262161..a5a0ca5 100644
--- a/cc/test/test_hooks.h
+++ b/cc/test/test_hooks.h
@@ -21,6 +21,8 @@
 
 namespace cc {
 
+struct ApplyViewportChangesArgs;
+
 // Used by test stubs to notify the test when something interesting happens.
 class TestHooks : public AnimationDelegate {
  public:
@@ -95,12 +97,7 @@
   virtual void DisplayDidDrawAndSwapOnThread() {}
 
   // Main thread hooks.
-  virtual void ApplyViewportDeltas(
-      const gfx::Vector2dF& inner_delta,
-      const gfx::Vector2dF& outer_delta,
-      const gfx::Vector2dF& elastic_overscroll_delta,
-      float scale,
-      float top_controls_delta) {}
+  virtual void ApplyViewportChanges(const ApplyViewportChangesArgs& args) {}
   virtual void BeginMainFrameNotExpectedSoon() {}
   virtual void BeginMainFrame(const viz::BeginFrameArgs& args) {}
   virtual void WillBeginMainFrame() {}
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index e3bd5b6d..ecee592 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -893,9 +893,9 @@
                                    info.elastic_overscroll_delta);
   // TODO(ccameron): pass the elastic overscroll here so that input events
   // may be translated appropriately.
-  client_->ApplyViewportDeltas(inner_viewport_scroll_delta, gfx::Vector2dF(),
-                               info.elastic_overscroll_delta,
-                               info.page_scale_delta, info.top_controls_delta);
+  client_->ApplyViewportChanges(
+      {inner_viewport_scroll_delta, info.elastic_overscroll_delta,
+       info.page_scale_delta, info.top_controls_delta});
   SetNeedsUpdateLayers();
 }
 
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index 302521d..9715043 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -9,6 +9,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/time/time.h"
+#include "ui/gfx/geometry/vector2d_f.h"
 
 namespace gfx {
 struct PresentationFeedback;
@@ -21,6 +22,23 @@
 
 namespace cc {
 
+struct ApplyViewportChangesArgs {
+  // Scroll offset delta of the inner (visual) viewport.
+  gfx::Vector2dF inner_delta;
+
+  // Elastic overscroll effect offset delta. This is used only on Mac. a.k.a
+  // "rubber-banding" overscroll.
+  gfx::Vector2dF elastic_overscroll_delta;
+
+  // "Pinch-zoom" page scale delta. This is a multiplicative delta. i.e.
+  // main_thread_scale * delta == impl_thread_scale.
+  float page_scale_delta;
+
+  // How much the browser controls have been shown or hidden. The ratio runs
+  // between 0 (hidden) and 1 (full-shown). This is additive.
+  float browser_controls_delta;
+};
+
 // A LayerTreeHost is bound to a LayerTreeHostClient. The main rendering
 // loop (in ProxyMain or SingleThreadProxy) calls methods on the
 // LayerTreeHost, which then handles them and also calls into the equivalent
@@ -65,12 +83,11 @@
   // mutations on the LayerTreeHost.)
   virtual void UpdateLayerTreeHost() = 0;
 
-  virtual void ApplyViewportDeltas(
-      const gfx::Vector2dF& inner_delta,
-      const gfx::Vector2dF& outer_delta,
-      const gfx::Vector2dF& elastic_overscroll_delta,
-      float page_scale,
-      float top_controls_delta) = 0;
+  // Notifies the client of viewport-related changes that occured in the
+  // LayerTreeHost since the last commit. This typically includes things
+  // related to pinch-zoom, browser controls (aka URL bar), overscroll, etc.
+  virtual void ApplyViewportChanges(const ApplyViewportChangesArgs& args) = 0;
+
   virtual void RecordWheelAndTouchScrollingCount(
       bool has_scrolled_by_wheel,
       bool has_scrolled_by_touch) = 0;
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index fb12cf6..d71fdf9 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -3234,14 +3234,12 @@
 
   void BeginTest() override { PostSetNeedsCommitToMainThread(); }
 
-  void ApplyViewportDeltas(const gfx::Vector2dF& scroll_delta,
-                           const gfx::Vector2dF&,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float scale,
-                           float) override {
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
     gfx::ScrollOffset offset = scroll_layer_->CurrentScrollOffset();
-    scroll_layer_->SetScrollOffset(ScrollOffsetWithDelta(offset, scroll_delta));
-    layer_tree_host()->SetPageScaleFactorAndLimits(scale, 0.5f, 2.f);
+    scroll_layer_->SetScrollOffset(
+        ScrollOffsetWithDelta(offset, args.inner_delta));
+    layer_tree_host()->SetPageScaleFactorAndLimits(args.page_scale_delta, 0.5f,
+                                                   2.f);
   }
 
   void DidActivateTreeOnThread(LayerTreeHostImpl* impl) override {
@@ -3323,14 +3321,10 @@
     }
   }
 
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner,
-                           const gfx::Vector2dF& outer,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float scale_delta,
-                           float top_controls_delta) override {
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
     EXPECT_TRUE(sent_gesture_);
-    EXPECT_EQ(gfx::Vector2dF(50, 50), inner);
-    EXPECT_EQ(2, scale_delta);
+    EXPECT_EQ(gfx::Vector2dF(50, 50), args.inner_delta);
+    EXPECT_EQ(2, args.page_scale_delta);
 
     auto* scroll_layer = layer_tree_host()->inner_viewport_scroll_layer();
     EXPECT_EQ(gfx::ScrollOffset(50, 50), scroll_layer->CurrentScrollOffset());
@@ -7022,13 +7016,9 @@
     EndTest();
   }
 
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner,
-                           const gfx::Vector2dF& outer,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float scale_delta,
-                           float top_controls_delta) override {
-    EXPECT_EQ(info_.page_scale_delta, scale_delta);
-    EXPECT_EQ(info_.top_controls_delta, top_controls_delta);
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
+    EXPECT_EQ(info_.page_scale_delta, args.page_scale_delta);
+    EXPECT_EQ(info_.top_controls_delta, args.browser_controls_delta);
     deltas_sent_to_client_ = true;
   }
 
diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc
index 8f5ff61..cef1be49 100644
--- a/cc/trees/layer_tree_host_unittest_scroll.cc
+++ b/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -1852,18 +1852,14 @@
     DCHECK(scroll_elasticity_helper_);
   }
 
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float scale,
-                           float top_controls_delta) override {
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
     DCHECK_NE(0, num_begin_main_frames_main_thread_)
         << "The first BeginMainFrame has no deltas to report";
     DCHECK_LT(num_begin_main_frames_main_thread_, 5);
 
     gfx::Vector2dF expected_elastic_overscroll =
         elastic_overscroll_test_cases_[num_begin_main_frames_main_thread_];
-    current_elastic_overscroll_ += elastic_overscroll_delta;
+    current_elastic_overscroll_ += args.elastic_overscroll_delta;
     EXPECT_EQ(expected_elastic_overscroll, current_elastic_overscroll_);
     EXPECT_EQ(expected_elastic_overscroll,
               layer_tree_host()->elastic_overscroll());
diff --git a/chrome/VERSION b/chrome/VERSION
index 071c697..3f52625 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=71
 MINOR=0
-BUILD=3574
+BUILD=3575
 PATCH=0
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
index 218389c..36038f0 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
@@ -9,6 +9,7 @@
 import com.google.android.libraries.feed.api.common.ThreadUtils;
 import com.google.android.libraries.feed.api.scope.FeedProcessScope;
 import com.google.android.libraries.feed.feedapplifecyclelistener.FeedAppLifecycleListener;
+import com.google.android.libraries.feed.host.config.ApplicationInfo;
 import com.google.android.libraries.feed.host.config.Configuration;
 import com.google.android.libraries.feed.host.config.DebugBehavior;
 import com.google.android.libraries.feed.host.network.NetworkClient;
@@ -97,19 +98,23 @@
         sFeedScheduler = schedulerBridge;
         FeedAppLifecycleListener lifecycleListener =
                 new FeedAppLifecycleListener(new ThreadUtils());
+        // TODO(gangwu): Getting real build info if possible.
+        ApplicationInfo applicationInfo =
+                new ApplicationInfo.Builder(ContextUtils.getApplicationContext())
+                        .setAppType(ApplicationInfo.AppType.CHROME)
+                        .build();
         FeedContentStorage contentStorage = new FeedContentStorage(profile);
         FeedJournalStorage journalStorage = new FeedJournalStorage(profile);
         NetworkClient networkClient = sTestNetworkClient == null ?
             new FeedNetworkBridge(profile) : sTestNetworkClient;
-        sFeedProcessScope =
-                new FeedProcessScope
-                        .Builder(configHostApi, Executors.newSingleThreadExecutor(),
-                                new LoggingApiImpl(), networkClient,
-                                schedulerBridge, lifecycleListener, DebugBehavior.SILENT,
-                                ContextUtils.getApplicationContext())
-                        .setContentStorage(contentStorage)
-                        .setJournalStorage(journalStorage)
-                        .build();
+        sFeedProcessScope = new FeedProcessScope
+                                    .Builder(configHostApi, Executors.newSingleThreadExecutor(),
+                                            new LoggingApiImpl(), networkClient, schedulerBridge,
+                                            lifecycleListener, DebugBehavior.SILENT,
+                                            ContextUtils.getApplicationContext(), applicationInfo)
+                                    .setContentStorage(contentStorage)
+                                    .setJournalStorage(journalStorage)
+                                    .build();
         schedulerBridge.initializeFeedDependencies(
                 sFeedProcessScope.getRequestManager(), sFeedProcessScope.getSessionManager());
 
@@ -134,11 +139,14 @@
             FeedAppLifecycle feedAppLifecycle, FeedAppLifecycleListener lifecycleListener) {
         Configuration configHostApi = FeedConfiguration.createConfiguration();
         sFeedScheduler = feedScheduler;
+        ApplicationInfo applicationInfo =
+                new ApplicationInfo.Builder(ContextUtils.getApplicationContext()).build();
+
         sFeedProcessScope = new FeedProcessScope
                                     .Builder(configHostApi, Executors.newSingleThreadExecutor(),
                                             new LoggingApiImpl(), networkClient, sFeedScheduler,
                                             lifecycleListener, DebugBehavior.SILENT,
-                                            ContextUtils.getApplicationContext())
+                                            ContextUtils.getApplicationContext(), applicationInfo)
                                     .build();
         sFeedOfflineIndicator = feedOfflineIndicator;
         sFeedAppLifecycle = feedAppLifecycle;
diff --git a/chrome/android/java/res/layout/icon_row_menu_footer.xml b/chrome/android/java/res/layout/icon_row_menu_footer.xml
index 9b18e375..d8d9967dd 100644
--- a/chrome/android/java/res/layout/icon_row_menu_footer.xml
+++ b/chrome/android/java/res/layout/icon_row_menu_footer.xml
@@ -6,6 +6,7 @@
 -->
 <org.chromium.chrome.browser.appmenu.AppMenuIconRowFooter
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical" >
@@ -42,10 +43,11 @@
             android:src="@drawable/btn_info"
             android:contentDescription="@string/accessibility_menu_info" />
 
+        <!-- The src will be set in onFinishInflate. -->
         <android.support.v7.widget.AppCompatImageButton
             android:id="@+id/reload_menu_id"
             style="@style/OverflowMenuButton"
-            android:src="@drawable/btn_reload_stop"
-            android:contentDescription="@string/accessibility_btn_refresh" />
+            android:contentDescription="@string/accessibility_btn_refresh"
+            tools:src="@drawable/btn_reload_stop" />
     </LinearLayout>
 </org.chromium.chrome.browser.appmenu.AppMenuIconRowFooter>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 997b5a8..7f5450c 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -539,7 +539,6 @@
     <dimen name="download_manager_prefetch_horizontal_margin">16dp</dimen>
     <dimen name="download_manager_prefetch_vertical_margin">12dp</dimen>
     <dimen name="download_manager_section_title_padding_top">16dp</dimen>
-    <dimen name="download_manager_section_title_padding_top_condensed">0dp</dimen>
     <dimen name="download_manager_section_title_padding_bottom">0dp</dimen>
     <dimen name="download_manager_section_title_padding_image">8dp</dimen>
     <!-- The corner radius is calculated by subtracting the hairline border width from the
diff --git a/chrome/android/java/res_download/layout/download_manager_date_item.xml b/chrome/android/java/res_download/layout/download_manager_date_item.xml
deleted file mode 100644
index 7069da5..0000000
--- a/chrome/android/java/res_download/layout/download_manager_date_item.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingTop="@dimen/download_manager_section_title_padding_top"
-    android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
-    android:paddingStart="@dimen/list_item_default_margin"
-    android:maxLines="1"
-    android:gravity="start|center_vertical"
-    android:textAppearance="@style/BlackTitle1"
-    android:textAlignment="viewStart" />
\ No newline at end of file
diff --git a/chrome/android/java/res_download/layout/download_manager_section_header.xml b/chrome/android/java/res_download/layout/download_manager_section_header.xml
index a914005..086cdac5 100644
--- a/chrome/android/java/res_download/layout/download_manager_section_header.xml
+++ b/chrome/android/java/res_download/layout/download_manager_section_header.xml
@@ -4,28 +4,45 @@
      found in the LICENSE file.
 -->
 
-<LinearLayout
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal">
+    android:layout_height="wrap_content">
+
+    <Space
+        android:id="@+id/top_space"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/download_manager_section_title_padding_top" />
+
+    <TextView
+        android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/top_space"
+        android:paddingStart="@dimen/list_item_default_margin"
+        android:maxLines="1"
+        android:textAppearance="@style/BlackTitle1"/>
 
     <TextView
         android:id="@+id/title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:paddingTop="@dimen/download_manager_section_title_padding_top"
-        android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
-        android:paddingStart="@dimen/list_item_default_margin"
-        android:textAppearance="@style/BlackHint2"
-        android:textAlignment="viewStart"/>
-
-    <include layout="@layout/list_menu_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical" />
+        android:layout_below="@+id/date"
+        android:paddingStart="@dimen/list_item_default_margin"
+        android:textAppearance="@style/BlackHint2"
+        android:maxLines="1" />
 
-</LinearLayout>
+    <Space
+        android:id="@+id/bottom_space"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_below="@+id/title" />
+
+    <include layout="@layout/list_menu_button"
+        android:layout_width="48dp"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true" />
+
+</RelativeLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuIconRowFooter.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuIconRowFooter.java
index 4000e47..a20e9b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuIconRowFooter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuIconRowFooter.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.appmenu;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v7.content.res.AppCompatResources;
 import android.support.v7.widget.AppCompatImageButton;
 import android.util.AttributeSet;
@@ -53,6 +55,13 @@
 
         mReloadButton = (AppCompatImageButton) findViewById(R.id.reload_menu_id);
         mReloadButton.setOnClickListener(this);
+
+        // ImageView tinting doesn't work with LevelListDrawable, use Drawable tinting instead.
+        // See https://crbug.com/891593 for details.
+        Drawable icon = AppCompatResources.getDrawable(getContext(), R.drawable.btn_reload_stop);
+        DrawableCompat.setTintList(
+                icon, AppCompatResources.getColorStateList(getContext(), R.color.dark_mode_tint));
+        mReloadButton.setImageDrawable(icon);
     }
 
     /**
@@ -74,7 +83,6 @@
 
         mDownloadButton.setEnabled(DownloadUtils.isAllowedToDownloadPage(currentTab));
 
-        mReloadButton.setImageResource(R.drawable.btn_reload_stop);
         loadingStateChanged(currentTab.isLoading());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
index efb7742..d6d1eaf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
@@ -8,7 +8,6 @@
 import org.chromium.chrome.browser.download.home.filter.Filters;
 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterObserver;
 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
-import org.chromium.chrome.browser.download.home.list.ListItem.DateListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.SeparatorViewListItem;
@@ -146,21 +145,18 @@
             DateGroup dateGroup = mDateGroups.get(date);
             int sectionIndex = 0;
 
-            // Add an item for the date header.
-            if (!mHideAllHeaders) {
-                listItems.add(new DateListItem(CalendarUtils.getStartOfDay(date.getTime())));
-            }
-
             // For each section.
             for (Integer filter : dateGroup.sections.keySet()) {
                 Section section = dateGroup.sections.get(filter);
 
                 // Add a section header.
-                if (!mHideSectionHeaders && !mHideAllHeaders) {
+                if (!mHideAllHeaders) {
                     SectionHeaderListItem sectionHeaderItem =
                             new SectionHeaderListItem(filter, date.getTime());
+                    sectionHeaderItem.showDate = sectionIndex == 0;
+                    sectionHeaderItem.showTitle = !mHideSectionHeaders;
+                    sectionHeaderItem.showMenu = filter == OfflineItemFilter.FILTER_IMAGE;
                     sectionHeaderItem.items = new ArrayList<>(section.items.values());
-                    sectionHeaderItem.isFirstSectionOfDay = sectionIndex == 0;
                     listItems.add(sectionHeaderItem);
                 }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java
index e43c6fdd..78191a7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListView.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.modelutil.ForwardingListObservable;
 import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
 import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
+import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter.Delegate;
 
 /**
  * The View component of a DateOrderedList.  This takes the DateOrderedListModel and creates the
@@ -60,6 +61,11 @@
                 ListItemViewHolder viewHolder, int position, @Nullable Void payload) {
             viewHolder.bind(mModel.getProperties(), mModel.get(position));
         }
+
+        @Override
+        public void onViewRecycled(ListItemViewHolder viewHolder) {
+            viewHolder.recycle();
+        }
     }
 
     /** Creates an instance of a {@link DateOrderedListView} representing {@code model}. */
@@ -128,6 +134,11 @@
             super.onLayoutChildren(recycler, state);
         }
 
+        @Override
+        public boolean supportsPredictiveItemAnimations() {
+            return false;
+        }
+
         private class SpanSizeLookupImpl extends SpanSizeLookup {
             // SpanSizeLookup implementation.
             @Override
@@ -156,7 +167,8 @@
                 case ListUtils.ViewType.IN_PROGRESS_VIDEO:
                     outRect.left = mPrefetchHorizontalPaddingPx;
                     outRect.right = mPrefetchHorizontalPaddingPx;
-                    outRect.bottom = mPrefetchHorizontalPaddingPx;
+                    outRect.top = mPrefetchVerticalPaddingPx / 2;
+                    outRect.bottom = mPrefetchVerticalPaddingPx / 2;
                     break;
                 case ListUtils.ViewType.PREFETCH:
                     outRect.left = mPrefetchHorizontalPaddingPx;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
index e79c150..11ee72f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
@@ -44,7 +44,7 @@
     }
 
     /** A {@link ListItem} that involves a {@link Date}. */
-    public static class DateListItem extends ListItem {
+    private abstract static class DateListItem extends ListItem {
         public final Date date;
 
         /**
@@ -74,7 +74,9 @@
     /** A {@link ListItem} representing a section header. */
     public static class SectionHeaderListItem extends DateListItem {
         public final int filter;
-        public boolean isFirstSectionOfDay;
+        public boolean showDate;
+        public boolean showTitle;
+        public boolean showMenu;
         public List<OfflineItem> items;
 
         /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java
index 4ba2d164..e7a7c864 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java
@@ -8,7 +8,6 @@
 import android.support.annotation.StringRes;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.download.home.list.ListItem.DateListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.ViewListItem;
 import org.chromium.components.offline_items_collection.OfflineItem;
@@ -70,32 +69,28 @@
             return separator.isDateDivider() ? ViewType.SEPARATOR_DATE : ViewType.SEPARATOR_SECTION;
         }
 
-        if (item instanceof DateListItem) {
-            if (item instanceof OfflineItemListItem) {
-                OfflineItemListItem offlineItem = (OfflineItemListItem) item;
+        if (item instanceof OfflineItemListItem) {
+            OfflineItemListItem offlineItem = (OfflineItemListItem) item;
 
-                if (offlineItem.item.isSuggested) return ViewType.PREFETCH;
+            if (offlineItem.item.isSuggested) return ViewType.PREFETCH;
 
-                boolean inProgress = offlineItem.item.state == OfflineItemState.IN_PROGRESS
-                        || offlineItem.item.state == OfflineItemState.PAUSED
-                        || offlineItem.item.state == OfflineItemState.INTERRUPTED
-                        || offlineItem.item.state == OfflineItemState.PENDING
-                        || offlineItem.item.state == OfflineItemState.FAILED;
+            boolean inProgress = offlineItem.item.state == OfflineItemState.IN_PROGRESS
+                    || offlineItem.item.state == OfflineItemState.PAUSED
+                    || offlineItem.item.state == OfflineItemState.INTERRUPTED
+                    || offlineItem.item.state == OfflineItemState.PENDING
+                    || offlineItem.item.state == OfflineItemState.FAILED;
 
-                switch (offlineItem.item.filter) {
-                    case OfflineItemFilter.FILTER_VIDEO:
-                        return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO;
-                    case OfflineItemFilter.FILTER_IMAGE:
-                        return inProgress ? ViewType.IN_PROGRESS_IMAGE : ViewType.IMAGE;
-                    // case OfflineItemFilter.FILTER_PAGE:
-                    // case OfflineItemFilter.FILTER_AUDIO:
-                    // case OfflineItemFilter.FILTER_OTHER:
-                    // case OfflineItemFilter.FILTER_DOCUMENT:
-                    default:
-                        return inProgress ? ViewType.IN_PROGRESS : ViewType.GENERIC;
-                }
-            } else {
-                return ViewType.DATE;
+            switch (offlineItem.item.filter) {
+                case OfflineItemFilter.FILTER_VIDEO:
+                    return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO;
+                case OfflineItemFilter.FILTER_IMAGE:
+                    return inProgress ? ViewType.IN_PROGRESS_IMAGE : ViewType.IMAGE;
+                // case OfflineItemFilter.FILTER_PAGE:
+                // case OfflineItemFilter.FILTER_AUDIO:
+                // case OfflineItemFilter.FILTER_OTHER:
+                // case OfflineItemFilter.FILTER_DOCUMENT:
+                default:
+                    return inProgress ? ViewType.IN_PROGRESS : ViewType.GENERIC;
             }
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/DateViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/DateViewHolder.java
deleted file mode 100644
index 308c1d8..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/DateViewHolder.java
+++ /dev/null
@@ -1,37 +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.
-
-package org.chromium.chrome.browser.download.home.list.holder;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.chromium.chrome.browser.download.home.list.ListItem;
-import org.chromium.chrome.browser.download.home.list.UiUtils;
-import org.chromium.chrome.browser.modelutil.PropertyModel;
-import org.chromium.chrome.download.R;
-
-/** A {@link RecyclerView.ViewHolder} specifically meant to display a date header. */
-public class DateViewHolder extends ListItemViewHolder {
-    /** Creates a new {@link DateViewHolder} instance. */
-    public static org.chromium.chrome.browser.download.home.list.holder.DateViewHolder create(
-            ViewGroup parent) {
-        View view = LayoutInflater.from(parent.getContext())
-                            .inflate(R.layout.download_manager_date_item, null);
-        return new org.chromium.chrome.browser.download.home.list.holder.DateViewHolder(view);
-    }
-
-    private DateViewHolder(View view) {
-        super(view);
-    }
-
-    // ListItemViewHolder implementation.
-    @Override
-    public void bind(PropertyModel properties, ListItem item) {
-        ListItem.DateListItem dateItem = (ListItem.DateListItem) item;
-        ((TextView) itemView).setText(UiUtils.dateToHeaderString(dateItem.date));
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java
index f5e41cb..855ba25 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java
@@ -29,8 +29,6 @@
      */
     public static ListItemViewHolder create(ViewGroup parent, @ListUtils.ViewType int viewType) {
         switch (viewType) {
-            case ListUtils.ViewType.DATE:
-                return DateViewHolder.create(parent);
             case ListUtils.ViewType.IN_PROGRESS:
                 return InProgressViewHolder.create(parent);
             case ListUtils.ViewType.GENERIC:
@@ -65,4 +63,10 @@
      * @param item       The {@link ListItem} to visually represent in this {@link ViewHolder}.
      */
     public abstract void bind(PropertyModel properties, ListItem item);
+
+    /**
+     * Gives subclasses a chance to free up expensive resources when this {@link ViewHolder} is no
+     * longer attached to the parent {@link RecyclerView}.
+     */
+    public void recycle() {}
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/OfflineItemViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/OfflineItemViewHolder.java
index 861d1c5..ce172ee7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/OfflineItemViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/OfflineItemViewHolder.java
@@ -95,6 +95,13 @@
         }
     }
 
+    @Override
+    public void recycle() {
+        // This should cancel any outstanding async request as well as drop any currently visible
+        // bitmap.
+        mThumbnail.setImageDrawable(null);
+    }
+
     /**
      * Called when a {@link OfflineItemVisuals} are retrieved and are used to build the
      * {@link Drawable} to use for the thumbnail {@link View}.  Can be overridden by subclasses who
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
index e363a23..e9a16e07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
@@ -15,11 +15,11 @@
 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderListItem;
 import org.chromium.chrome.browser.download.home.list.ListProperties;
 import org.chromium.chrome.browser.download.home.list.ListUtils;
+import org.chromium.chrome.browser.download.home.list.UiUtils;
 import org.chromium.chrome.browser.modelutil.PropertyModel;
 import org.chromium.chrome.browser.widget.ListMenuButton;
 import org.chromium.chrome.download.R;
 import org.chromium.components.offline_items_collection.OfflineItem;
-import org.chromium.components.offline_items_collection.OfflineItemFilter;
 import org.chromium.components.offline_items_collection.OfflineItemState;
 
 import java.util.ArrayList;
@@ -30,8 +30,11 @@
  * A {@link ViewHolder} specifically meant to display a section header.
  */
 public class SectionTitleViewHolder extends ListItemViewHolder implements ListMenuButton.Delegate {
+    private final TextView mDate;
     private final TextView mTitle;
     private final ListMenuButton mMore;
+    private final View mTopSpace;
+    private final View mBottomSpace;
 
     private Runnable mShareCallback;
     private Runnable mDeleteCallback;
@@ -51,8 +54,11 @@
 
     private SectionTitleViewHolder(View view) {
         super(view);
+        mDate = (TextView) view.findViewById(R.id.date);
         mTitle = (TextView) view.findViewById(R.id.title);
         mMore = (ListMenuButton) view.findViewById(R.id.more);
+        mTopSpace = view.findViewById(R.id.top_space);
+        mBottomSpace = view.findViewById(R.id.bottom_space);
         if (mMore != null) mMore.setDelegate(this);
     }
 
@@ -62,30 +68,19 @@
         SectionHeaderListItem sectionItem = (SectionHeaderListItem) item;
         mTitle.setText(ListUtils.getTextForSection(sectionItem.filter));
 
-        boolean isPhoto = sectionItem.filter == OfflineItemFilter.FILTER_IMAGE;
-        Resources resources = itemView.getContext().getResources();
-
-        int paddingTop = resources.getDimensionPixelSize(isPhoto
-                        ? R.dimen.download_manager_section_title_padding_image
-                        : R.dimen.download_manager_section_title_padding_top);
-        int paddingBottom = resources.getDimensionPixelSize(isPhoto
-                        ? R.dimen.download_manager_section_title_padding_image
-                        : R.dimen.download_manager_section_title_padding_bottom);
-
-        if (sectionItem.isFirstSectionOfDay) {
-            paddingTop = resources.getDimensionPixelSize(
-                    R.dimen.download_manager_section_title_padding_top_condensed);
+        if (sectionItem.showDate) {
+            mDate.setText(UiUtils.dateToHeaderString(sectionItem.date));
         }
 
-        mTitle.setPadding(
-                mTitle.getPaddingLeft(), paddingTop, mTitle.getPaddingRight(), paddingBottom);
-
-        if (mMore != null) mMore.setVisibility(isPhoto ? View.VISIBLE : View.GONE);
+        updateTopBottomSpacing(sectionItem.showMenu);
+        mDate.setVisibility(sectionItem.showDate ? View.VISIBLE : View.GONE);
+        mTitle.setVisibility((sectionItem.showTitle ? View.VISIBLE : View.GONE));
+        if (mMore != null) mMore.setVisibility(sectionItem.showMenu ? View.VISIBLE : View.GONE);
 
         mHasMultipleItems = sectionItem.items.size() > 1;
         mCanSelectItems = !getCompletedItems(sectionItem.items).isEmpty();
 
-        if (isPhoto && mMore != null) {
+        if (sectionItem.showMenu && mMore != null) {
             assert sectionItem.items.size() > 0;
             mShareCallback = ()
                     -> properties.get(ListProperties.CALLBACK_SHARE)
@@ -137,6 +132,22 @@
         }
     }
 
+    private void updateTopBottomSpacing(boolean showMenu) {
+        Resources resources = itemView.getContext().getResources();
+        ViewGroup.LayoutParams topSpaceParams = mTopSpace.getLayoutParams();
+        ViewGroup.LayoutParams bottomSpaceParams = mBottomSpace.getLayoutParams();
+
+        topSpaceParams.height = resources.getDimensionPixelSize(showMenu
+                        ? R.dimen.download_manager_section_title_padding_image
+                        : R.dimen.download_manager_section_title_padding_top);
+        bottomSpaceParams.height = resources.getDimensionPixelSize(showMenu
+                        ? R.dimen.download_manager_section_title_padding_image
+                        : R.dimen.download_manager_section_title_padding_bottom);
+
+        mTopSpace.setLayoutParams(topSpaceParams);
+        mBottomSpace.setLayoutParams(bottomSpaceParams);
+    }
+
     private static List<OfflineItem> getCompletedItems(Collection<OfflineItem> items) {
         List<OfflineItem> completedItems = new ArrayList<>();
         for (OfflineItem item : items) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java
index e53e8c7..a14330b9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java
@@ -85,12 +85,12 @@
      */
     public void setAsyncImageDrawable(Factory factory, @Nullable Object identifier) {
         if (mIdentifier != null && identifier != null && mIdentifier.equals(identifier)) return;
-        mIdentifier = identifier;
 
         // This will clear out any outstanding request.
         setImageDrawable(null);
         setForegroundDrawableCompat(mWaitingDrawable);
 
+        mIdentifier = identifier;
         mFactory = factory;
         retrieveDrawableIfNeeded();
     }
@@ -138,7 +138,7 @@
         // If we ended up swapping out the identifier and somehow this request didn't cancel ignore
         // the response.  This does a direct == comparison instead of .equals() because any new
         // request should have canceled this one (we'll leave null alone though).
-        if (mIdentifier != identifier) return;
+        if (mIdentifier != identifier || !mWaitingForResponse) return;
 
         mCancelable = null;
         mWaitingForResponse = false;
@@ -149,9 +149,10 @@
     private void cancelPreviousDrawableRequest() {
         mFactory = null;
 
-        if (mCancelable != null) {
-            mCancelable.run();
+        if (mWaitingForResponse) {
+            if (mCancelable != null) mCancelable.run();
             mCancelable = null;
+            mIdentifier = null;
             mWaitingForResponse = false;
         }
     }
@@ -160,20 +161,19 @@
         // If width or height are not valid, don't start to retrieve the drawable since the
         // thumbnail may be scaled down to 0.
         if (getWidth() <= 0 || getHeight() <= 0) return;
+        if (mFactory == null) return;
 
-        if (mFactory != null) {
-            // Start to retrieve the drawable.
-            mWaitingForResponse = true;
+        // Start to retrieve the drawable.
+        mWaitingForResponse = true;
 
-            Object localIdentifier = mIdentifier;
-            mCancelable = mFactory.get(d
-                    -> setAsyncImageDrawableResponse(d, localIdentifier),
-                    getWidth(), getHeight());
+        Object localIdentifier = mIdentifier;
+        mCancelable = mFactory.get(drawable
+                -> setAsyncImageDrawableResponse(drawable, localIdentifier),
+                getWidth(), getHeight());
 
-            // If setAsyncImageDrawableResponse is called synchronously, clear mCancelable.
-            if (!mWaitingForResponse) mCancelable = null;
+        // If setAsyncImageDrawableResponse is called synchronously, clear mCancelable.
+        if (!mWaitingForResponse) mCancelable = null;
 
-            mFactory = null;
-        }
+        mFactory = null;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
index bf028aa1..0011a75a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.widget.LoadingView;
 import org.chromium.chrome.browser.widget.RoundedIconGenerator;
 
@@ -41,13 +42,14 @@
     private final RoundedIconGenerator mIconGenerator;
     private final ContextMenuManager mContextMenuManager;
     private final NativePageNavigationDelegate mNavDelegate;
+    private final Profile mProfile;
 
     private RecyclerView.LayoutManager mLayoutManager;
     private PropertyModel mCategoryModel;
 
     public CategoryCardAdapter(PropertyModel model, RecyclerView.LayoutManager layoutManager,
             RoundedIconGenerator iconGenerator, ContextMenuManager contextMenuManager,
-            NativePageNavigationDelegate navDelegate) {
+            NativePageNavigationDelegate navDelegate, Profile profile) {
         mCategoryModel = model;
         mCategoryModel.addObserver(this);
         mCategoryModel.get(ExploreSitesPage.CATEGORY_LIST_KEY).addObserver(this);
@@ -56,6 +58,7 @@
         mIconGenerator = iconGenerator;
         mContextMenuManager = contextMenuManager;
         mNavDelegate = navDelegate;
+        mProfile = profile;
     }
 
     @Override
@@ -95,7 +98,7 @@
             // Position - 1 because there is always title.
             view.setCategory(
                     mCategoryModel.get(ExploreSitesPage.CATEGORY_LIST_KEY).get(position - 1),
-                    mIconGenerator, mContextMenuManager, mNavDelegate);
+                    mIconGenerator, mContextMenuManager, mNavDelegate, mProfile);
         } else if (holder.getItemViewType() == ViewType.LOADING) {
             // Start spinner.
             LoadingView spinner = holder.itemView.findViewById(R.id.loading);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
index 35a00985..1bb798d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
@@ -4,9 +4,13 @@
 
 package org.chromium.chrome.browser.explore_sites;
 
+import android.content.Context;
 import android.graphics.Bitmap;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -38,10 +42,7 @@
 
     public static void getCategoryImage(
             Profile profile, int categoryID, int pixelSize, Callback<Bitmap> callback) {
-        // TODO(dewittj): Remove this when image decoding works correctly.
-        Bitmap image = Bitmap.createBitmap(pixelSize, pixelSize, Bitmap.Config.ARGB_8888);
-        image.eraseColor(android.graphics.Color.GREEN);
-        callback.onResult(image);
+        nativeGetCategoryImage(profile, categoryID, pixelSize, callback);
     }
 
     /**
@@ -65,6 +66,21 @@
         ExploreSitesBackgroundTask.schedule(false /* updateCurrent */);
     }
 
+    /**
+     * Returns the scale factor on this device.
+     */
+    @CalledByNative
+    static float getScaleFactorFromDevice() {
+        // Get DeviceMetrics from context.
+        DisplayMetrics metrics = new DisplayMetrics();
+        ((WindowManager) ContextUtils.getApplicationContext().getSystemService(
+                 Context.WINDOW_SERVICE))
+                .getDefaultDisplay()
+                .getMetrics(metrics);
+        // Get density and return it.
+        return metrics.density;
+    }
+
     static native int nativeGetVariation();
     private static native void nativeGetEspCatalog(Profile profile,
             List<ExploreSitesCategory> result, Callback<List<ExploreSitesCategory>> callback);
@@ -74,4 +90,7 @@
 
     private static native void nativeUpdateCatalogFromNetwork(
             Profile profile, boolean isImmediateFetch, Callback<Boolean> callback);
+
+    private static native void nativeGetCategoryImage(
+            Profile profile, int categoryID, int pixelSize, Callback<Bitmap> callback);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
index 298ba35..d78b6df 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.explore_sites;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.util.AttributeSet;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
@@ -16,13 +17,18 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.modelutil.PropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.widget.RoundedIconGenerator;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.mojom.WindowOpenDisposition;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -30,18 +36,23 @@
  */
 public class ExploreSitesCategoryCardView extends LinearLayout {
     private static final int MAX_TILE_COUNT = 8;
+    private static final int TITLE_LINES = 1;
 
     private TextView mTitleView;
     private GridLayout mTileView;
     private RoundedIconGenerator mIconGenerator;
     private ContextMenuManager mContextMenuManager;
     private NativePageNavigationDelegate mNavigationDelegate;
+    private Profile mProfile;
+    private List<PropertyModelChangeProcessor<PropertyModel, ExploreSitesTileView, PropertyKey>>
+            mModelChangeProcessors;
 
     private class CategoryCardInteractionDelegate
             implements ContextMenuManager.Delegate, OnClickListener, OnCreateContextMenuListener {
-        private ExploreSitesSite mSite;
-        public CategoryCardInteractionDelegate(ExploreSitesSite site) {
-            mSite = site;
+        private String mSiteUrl;
+
+        public CategoryCardInteractionDelegate(String siteUrl) {
+            mSiteUrl = siteUrl;
         }
 
         @Override
@@ -67,7 +78,7 @@
 
         @Override
         public String getUrl() {
-            return mSite.getUrl();
+            return mSiteUrl;
         }
 
         @Override
@@ -82,8 +93,29 @@
         public void onContextMenuCreated(){};
     }
 
+    private class ExploreSitesSiteViewBinder
+            implements PropertyModelChangeProcessor
+                               .ViewBinder<PropertyModel, ExploreSitesTileView, PropertyKey> {
+        @Override
+        public void bind(PropertyModel model, ExploreSitesTileView view, PropertyKey key) {
+            if (key == ExploreSitesSite.ICON_KEY) {
+                view.updateIcon(model.get(ExploreSitesSite.ICON_KEY),
+                        model.get(ExploreSitesSite.TITLE_KEY));
+            } else if (key == ExploreSitesSite.TITLE_KEY) {
+                view.setTitle(model.get(ExploreSitesSite.TITLE_KEY), TITLE_LINES);
+            } else if (key == ExploreSitesSite.URL_KEY) {
+                // Attach click handlers.
+                CategoryCardInteractionDelegate interactionDelegate =
+                        new CategoryCardInteractionDelegate(model.get(ExploreSitesSite.URL_KEY));
+                view.setOnClickListener(interactionDelegate);
+                view.setOnCreateContextMenuListener(interactionDelegate);
+            }
+        }
+    }
+
     public ExploreSitesCategoryCardView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mModelChangeProcessors = new ArrayList<>(MAX_TILE_COUNT);
     }
 
     @Override
@@ -94,11 +126,12 @@
     }
 
     public void setCategory(ExploreSitesCategory category, RoundedIconGenerator iconGenerator,
-            ContextMenuManager contextMenuManager,
-            NativePageNavigationDelegate navigationDelegate) {
+            ContextMenuManager contextMenuManager, NativePageNavigationDelegate navigationDelegate,
+            Profile profile) {
         mIconGenerator = iconGenerator;
         mContextMenuManager = contextMenuManager;
         mNavigationDelegate = navigationDelegate;
+        mProfile = profile;
 
         updateTitle(category.getTitle());
         updateTileViews(category.getSites());
@@ -109,6 +142,13 @@
     }
 
     public void updateTileViews(List<ExploreSitesSite> sites) {
+        // Clear observers.
+        for (PropertyModelChangeProcessor<PropertyModel, ExploreSitesTileView, PropertyKey>
+                        observer : mModelChangeProcessors) {
+            observer.destroy();
+        }
+        mModelChangeProcessors.clear();
+
         // Remove extra tiles if too many.
         if (mTileView.getChildCount() > sites.size()) {
             mTileView.removeViews(sites.size(), mTileView.getChildCount() - sites.size());
@@ -129,13 +169,17 @@
         // Initialize all the non-empty tiles again to update.
         for (int i = 0; i < tileMax; i++) {
             ExploreSitesTileView tileView = (ExploreSitesTileView) mTileView.getChildAt(i);
-            final ExploreSitesSite site = sites.get(i);
-            tileView.initialize(site, mIconGenerator);
+            final PropertyModel site = sites.get(i).getModel();
+            tileView.initialize(mIconGenerator);
 
-            CategoryCardInteractionDelegate interactionDelegate =
-                    new CategoryCardInteractionDelegate(site);
-            tileView.setOnClickListener(interactionDelegate);
-            tileView.setOnCreateContextMenuListener(interactionDelegate);
+            mModelChangeProcessors.add(PropertyModelChangeProcessor.create(
+                    site, tileView, new ExploreSitesSiteViewBinder()));
+
+            // Fetch icon if not present already.
+            if (site.get(ExploreSitesSite.ICON_KEY) == null) {
+                ExploreSitesBridge.getSiteImage(mProfile, site.get(ExploreSitesSite.ID_KEY),
+                        (Bitmap icon) -> site.set(ExploreSitesSite.ICON_KEY, icon));
+            }
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
index 1ea3f47..aa0437e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
@@ -13,6 +13,7 @@
 import android.view.ViewGroup;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.UrlConstants;
@@ -105,7 +106,7 @@
         host.getActiveTab().getWindowAndroid().addContextMenuCloseListener(mContextMenuManager);
 
         CategoryCardAdapter adapterDelegate = new CategoryCardAdapter(
-                mModel, layoutManager, iconGenerator, mContextMenuManager, navDelegate);
+                mModel, layoutManager, iconGenerator, mContextMenuManager, navDelegate, profile);
 
         RecyclerView recyclerView =
                 (RecyclerView) mView.findViewById(R.id.explore_sites_category_recycler);
@@ -116,6 +117,7 @@
         recyclerView.setAdapter(adapter);
 
         ExploreSitesBridge.getEspCatalog(profile, this::translateToModel);
+        RecordUserAction.record("Android.ExploreSitesPage.Open");
 
         // TODO(chili): Set layout to be an observer of list model
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java
index d5799a5..7a6fb915 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java
@@ -11,6 +11,8 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesCategory.CategoryType;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
@@ -32,6 +34,10 @@
  */
 public class ExploreSitesSection {
     private static final int MAX_CATEGORIES = 3;
+    // This is a number of UMA histogram buckets that should be an upper bound
+    // of MAX_CATEGORIES over time. If MAX_CATEGORIES changes, this value should
+    // be updated only upwards.
+    private static final int MAX_CATEGORIES_HISTOGRAM_BUCKETS = 3;
 
     @TileStyle
     private int mStyle;
@@ -51,6 +57,7 @@
     }
 
     private void initialize() {
+        RecordUserAction.record("Android.ExploreSitesNTP.Opened");
         ExploreSitesBridge.getEspCatalog(mProfile, this::gotEspCatalog);
     }
 
@@ -98,7 +105,7 @@
         return category;
     }
 
-    private void createTileView(ExploreSitesCategory category) {
+    private void createTileView(int tileIndex, ExploreSitesCategory category) {
         ExploreSitesCategoryTileView tileView;
         if (mStyle == TileStyle.MODERN_CONDENSED) {
             tileView = (ExploreSitesCategoryTileView) LayoutInflater.from(getContext())
@@ -111,7 +118,7 @@
         }
         tileView.initialize(category);
         mExploreSection.addView(tileView);
-        tileView.setOnClickListener((View v) -> onClicked(category, v));
+        tileView.setOnClickListener((View v) -> onClicked(tileIndex, category, v));
     }
 
     /**
@@ -120,10 +127,14 @@
      * from the ExploreSitesBridge.
      */
     private void gotEspCatalog(List<ExploreSitesCategory> categoryList) {
+        boolean loadingCatalogFromNetwork = false;
         if (categoryList == null || categoryList.size() == 0) {
+            loadingCatalogFromNetwork = true;
             ExploreSitesBridge.updateCatalogFromNetwork(mProfile, true /*isImmediateFetch*/,
                     (Boolean success) -> { updateCategoryIcons(); });
         }
+        RecordHistogram.recordBooleanHistogram(
+                "ExploreSites.NTPLoadingCatalogFromNetwork", loadingCatalogFromNetwork);
         // Initialize with defaults right away.
         initializeCategoryTiles(categoryList);
     }
@@ -167,17 +178,19 @@
 
         int tileCount = 0;
         for (final ExploreSitesCategory category : categoryList) {
+            if (tileCount >= MAX_CATEGORIES) break;
+            createTileView(tileCount, category);
             tileCount++;
-            if (tileCount > MAX_CATEGORIES) break;
-            createTileView(category);
         }
-        createTileView(createMoreTileCategory());
+        createTileView(tileCount, createMoreTileCategory());
         if (needIcons) {
             updateCategoryIcons();
         }
     }
 
-    private void onClicked(ExploreSitesCategory category, View v) {
+    private void onClicked(int tileIndex, ExploreSitesCategory category, View v) {
+        RecordHistogram.recordLinearCountHistogram("ExploreSites.ClickedNTPCategoryIndex",
+                tileIndex, 0, MAX_CATEGORIES_HISTOGRAM_BUCKETS, MAX_CATEGORIES_HISTOGRAM_BUCKETS);
         mNavigationDelegate.openUrl(WindowOpenDisposition.CURRENT_TAB,
                 new LoadUrlParams(category.getUrl(), PageTransition.AUTO_BOOKMARK));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java
index 8be10ab..cf59acf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java
@@ -7,40 +7,33 @@
 import android.graphics.Bitmap;
 
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
 
 /**
  * An object encapsulating info for a website.
  */
 public class ExploreSitesSite {
-    private int mSiteId;
-    private String mSiteTitle;
-    private Bitmap mIcon;
-    private String mUrl;
+    static final PropertyModel.ReadableIntPropertyKey ID_KEY =
+            new PropertyModel.ReadableIntPropertyKey();
+    static final PropertyModel.ReadableObjectPropertyKey<String> TITLE_KEY =
+            new PropertyModel.ReadableObjectPropertyKey<>();
+    static final PropertyModel.ReadableObjectPropertyKey<String> URL_KEY =
+            new PropertyModel.ReadableObjectPropertyKey<>();
+    static final PropertyModel.WritableObjectPropertyKey<Bitmap> ICON_KEY =
+            new PropertyModel.WritableObjectPropertyKey<>();
+
+    private PropertyModel mModel;
 
     public ExploreSitesSite(int id, String title, String url) {
-        mSiteId = id;
-        mSiteTitle = title;
-        mUrl = url;
+        mModel = new PropertyModel.Builder(ID_KEY, TITLE_KEY, URL_KEY, ICON_KEY)
+                         .with(ID_KEY, id)
+                         .with(TITLE_KEY, title)
+                         .with(URL_KEY, url)
+                         .build();
     }
 
-    public int getId() {
-        return mSiteId;
-    }
-
-    public void setIcon(Bitmap icon) {
-        mIcon = icon;
-    }
-
-    public String getTitle() {
-        return mSiteTitle;
-    }
-
-    public String getUrl() {
-        return mUrl;
-    }
-
-    public Bitmap getIcon() {
-        return mIcon;
+    public PropertyModel getModel() {
+        return mModel;
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesTileView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesTileView.java
index 20c097e7..36ea1ca9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesTileView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesTileView.java
@@ -19,8 +19,6 @@
  * View for a category name and site tiles.
  */
 public class ExploreSitesTileView extends TileWithTextView {
-    private static final int TITLE_LINES = 1;
-
     private final int mIconSizePx;
     private RoundedIconGenerator mIconGenerator;
 
@@ -29,10 +27,8 @@
         mIconSizePx = getResources().getDimensionPixelSize(R.dimen.tile_view_icon_size);
     }
 
-    public void initialize(ExploreSitesSite site, RoundedIconGenerator generator) {
+    public void initialize(RoundedIconGenerator generator) {
         mIconGenerator = generator;
-        super.initialize(site.getTitle(), /* showOfflineBadge = */ false,
-                getDrawableForBitmap(site.getIcon(), site.getTitle()), TITLE_LINES);
     }
 
     public void updateIcon(Bitmap iconImage, String text) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java
index 7d6949e..7fa7ce9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java
@@ -63,6 +63,16 @@
         void onBindViewHolder(VH viewHolder, int position, @Nullable P payload);
 
         /**
+         * Called when the {@link View} owned by {@code viewHolder} no longer needs to be attached
+         * to its parent {@link RecyclerView}.  This is a good place to free up expensive resources
+         * that might be owned by the particular {@link View} or {@code viewHolder}.
+         * @param viewHolder A view holder that will be recycled.
+         * @see RecyclerView.Adapter#onViewRecycled(ViewHolder)
+         */
+        default void
+            onViewRecycled(VH viewHolder) {}
+
+        /**
          * @param position The position of an item to be dismissed.
          * @return The set of item positions that should be dismissed simultaneously when dismissing
          *         the item at the given {@code position} (including the position itself), or an
@@ -163,6 +173,11 @@
     }
 
     @Override
+    public void onViewRecycled(VH holder) {
+        mDelegate.onViewRecycled(holder);
+    }
+
+    @Override
     public void onItemRangeInserted(ListObservable source, int index, int count) {
         notifyItemRangeInserted(index, count);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteCoordinator.java
index 11f1d94..88265ce4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteCoordinator.java
@@ -25,6 +25,7 @@
 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem;
 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggestionDelegate;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestionsList.OmniboxSuggestionListEmbedder;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
 import org.chromium.chrome.browser.omnibox.VoiceSuggestionProvider.VoiceResult;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
@@ -40,7 +41,8 @@
 /**
  * Coordinator that handles the interactions with the autocomplete system.
  */
-public class AutocompleteCoordinator implements OnSuggestionsReceivedListener {
+public class AutocompleteCoordinator
+        implements OnSuggestionsReceivedListener, UrlTextChangeListener {
     private static final String TAG = "cr_Autocomplete";
 
     // Delay triggering the omnibox results upon key press to allow the location bar to repaint
@@ -93,6 +95,11 @@
      */
     interface AutocompleteDelegate {
         /**
+         * Notified that the URL text has changed.
+         */
+        void onUrlTextChanged();
+
+        /**
          * Notified that suggestions have changed.
          * @param autocompleteText The inline autocomplete text that can be appended to the
          *                         currently entered user text.
@@ -397,7 +404,7 @@
         if (mSuggestionsShown == visible) return;
         mSuggestionsShown = visible;
         if (mSuggestionList != null) {
-            final boolean isShowing = mSuggestionList.isShown();
+            final boolean isShowing = mSuggestionList.getVisibility() == View.VISIBLE;
             if (visible && !isShowing) {
                 mIgnoreOmniboxItemSelection = true; // Reset to default value.
 
@@ -613,7 +620,11 @@
      * Notifies the autocomplete system that the text has changed that drives autocomplete and the
      * autocomplete suggestions should be updated.
      */
+    @Override
     public void onTextChangedForAutocomplete() {
+        // crbug.com/764749
+        Log.w(TAG, "onTextChangedForAutocomplete");
+
         if (mShouldPreventOmniboxAutocomplete) return;
 
         cancelPendingAutocompleteStart();
@@ -667,6 +678,8 @@
                 mDeferredNativeRunnables.add(mRequestSuggestions);
             }
         }
+
+        mDelegate.onUrlTextChanged();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 4a377a3..d3436ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -39,7 +39,6 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.CommandLine;
-import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordUserAction;
@@ -107,7 +106,7 @@
     protected BottomSheet mBottomSheet;
 
     protected UrlBarCoordinator mUrlCoordinator;
-    private AutocompleteCoordinator mAutocompleteCoordinator;
+    protected AutocompleteCoordinator mAutocompleteCoordinator;
 
     protected ToolbarDataProvider mToolbarDataProvider;
     private ObserverList<UrlFocusChangeListener> mUrlFocusChangeListeners = new ObserverList<>();
@@ -269,6 +268,7 @@
                 };
         mAutocompleteCoordinator =
                 new AutocompleteCoordinator(this, this, embedder, mUrlCoordinator);
+        mUrlCoordinator.setUrlTextChangeListener(mAutocompleteCoordinator);
 
         mMicButton = (AppCompatImageButton) findViewById(R.id.mic_button);
 
@@ -640,13 +640,9 @@
     }
 
     @Override
-    public void onTextChangedForAutocomplete() {
-        // crbug.com/764749
-        Log.w(TAG, "onTextChangedForAutocomplete");
-
+    public void onUrlTextChanged() {
         updateButtonVisibility();
         updateNavigationButton();
-        mAutocompleteCoordinator.onTextChangedForAutocomplete();
     }
 
     @Override
@@ -669,7 +665,7 @@
             // This must be happen after requestUrlFocus(), which changes the selection.
             mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(pastedText),
                     UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_END);
-            onTextChangedForAutocomplete();
+            mAutocompleteCoordinator.onTextChangedForAutocomplete();
         } else {
             ToolbarManager.recordOmniboxFocusReason(ToolbarManager.OmniboxFocusReason.FAKE_BOX_TAP);
         }
@@ -1384,7 +1380,7 @@
                                mToolbarDataProvider.getUrlBarData().getEditingOrDisplayText())) {
                 mAutocompleteCoordinator.startZeroSuggest();
             } else {
-                onTextChangedForAutocomplete();
+                mAutocompleteCoordinator.onTextChangedForAutocomplete();
             }
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/OmniboxSuggestionsList.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/OmniboxSuggestionsList.java
index 422e878..b942dcd87 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/OmniboxSuggestionsList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/OmniboxSuggestionsList.java
@@ -9,6 +9,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
 import android.text.TextUtils;
@@ -220,7 +221,15 @@
     /**
      * Update the layout params to ensure the suggestion popup is properly sized.
      */
+    // TODO(tedchoc): This should likely be done in measure/layout instead of just manipulating
+    //                the layout params.  Investigate converting to that flow.
     void updateLayoutParams() {
+        // If in the middle of a layout pass, post till it is completed to avoid the layout param
+        // update being ignored.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isInLayout()) {
+            post(this::updateLayoutParams);
+            return;
+        }
         boolean updateLayout = false;
         FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
         if (layoutParams == null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index e820f90..fc62d72e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -69,6 +69,7 @@
     private int mUrlDirection;
 
     private UrlBarDelegate mUrlBarDelegate;
+    private UrlTextChangeListener mTextChangeListener;
     private UrlBarTextContextMenuDelegate mTextContextMenuDelegate;
     private UrlDirectionListener mUrlDirectionListener;
 
@@ -156,12 +157,6 @@
         boolean allowKeyboardLearning();
 
         /**
-         * Called when the text state has changed and the autocomplete suggestions should be
-         * refreshed.
-         */
-        void onTextChangedForAutocomplete();
-
-        /**
          * Called to notify that back key has been pressed while the URL bar has focus.
          */
         void backKeyPressed();
@@ -178,6 +173,15 @@
         boolean shouldCutCopyVerbatim();
     }
 
+    /** Provides updates about the URL text changes. */
+    public interface UrlTextChangeListener {
+        /**
+         * Called when the text state has changed and the autocomplete suggestions should be
+         * refreshed.
+         */
+        void onTextChangedForAutocomplete();
+    }
+
     /** Delegate that provides the additional functionality to the textual context menus. */
     interface UrlBarTextContextMenuDelegate {
         /** @return The text to be pasted into the UrlBar. */
@@ -525,6 +529,14 @@
         mUrlBarDelegate = delegate;
     }
 
+    /**
+     * Set the listener to be notified when the URL text has changed.
+     * @param listener The listener to be notified.
+     */
+    public void setUrlTextChangeListener(UrlTextChangeListener listener) {
+        mTextChangeListener = listener;
+    }
+
     @Override
     public boolean onTextContextMenuItem(int id) {
         if (mTextContextMenuDelegate == null) return super.onTextContextMenuItem(id);
@@ -845,12 +857,12 @@
         if (DEBUG) {
             Log.i(TAG, "onAutocompleteTextStateChanged: DIS[%b]", updateDisplay);
         }
-        if (mUrlBarDelegate == null) return;
+        if (mTextChangeListener == null) return;
         if (updateDisplay) limitDisplayableLength();
         // crbug.com/764749
         Log.w(TAG, "Text change observed, triggering autocomplete.");
 
-        mUrlBarDelegate.onTextChangedForAutocomplete();
+        mTextChangeListener.onTextChangedForAutocomplete();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
index 96ff63dd..53e42b60 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,6 +54,11 @@
         mMediator.setDelegate(delegate);
     }
 
+    /** @see UrlBarMediator#setDelegate(UrlBarDelegate) */
+    public void setUrlTextChangeListener(UrlTextChangeListener listener) {
+        mMediator.setUrlTextChangeListener(listener);
+    }
+
     /** @see UrlBarMediator#setUrlBarData(UrlBarData, int, int) */
     public boolean setUrlBarData(
             UrlBarData data, @ScrollType int scrollType, @SelectionState int state) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
index d41b30c7..1087c17 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
 import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
 import org.chromium.chrome.browser.omnibox.UrlBarProperties.AutocompleteText;
 import org.chromium.chrome.browser.omnibox.UrlBarProperties.UrlBarTextState;
@@ -57,6 +58,11 @@
         mModel.set(UrlBarProperties.DELEGATE, delegate);
     }
 
+    /** Set the listener to be notified when the url text chagnes. */
+    public void setUrlTextChangeListener(UrlTextChangeListener listener) {
+        mModel.set(UrlBarProperties.URL_TEXT_CHANGE_LISTENER, listener);
+    }
+
     /**
      * Updates the text content of the UrlBar.
      *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
index 5738c02..ca2a3db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarTextContextMenuDelegate;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
 import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
 
 import java.util.Locale;
@@ -110,6 +111,10 @@
     public static final WritableObjectPropertyKey<UrlDirectionListener> URL_DIRECTION_LISTENER =
             new WritableObjectPropertyKey<>();
 
+    /** The callback to be notified on text changes. */
+    public static final WritableObjectPropertyKey<UrlTextChangeListener> URL_TEXT_CHANGE_LISTENER =
+            new WritableObjectPropertyKey<>();
+
     /** Specifies whether dark text colors should be used in the view. */
     public static final WritableBooleanPropertyKey USE_DARK_TEXT_COLORS =
             new WritableBooleanPropertyKey();
@@ -118,8 +123,8 @@
     public static final WritableObjectPropertyKey<WindowDelegate> WINDOW_DELEGATE =
             new WritableObjectPropertyKey<>();
 
-    public static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE,
-                    FOCUS_CHANGE_CALLBACK, SHOW_CURSOR, TEXT_CONTEXT_MENU_DELEGATE, TEXT_STATE,
-                    URL_DIRECTION_LISTENER, USE_DARK_TEXT_COLORS, WINDOW_DELEGATE};
+    public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {ACTION_MODE_CALLBACK,
+            ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE, FOCUS_CHANGE_CALLBACK, SHOW_CURSOR,
+            TEXT_CONTEXT_MENU_DELEGATE, TEXT_STATE, URL_DIRECTION_LISTENER,
+            URL_TEXT_CHANGE_LISTENER, USE_DARK_TEXT_COLORS, WINDOW_DELEGATE};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
index d0300e3a..9a1f8df 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
@@ -69,6 +69,8 @@
             updateTextColors(view, model.get(UrlBarProperties.USE_DARK_TEXT_COLORS));
         } else if (UrlBarProperties.URL_DIRECTION_LISTENER.equals(propertyKey)) {
             view.setUrlDirectionListener(model.get(UrlBarProperties.URL_DIRECTION_LISTENER));
+        } else if (UrlBarProperties.URL_TEXT_CHANGE_LISTENER.equals(propertyKey)) {
+            view.setUrlTextChangeListener(model.get(UrlBarProperties.URL_TEXT_CHANGE_LISTENER));
         } else if (UrlBarProperties.WINDOW_DELEGATE.equals(propertyKey)) {
             view.setWindowDelegate(model.get(UrlBarProperties.WINDOW_DELEGATE));
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
index 815cdf9..3b90470e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/WebsitePermissionsFetcher.java
@@ -234,6 +234,17 @@
     }
 
     private void setException(int contentSettingsType) {
+        @ContentSettingException.Type
+        int exceptionType;
+        for (exceptionType = 0; exceptionType < ContentSettingException.Type.NUM_ENTRIES;
+                exceptionType++) {
+            if (contentSettingsType == ContentSettingException.CONTENT_TYPES[exceptionType]) break;
+        }
+        assert contentSettingsType
+                == ContentSettingException.CONTENT_TYPES[exceptionType]
+            : "Unexpected content setting type received: "
+                        + contentSettingsType;
+
         for (ContentSettingException exception :
                 WebsitePreferenceBridge.getContentSettingsExceptions(contentSettingsType)) {
             // The pattern "*" represents the default setting, not a specific website.
@@ -241,13 +252,7 @@
             WebsiteAddress address = WebsiteAddress.create(exception.getPattern());
             if (address == null) continue;
             Website site = findOrCreateSite(address, null);
-            for (int i = 0; i < ContentSettingException.CONTENT_TYPES.length; i++) {
-                if (contentSettingsType == ContentSettingException.CONTENT_TYPES[i]) {
-                    site.setContentSettingException(i, exception);
-                    return;
-                }
-            }
-            assert false : "Unexpected content setting type received: " + contentSettingsType;
+            site.setContentSettingException(exceptionType, exception);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
index b27ac35..2e62d56 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
@@ -90,7 +90,7 @@
         getAutocompleteCoordinator().setShouldPreventOmniboxAutocomplete(
                 mPendingSearchPromoDecision);
         if (!TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete())) {
-            onTextChangedForAutocomplete();
+            mAutocompleteCoordinator.onTextChangedForAutocomplete();
         }
 
         if (mPendingBeginQuery) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
index f73b4eb..b0ec39d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
@@ -749,9 +749,6 @@
     // Toolbar and LocationBar calls that are not relevant here.
 
     @Override
-    public void onTextChangedForAutocomplete() {}
-
-    @Override
     public void backKeyPressed() {
         assert false : "The URL bar should never take focus in CCTs.";
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/tile/TileWithTextView.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/tile/TileWithTextView.java
index 83d50f5..ab99f13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/tile/TileWithTextView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/tile/TileWithTextView.java
@@ -48,12 +48,17 @@
      * @param titleLines The number of text lines to use for the tile title.
      */
     public void initialize(String title, boolean showOfflineBadge, Drawable icon, int titleLines) {
-        mTitleView.setLines(titleLines);
-        mTitleView.setText(title);
+        setTitle(title, titleLines);
         setOfflineBadgeVisibility(showOfflineBadge);
         setIconDrawable(icon);
     }
 
+    /** Sets the title text and number lines. */
+    public void setTitle(String title, int titleLines) {
+        mTitleView.setLines(titleLines);
+        mTitleView.setText(title);
+    }
+
     /**
      * Renders the icon or clears it from the view if the icon is null.
      */
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 8359743..1c0457a 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -517,7 +517,6 @@
   "java/src/org/chromium/chrome/browser/download/home/list/ListItem.java",
   "java/src/org/chromium/chrome/browser/download/home/list/ListItemModel.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/CustomViewHolder.java",
-  "java/src/org/chromium/chrome/browser/download/home/list/holder/DateViewHolder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/GenericViewHolder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/InProgressImageViewHolder.java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
index 1da03d1..b98a78b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
@@ -22,7 +22,6 @@
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
-import org.chromium.chrome.browser.download.home.list.ListItem.DateListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.SeparatorViewListItem;
@@ -32,6 +31,7 @@
 
 import java.util.Calendar;
 import java.util.Collections;
+import java.util.Date;
 
 /** Unit tests for the DateOrderedListMutator class. */
 @RunWith(BaseRobolectricTestRunner.class)
@@ -84,11 +84,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item1);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
     }
 
     /**
@@ -107,12 +106,11 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(4, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(3, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 2), item1);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
     }
 
     /**
@@ -133,15 +131,76 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(6, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 2), item1);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 1, 0), false);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 1, 0), false);
         assertSectionHeader(
-                mModel.get(4), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_AUDIO);
-        assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 1), item2);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_AUDIO, false);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 1), item2);
+    }
+
+    /**
+     * Action                                     List
+     * 1. Set(item1 @ 2:00 1/1/2018 Video,        [ DATE    @ 0:00 1/1/2018,
+     *        item2 @ 1:00 1/1/2018 Image)          SECTION @ Video,
+     *                                              item1   @ 2:00 1/1/2018,
+     *                                              -----------------------
+     *                                              SECTION @ Image,
+     *                                              item2   @ 1:00 1/1/2018 ]
+     */
+    @Test
+    public void testShowMenuButtonForImageSectionWithoutDate() {
+        OfflineItem item1 =
+                buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.FILTER_VIDEO);
+        OfflineItem item2 =
+                buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.FILTER_IMAGE);
+        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
+        DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
+
+        Assert.assertEquals(5, mModel.size());
+        assertSectionHeader(
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        Assert.assertFalse(((SectionHeaderListItem) mModel.get(0)).showMenu);
+
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 1, 0), false);
+        assertSectionHeader(
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_IMAGE, false);
+        Assert.assertTrue(((SectionHeaderListItem) mModel.get(3)).showMenu);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 1), item2);
+    }
+
+    /**
+     * Action                                     List
+     * 1. Set(item1 @ 2:00 1/1/2018 Image,        [ DATE    @ 0:00 1/1/2018,
+     *        item2 @ 1:00 1/1/2018 Page)           SECTION @ Image,
+     *                                              item1   @ 2:00 1/1/2018,
+     *                                              -----------------------
+     *                                              SECTION @ Page,
+     *                                              item2   @ 1:00 1/1/2018 ]
+     */
+    @Test
+    public void testShowMenuButtonForImageSectionWithDate() {
+        OfflineItem item1 =
+                buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.FILTER_IMAGE);
+        OfflineItem item2 =
+                buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.FILTER_PAGE);
+        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
+        DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
+
+        Assert.assertEquals(5, mModel.size());
+        assertSectionHeader(
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_IMAGE, true);
+        Assert.assertTrue(((SectionHeaderListItem) mModel.get(0)).showMenu);
+
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 1, 0), false);
+        assertSectionHeader(
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_PAGE, false);
+        Assert.assertFalse(((SectionHeaderListItem) mModel.get(3)).showMenu);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 1), item2);
     }
 
     /**
@@ -163,16 +222,14 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(7, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 0), item1);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(4), buildCalendar(2018, 1, 1, 0));
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 0), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 2, 0), true);
         assertSectionHeader(
-                mModel.get(5), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_AUDIO);
-        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 1, 0), item2);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_AUDIO, true);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 0), item2);
     }
 
     /**
@@ -191,12 +248,11 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(4, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(3, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 5), item2);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 4), item1);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 5), item2);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item1);
     }
 
     /**
@@ -218,16 +274,14 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(7, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 4), item1);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(4), buildCalendar(2018, 1, 1, 0));
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 2, 0), true);
         assertSectionHeader(
-                mModel.get(5), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 1, 5), item2);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 5), item2);
     }
 
     /**
@@ -249,16 +303,14 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(7, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 4), item1);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(4), buildCalendar(2018, 1, 1, 0));
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 2, 0), true);
         assertSectionHeader(
-                mModel.get(5), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_PAGE);
-        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 1, 5), item2);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_PAGE, true);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 5), item2);
     }
 
     /**
@@ -278,16 +330,14 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
 
-        Assert.assertEquals(7, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item2);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(4), buildCalendar(2018, 1, 1, 0));
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item2);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 2, 0), true);
         assertSectionHeader(
-                mModel.get(5), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 1, 4), item1);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 4), item1);
     }
 
     /**
@@ -308,9 +358,8 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1));
         list.onItemsAdded(CollectionUtil.newArrayList(item1));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item1);
+        Assert.assertEquals(2, mModel.size());
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item1);
     }
 
     /**
@@ -345,10 +394,9 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         list.onItemsAdded(CollectionUtil.newArrayList(item2));
 
-        Assert.assertEquals(4, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 2), item2);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 2, 1), item1);
+        Assert.assertEquals(3, mModel.size());
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 1), item1);
 
         // Add an item on an earlier day that will be placed first.
         OfflineItem item3 =
@@ -356,13 +404,11 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2, item3));
         list.onItemsAdded(CollectionUtil.newArrayList(item3));
 
-        Assert.assertEquals(8, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 3, 0));
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 3, 2), item3);
-        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 3, 0), true);
-        assertDateHeader(mModel.get(4), buildCalendar(2018, 1, 2, 0));
-        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 2, 2), item2);
-        assertOfflineItem(mModel.get(7), buildCalendar(2018, 1, 2, 1), item1);
+        Assert.assertEquals(6, mModel.size());
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 2), item3);
+        assertSeparator(mModel.get(2), buildCalendar(2018, 1, 3, 0), true);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 2, 2), item2);
+        assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 2, 1), item1);
     }
 
     /**
@@ -399,10 +445,9 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         list.onItemsAdded(CollectionUtil.newArrayList(item2));
 
-        Assert.assertEquals(4, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 4), item1);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 2, 3), item2);
+        Assert.assertEquals(3, mModel.size());
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item2);
 
         // Add an item on a later day that will be placed last.
         OfflineItem item3 =
@@ -410,13 +455,11 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2, item3));
         list.onItemsAdded(CollectionUtil.newArrayList(item3));
 
-        Assert.assertEquals(8, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 4), item1);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 2, 3), item2);
-        assertSeparator(mModel.get(4), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(5), buildCalendar(2018, 1, 1, 0));
-        assertOfflineItem(mModel.get(7), buildCalendar(2018, 1, 1, 4), item3);
+        Assert.assertEquals(6, mModel.size());
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item2);
+        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
+        assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 4), item3);
     }
 
     /**
@@ -465,11 +508,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item2));
         list.onItemsRemoved(CollectionUtil.newArrayList(item1));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 2), item2);
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
     }
 
     /**
@@ -496,11 +538,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1));
         list.onItemsRemoved(CollectionUtil.newArrayList(item2));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item1);
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item1);
     }
 
     /**
@@ -525,16 +566,15 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = new DateOrderedListMutator(mSource, mModel);
         mModel.addObserver(mObserver);
-        Assert.assertEquals(6, mModel.size());
+        Assert.assertEquals(5, mModel.size());
 
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item2));
         list.onItemsRemoved(CollectionUtil.newArrayList(item1));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_IMAGE);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 2), item2);
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_IMAGE, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
     }
 
     /**
@@ -564,11 +604,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1));
         list.onItemsRemoved(CollectionUtil.newArrayList(item2));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 3, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 3, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 3, 3), item1);
+                mModel.get(0), buildCalendar(2018, 1, 3, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 3), item1);
     }
 
     /**
@@ -604,18 +643,16 @@
                 .thenReturn(CollectionUtil.newArrayList(item1, item2, item3, item4));
         list.onItemsAdded(CollectionUtil.newArrayList(item1, item2, item3, item4));
 
-        Assert.assertEquals(9, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(7, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 12), item4);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 2, 10), item3);
-        assertSeparator(mModel.get(4), buildCalendar(2018, 1, 2, 0), true);
-        assertDateHeader(mModel.get(5), buildCalendar(2018, 1, 1, 0));
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 12), item4);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 10), item3);
+        assertSeparator(mModel.get(3), buildCalendar(2018, 1, 2, 0), true);
         assertSectionHeader(
-                mModel.get(6), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(7), buildCalendar(2018, 1, 1, 6), item1);
-        assertOfflineItem(mModel.get(8), buildCalendar(2018, 1, 1, 4), item2);
+                mModel.get(4), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 6), item1);
+        assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 1, 4), item2);
     }
 
     /**
@@ -653,11 +690,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1));
         list.onItemsRemoved(CollectionUtil.newArrayList(item2, item3, item4));
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 6), item1);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), item1);
     }
 
     /**
@@ -686,11 +722,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(newItem1));
         list.onItemUpdated(item1, newItem1);
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), newItem1);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), newItem1);
     }
 
     /**
@@ -722,12 +757,11 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(newItem1, item2));
         list.onItemUpdated(item1, newItem1);
 
-        Assert.assertEquals(4, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(3, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item2);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 3), newItem1);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 3), newItem1);
     }
 
     /**
@@ -761,14 +795,13 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(newItem1, item2));
         list.onItemUpdated(item1, newItem1);
 
-        Assert.assertEquals(6, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0));
+        Assert.assertEquals(5, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item2);
+                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
         assertSectionHeader(
-                mModel.get(4), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_IMAGE);
-        assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 3), newItem1);
+                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.FILTER_IMAGE, false);
+        assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 3), newItem1);
     }
 
     /**
@@ -797,11 +830,10 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(newItem1));
         list.onItemUpdated(item1, newItem1);
 
-        Assert.assertEquals(3, mModel.size());
-        assertDateHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0));
+        Assert.assertEquals(2, mModel.size());
         assertSectionHeader(
-                mModel.get(1), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO);
-        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 6), newItem1);
+                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.FILTER_VIDEO, true);
+        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 6), newItem1);
     }
 
     private static Calendar buildCalendar(int year, int month, int dayOfMonth, int hourOfDay) {
@@ -820,44 +852,35 @@
         return item;
     }
 
-    private static void assertDatesAreEqual(ListItem item, Calendar calendar) {
-        Assert.assertTrue(item instanceof DateListItem);
+    private static void assertDatesAreEqual(Date date, Calendar calendar) {
         Calendar calendar2 = CalendarFactory.get();
-        calendar2.setTime(((DateListItem) item).date);
+        calendar2.setTime(date);
         Assert.assertEquals(calendar.getTimeInMillis(), calendar2.getTimeInMillis());
     }
 
-    private static void assertDateHeader(ListItem item, Calendar calendar) {
-        Assert.assertTrue(item instanceof DateListItem);
-        Assert.assertFalse(item instanceof OfflineItemListItem);
-        Assert.assertFalse(item instanceof SectionHeaderListItem);
-        Assert.assertFalse(item instanceof SeparatorViewListItem);
-
-        assertDatesAreEqual(item, calendar);
-        Assert.assertEquals(DateListItem.generateStableIdForDayOfYear(calendar), item.stableId);
-    }
-
     private static void assertOfflineItem(
             ListItem item, Calendar calendar, OfflineItem offlineItem) {
-        assertDatesAreEqual(item, calendar);
         Assert.assertTrue(item instanceof OfflineItemListItem);
+        assertDatesAreEqual(((OfflineItemListItem) item).date, calendar);
         Assert.assertEquals(OfflineItemListItem.generateStableId(offlineItem), item.stableId);
         Assert.assertEquals(offlineItem, ((OfflineItemListItem) item).item);
     }
 
     private static void assertSectionHeader(
-            ListItem item, Calendar calendar, @OfflineItemFilter int filter) {
-        assertDatesAreEqual(item, calendar);
+            ListItem item, Calendar calendar, @OfflineItemFilter int filter, boolean showDate) {
         Assert.assertTrue(item instanceof SectionHeaderListItem);
-        Assert.assertEquals(filter, ((SectionHeaderListItem) item).filter);
+        SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
+        assertDatesAreEqual(sectionHeader.date, calendar);
+        Assert.assertEquals(filter, sectionHeader.filter);
         Assert.assertEquals(
                 SectionHeaderListItem.generateStableId(calendar.getTimeInMillis(), filter),
                 item.stableId);
+        Assert.assertEquals(sectionHeader.showDate, showDate);
     }
 
     private static void assertSeparator(ListItem item, Calendar calendar, boolean isDateDivider) {
-        assertDatesAreEqual(item, calendar);
         Assert.assertTrue(item instanceof SeparatorViewListItem);
+        assertDatesAreEqual(((SeparatorViewListItem) item).date, calendar);
         Assert.assertEquals(isDateDivider, ((SeparatorViewListItem) item).isDateDivider());
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java
index 1b6a1f9..c5169d40 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java
@@ -32,8 +32,8 @@
         assertEquals(id, category.getId());
         assertEquals(type, category.getType());
         assertEquals(1, category.getSites().size());
-        assertEquals(siteId, category.getSites().get(0).getId());
-        assertEquals(title, category.getSites().get(0).getTitle());
-        assertEquals(url, category.getSites().get(0).getUrl());
+        assertEquals(siteId, category.getSites().get(0).getModel().get(ExploreSitesSite.ID_KEY));
+        assertEquals(title, category.getSites().get(0).getModel().get(ExploreSitesSite.TITLE_KEY));
+        assertEquals(url, category.getSites().get(0).getModel().get(ExploreSitesSite.URL_KEY));
     }
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index abf476e..fba165d 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8517,12 +8517,6 @@
         <message name="IDS_LOCAL_DISCOVERY_ADDING_DEVICE_MESSAGE1" desc="Message for adding device number 1">
           Adding the device to your account - this may take a moment...
         </message>
-        <message name="IDS_LOCAL_DISCOVERY_CONFIRM_CODE_MESSAGE" desc="Message for confirming device security code">
-          Make sure the device is showing the same code.
-        </message>
-        <message name="IDS_LOCAL_DISCOVERY_CONFIRM_CODE" desc="button name to confirming security code">
-          Confirm
-        </message>
         <message name="IDS_LOCAL_DISCOVERY_ERROR_OCURRED_MESSAGE" desc="Message for error page">
           An error has occurred. Please check your printer and try again.
         </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3f93fe26..b19bab92 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2168,6 +2168,8 @@
       "android/explore_sites/get_images_task.h",
       "android/explore_sites/history_statistics_reporter.cc",
       "android/explore_sites/history_statistics_reporter.h",
+      "android/explore_sites/image_helper.cc",
+      "android/explore_sites/image_helper.h",
       "android/explore_sites/import_catalog_task.cc",
       "android/explore_sites/import_catalog_task.h",
       "android/explore_sites/ntp_json_fetcher.cc",
@@ -2925,6 +2927,8 @@
       "search/one_google_bar/one_google_bar_service_observer.h",
       "search/search_engine_base_url_tracker.cc",
       "search/search_engine_base_url_tracker.h",
+      "search/url_validity_checker_factory.cc",
+      "search/url_validity_checker_factory.h",
       "signin/mutable_profile_oauth2_token_service_delegate.cc",
       "signin/mutable_profile_oauth2_token_service_delegate.h",
       "signin/signin_promo.cc",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index ffde01a..b1a05ea 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -223,6 +223,11 @@
     # Needed for classic mode.
     "+ash/ash_service.h",
   ],
+  "platform_util_mac.mm": [
+    # The following is used to forward methods to an NSWindow in another
+    # process, via the views::Widget API.
+    "+ui/views/widget/widget.h",
+  ],
   # TODO(mash): Fix. https://crbug.com/768439, https://crbug.com/768395.
   "exo_parts\.cc": [
     "+ash/shell.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7faa7f6f2..7d8b3fc 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2297,6 +2297,10 @@
      flag_descriptions::kEnablePreviewsAndroidOmniboxUIName,
      flag_descriptions::kEnablePreviewsAndroidOmniboxUIDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(previews::features::kAndroidOmniboxPreviewsBadge)},
+    {"enable-lite-page-server-previews",
+     flag_descriptions::kEnableLitePageServerPreviewsName,
+     flag_descriptions::kEnableLitePageServerPreviewsDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(previews::features::kLitePageServerPreviews)},
 #endif  // OS_ANDROID
     {"enable-client-lo-fi", flag_descriptions::kEnableClientLoFiName,
      flag_descriptions::kEnableClientLoFiDescription, kOsAll,
@@ -2345,6 +2349,10 @@
     {"enable-system-webapps", flag_descriptions::kEnableSystemWebAppsName,
      flag_descriptions::kEnableSystemWebAppsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kSystemWebApps)},
+    {"enable-desktop-pwas-stay-in-window",
+     flag_descriptions::kDesktopPWAsStayInWindowName,
+     flag_descriptions::kDesktopPWAsStayInWindowDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kDesktopPWAsStayInWindow)},
     {"use-sync-sandbox", flag_descriptions::kSyncSandboxName,
      flag_descriptions::kSyncSandboxDescription, kOsAll,
      SINGLE_VALUE_TYPE_AND_VALUE(
diff --git a/chrome/browser/android/download/available_offline_content_provider.cc b/chrome/browser/android/download/available_offline_content_provider.cc
index 6f5bb2d..632f26c 100644
--- a/chrome/browser/android/download/available_offline_content_provider.cc
+++ b/chrome/browser/android/download/available_offline_content_provider.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/android/download/available_offline_content_provider.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/base64.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/strcat.h"
@@ -226,9 +229,10 @@
       offline_items_collection::ContentId(name_space, item_id));
 }
 
-void AvailableOfflineContentProvider::LaunchDownloadsPage() {
+void AvailableOfflineContentProvider::LaunchDownloadsPage(
+    bool open_prefetched_articles_tab) {
   DownloadManagerService::GetInstance()->ShowDownloadManager(
-      has_prefetched_content_);
+      open_prefetched_articles_tab);
 }
 
 void AvailableOfflineContentProvider::Create(
@@ -268,7 +272,6 @@
         break;
     }
   }
-  has_prefetched_content_ = summary->has_prefetched_page;
 
   // If the number of interesting items is lower then the minimum required then
   // reset all summary data so avoid presenting the card.
@@ -289,12 +292,12 @@
   // If the number of interesting items is lower then the minimum don't show any
   // suggestions. Otherwise trim it down to the number of expected items.
   size_t copied_count = end - selected.begin();
-  if (copied_count == kMinInterestingItemCount &&
-      ContentType(selected[kMinInterestingItemCount - 1]) !=
-          AvailableContentType::kUninteresting) {
-    selected.resize(kMaxListItemsToReturn);
-  } else {
+  DCHECK(copied_count <= kMinInterestingItemCount);
+  if (copied_count < kMinInterestingItemCount ||
+      ContentType(selected.back()) == AvailableContentType::kUninteresting) {
     selected.clear();
+  } else {
+    selected.resize(kMaxListItemsToReturn);
   }
 
   std::vector<offline_items_collection::ContentId> selected_ids;
diff --git a/chrome/browser/android/download/available_offline_content_provider.h b/chrome/browser/android/download/available_offline_content_provider.h
index 414009f..5db0b3e 100644
--- a/chrome/browser/android/download/available_offline_content_provider.h
+++ b/chrome/browser/android/download/available_offline_content_provider.h
@@ -5,6 +5,9 @@
 #ifndef CHROME_BROWSER_ANDROID_DOWNLOAD_AVAILABLE_OFFLINE_CONTENT_PROVIDER_H_
 #define CHROME_BROWSER_ANDROID_DOWNLOAD_AVAILABLE_OFFLINE_CONTENT_PROVIDER_H_
 
+#include <string>
+#include <vector>
+
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/common/available_offline_content.mojom.h"
@@ -34,7 +37,7 @@
   void Summarize(SummarizeCallback) override;
   void LaunchItem(const std::string& item_id,
                   const std::string& name_space) override;
-  void LaunchDownloadsPage() override;
+  void LaunchDownloadsPage(bool open_prefetched_articles_tab) override;
 
   static void Create(
       content::BrowserContext* browser_context,
@@ -52,11 +55,6 @@
 
   content::BrowserContext* browser_context_;
 
-  // Records if the last content fetch indicated that prefetched articles are
-  // available or not.
-  // TODO(carlosk): Directly check the existence of prefetched articles.
-  bool has_prefetched_content_ = false;
-
   base::WeakPtrFactory<AvailableOfflineContentProvider> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(AvailableOfflineContentProvider);
diff --git a/chrome/browser/android/download/available_offline_content_provider_unittest.cc b/chrome/browser/android/download/available_offline_content_provider_unittest.cc
index f596540..43a7445 100644
--- a/chrome/browser/android/download/available_offline_content_provider_unittest.cc
+++ b/chrome/browser/android/download/available_offline_content_provider_unittest.cc
@@ -53,8 +53,6 @@
   item.filter = offline_items_collection::FILTER_PAGE;
   item.id.id = "NonSuggestedOfflinePage";
   item.id.name_space = kProviderNamespace;
-  // Configuring this item as being read to make sure that's not taken into
-  // account for filtering and prioritization.
   item.last_accessed_time = base::Time::Now();
   return item;
 }
@@ -72,8 +70,6 @@
   // even if the test takes 1 hour to run.
   item.creation_time =
       base::Time::Now() - base::TimeDelta::FromMinutes(60 * 3.5);
-  // Configuring this item as being read to make sure that's not taken into
-  // account for filtering and prioritization.
   item.last_accessed_time = base::Time::Now();
   return item;
 }
@@ -129,6 +125,7 @@
 class AvailableOfflineContentTest : public testing::Test {
  protected:
   void SetUp() override {
+    scoped_feature_list_->InitAndEnableFeature(features::kNewNetErrorPageUI);
     // To control the items in the aggregator, we create it and register a
     // single MockOfflineContentProvider.
     aggregator_ = static_cast<OfflineContentAggregator*>(
@@ -156,14 +153,14 @@
 
   content::TestBrowserThreadBundle thread_bundle_;
   TestingProfile profile_;
-  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_ =
+      std::make_unique<base::test::ScopedFeatureList>();
   OfflineContentAggregator* aggregator_;
   offline_items_collection::MockOfflineContentProvider content_provider_;
   AvailableOfflineContentProvider provider_{&profile_};
 };
 
 TEST_F(AvailableOfflineContentTest, NoContent) {
-  scoped_feature_list_.InitAndEnableFeature(features::kNewNetErrorPageUI);
   std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions =
       ListAndWait();
   chrome::mojom::AvailableOfflineContentSummaryPtr summary = SummarizeAndWait();
@@ -177,7 +174,6 @@
 }
 
 TEST_F(AvailableOfflineContentTest, TooFewInterestingItems) {
-  scoped_feature_list_.InitAndEnableFeature(features::kNewNetErrorPageUI);
   // Adds items so that we're one-ff of reaching the minimum required count so
   // that any extra item considered interesting would effect the results.
   content_provider_.SetItems({UninterestingImageItem(), OfflinePageItem(),
@@ -202,7 +198,6 @@
 }
 
 TEST_F(AvailableOfflineContentTest, FourInterestingItems) {
-  scoped_feature_list_.InitAndEnableFeature(features::kNewNetErrorPageUI);
   // We need at least 4 interesting items for anything to show up at all.
   content_provider_.SetItems({UninterestingImageItem(), VideoItem(),
                               SuggestedOfflinePageItem(), AudioItem(),
@@ -251,7 +246,8 @@
 }
 
 TEST_F(AvailableOfflineContentTest, NotEnabled) {
-  scoped_feature_list_.InitAndDisableFeature(features::kNewNetErrorPageUI);
+  scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
+  scoped_feature_list_->InitAndDisableFeature(features::kNewNetErrorPageUI);
   content_provider_.SetItems({SuggestedOfflinePageItem()});
 
   std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions =
diff --git a/chrome/browser/android/explore_sites/explore_sites_bridge.cc b/chrome/browser/android/explore_sites/explore_sites_bridge.cc
index e2dfde387..3edb839 100644
--- a/chrome/browser/android/explore_sites/explore_sites_bridge.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_bridge.cc
@@ -127,7 +127,6 @@
       ExploreSitesServiceFactory::GetForBrowserContext(profile);
   if (!service) {
     DLOG(ERROR) << "Unable to create the ExploreSitesService!";
-
     base::android::RunObjectCallbackAndroid(j_callback_obj, nullptr);
     return;
   }
@@ -175,4 +174,34 @@
   Java_ExploreSitesBridge_scheduleDailyTask(env);
 }
 
+float ExploreSitesBridge::GetScaleFactorFromDevice() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  // Get scale factor from Java as a float.
+  return Java_ExploreSitesBridge_getScaleFactorFromDevice(env);
+}
+
+// static
+void JNI_ExploreSitesBridge_GetCategoryImage(
+    JNIEnv* env,
+    const JavaParamRef<jclass>& j_caller,
+    const JavaParamRef<jobject>& j_profile,
+    const jint j_category_id,
+    const jint j_pixel_size,
+    const JavaParamRef<jobject>& j_callback_obj) {
+  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+  DCHECK(profile);
+
+  ExploreSitesService* service =
+      ExploreSitesServiceFactory::GetForBrowserContext(profile);
+  if (!service) {
+    DLOG(ERROR) << "Unable to create the ExploreSitesService!";
+    base::android::RunBooleanCallbackAndroid(j_callback_obj, false);
+    return;
+  }
+
+  service->GetCategoryImage(
+      j_category_id, j_pixel_size,
+      base::BindOnce(&ImageReady,
+                     ScopedJavaGlobalRef<jobject>(j_callback_obj)));
+}
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/explore_sites_bridge.h b/chrome/browser/android/explore_sites/explore_sites_bridge.h
index 9de139a..98a66e5 100644
--- a/chrome/browser/android/explore_sites/explore_sites_bridge.h
+++ b/chrome/browser/android/explore_sites/explore_sites_bridge.h
@@ -14,6 +14,9 @@
   // The catalog update task checks that the feature is enabled and if not,
   // unschedules itself.
   static void ScheduleDailyTask();
+
+  // Gets the device screen scale factor from Android.
+  static float GetScaleFactorFromDevice();
 };
 
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/explore_sites_fetcher.cc b/chrome/browser/android/explore_sites/explore_sites_fetcher.cc
index 7adf18d6..2f80e466 100644
--- a/chrome/browser/android/explore_sites/explore_sites_fetcher.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_fetcher.cc
@@ -16,6 +16,7 @@
 #include "base/version.h"
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/explore_sites/catalog.pb.h"
+#include "chrome/browser/android/explore_sites/explore_sites_bridge.h"
 #include "chrome/browser/android/explore_sites/explore_sites_feature.h"
 #include "chrome/browser/android/explore_sites/explore_sites_types.h"
 #include "chrome/browser/android/explore_sites/url_util.h"
@@ -153,6 +154,7 @@
       max_failure_count_(is_immediate_fetch
                              ? kMaxFailureCountForImmediateFetch
                              : kMaxFailureCountForBackgroundFetch),
+      device_delegate_(std::make_unique<DeviceDelegate>()),
       callback_(std::move(callback)),
       url_loader_factory_(loader_factory),
       weak_factory_(this) {
@@ -183,6 +185,10 @@
   resource_request->headers.SetHeader("X-Client-Version", client_version_);
   resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
                                       kRequestContentType);
+  std::string scale_factor =
+      std::to_string(device_delegate_->GetScaleFactorFromDevice());
+  resource_request->headers.SetHeader("X-Device-Scale-Factor", scale_factor);
+
   if (!accept_languages_.empty()) {
     resource_request->headers.SetHeader(
         net::HttpRequestHeaders::kAcceptLanguage, accept_languages_);
@@ -204,6 +210,15 @@
                      weak_factory_.GetWeakPtr()));
 }
 
+float ExploreSitesFetcher::DeviceDelegate::GetScaleFactorFromDevice() {
+  return ExploreSitesBridge::GetScaleFactorFromDevice();
+}
+
+void ExploreSitesFetcher::SetDeviceDelegateForTest(
+    std::unique_ptr<ExploreSitesFetcher::DeviceDelegate> device_delegate) {
+  device_delegate_ = std::move(device_delegate);
+}
+
 void ExploreSitesFetcher::OnSimpleLoaderComplete(
     std::unique_ptr<std::string> response_body) {
   ExploreSitesRequestStatus status = HandleResponseCode();
diff --git a/chrome/browser/android/explore_sites/explore_sites_fetcher.h b/chrome/browser/android/explore_sites/explore_sites_fetcher.h
index 8b331dd..8497d55d 100644
--- a/chrome/browser/android/explore_sites/explore_sites_fetcher.h
+++ b/chrome/browser/android/explore_sites/explore_sites_fetcher.h
@@ -57,6 +57,18 @@
 
   void disable_retry_for_testing() { disable_retry_for_testing_ = true; }
 
+  // Delegate that knows how to get data from the device.  Can be overridden for
+  // testing.
+  class DeviceDelegate {
+   public:
+    DeviceDelegate() = default;
+    virtual ~DeviceDelegate() = default;
+    virtual float GetScaleFactorFromDevice();
+  };
+
+  // Allow overriding device specific functionality for testing
+  void SetDeviceDelegateForTest(std::unique_ptr<DeviceDelegate> delegate);
+
  private:
   explicit ExploreSitesFetcher(
       bool is_immediate_fetch,
@@ -81,6 +93,7 @@
   int max_failure_count_;
   bool disable_retry_for_testing_ = false;
 
+  std::unique_ptr<DeviceDelegate> device_delegate_;
   Callback callback_;
 
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
diff --git a/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc b/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
index d720b85..9cf30c1 100644
--- a/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_fetcher_unittest.cc
@@ -41,6 +41,12 @@
 
 namespace explore_sites {
 
+class TestingDeviceDelegate : public ExploreSitesFetcher::DeviceDelegate {
+ public:
+  TestingDeviceDelegate() = default;
+  float GetScaleFactorFromDevice() override { return 1.5; }
+};
+
 // TODO(freedjm): Add tests for the headers.
 class ExploreSitesFetcherTest : public testing::Test {
  public:
@@ -249,6 +255,7 @@
           test_shared_url_loader_factory_, StoreResult());
   if (disable_retry)
     fetcher->disable_retry_for_testing();
+  fetcher->SetDeviceDelegateForTest(std::make_unique<TestingDeviceDelegate>());
   fetcher->Start();
   return fetcher;
 }
@@ -328,6 +335,7 @@
   net::HttpRequestHeaders headers = last_resource_request.headers;
   std::string content_type;
   std::string languages;
+  std::string scale_factor;
   bool success;
 
   success = headers.HasHeader("x-goog-api-key");
@@ -336,15 +344,19 @@
   success = headers.HasHeader("X-Client-Version");
   EXPECT_TRUE(success);
 
+  success = headers.GetHeader("X-Device-Scale-Factor", &scale_factor);
+  EXPECT_TRUE(success);
+  EXPECT_EQ(1.5, std::stof(scale_factor));
+
   success =
       headers.GetHeader(net::HttpRequestHeaders::kContentType, &content_type);
   EXPECT_TRUE(success);
-  EXPECT_EQ(content_type, "application/x-protobuf");
+  EXPECT_EQ("application/x-protobuf", content_type);
 
   success =
       headers.GetHeader(net::HttpRequestHeaders::kAcceptLanguage, &languages);
   EXPECT_TRUE(success);
-  EXPECT_EQ(languages, kAcceptLanguages);
+  EXPECT_EQ(kAcceptLanguages, languages);
 
   // The finch header should not be set since the experiment is not on.
   success = headers.HasHeader("X-Google-Chrome-Experiment-Tag");
diff --git a/chrome/browser/android/explore_sites/explore_sites_service.h b/chrome/browser/android/explore_sites/explore_sites_service.h
index c3558a3..f0ec5f7d 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service.h
+++ b/chrome/browser/android/explore_sites/explore_sites_service.h
@@ -23,7 +23,9 @@
   // multiple site images. The site images are checked against the user
   // blacklist so that unwanted sites are not represented in the category image.
   // Returns |nullptr| if there was an error, or no match.
-  virtual void GetCategoryImage(int category_id, BitmapCallback callback) = 0;
+  virtual void GetCategoryImage(int category_id,
+                                int pixel_size,
+                                BitmapCallback callback) = 0;
 
   // Returns via callback the image for a site. This is typically the site
   // favicon. Returns |nullptr| if there was an error or no match for |site_id|.
diff --git a/chrome/browser/android/explore_sites/explore_sites_service_impl.cc b/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
index a47e0b0..24cdb24 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
@@ -12,15 +12,10 @@
 #include "chrome/browser/android/explore_sites/explore_sites_store.h"
 #include "chrome/browser/android/explore_sites/get_catalog_task.h"
 #include "chrome/browser/android/explore_sites/get_images_task.h"
+#include "chrome/browser/android/explore_sites/image_helper.h"
 #include "chrome/browser/android/explore_sites/import_catalog_task.h"
 #include "components/offline_pages/task/task.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/data_decoder/public/cpp/decode_image.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/service_manager/public/cpp/connector.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/geometry/size.h"
 
 using chrome::android::explore_sites::ExploreSitesVariation;
 using chrome::android::explore_sites::GetExploreSitesVariation;
@@ -63,19 +58,21 @@
 }
 
 void ExploreSitesServiceImpl::GetCategoryImage(int category_id,
+                                               int pixel_size,
                                                BitmapCallback callback) {
   task_queue_.AddTask(std::make_unique<GetImagesTask>(
       explore_sites_store_.get(), category_id, kFaviconsPerCategoryImage,
-      base::BindOnce(&ExploreSitesServiceImpl::DecodeImageBytes,
-                     std::move(callback))));
+      base::BindOnce(&ExploreSitesServiceImpl::ComposeCategoryImage,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     pixel_size)));
 }
 
 void ExploreSitesServiceImpl::GetSiteImage(int site_id,
                                            BitmapCallback callback) {
   task_queue_.AddTask(std::make_unique<GetImagesTask>(
       explore_sites_store_.get(), site_id,
-      base::BindOnce(&ExploreSitesServiceImpl::DecodeImageBytes,
-                     std::move(callback))));
+      base::BindOnce(&ExploreSitesServiceImpl::ComposeSiteImage,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
 }
 
 void ExploreSitesServiceImpl::UpdateCatalogFromNetwork(
@@ -127,6 +124,18 @@
   AddUpdatedCatalog(catalog_version, std::move(catalog), std::move(callback));
 }
 
+void ExploreSitesServiceImpl::ComposeSiteImage(BitmapCallback callback,
+                                               EncodedImageList images) {
+  image_helper_.ComposeSiteImage(std::move(callback), std::move(images));
+}
+
+void ExploreSitesServiceImpl::ComposeCategoryImage(BitmapCallback callback,
+                                                   int pixel_size,
+                                                   EncodedImageList images) {
+  image_helper_.ComposeCategoryImage(std::move(callback), pixel_size,
+                                     std::move(images));
+}
+
 void ExploreSitesServiceImpl::Shutdown() {}
 
 void ExploreSitesServiceImpl::OnTaskQueueIsIdle() {}
@@ -142,37 +151,4 @@
       explore_sites_store_.get(), version_token, std::move(catalog_proto),
       std::move(callback)));
 }
-
-// static
-void ExploreSitesServiceImpl::OnDecodeDone(BitmapCallback callback,
-                                           const SkBitmap& decoded_image) {
-  DVLOG(1) << "Decoded images, result "
-           << (decoded_image.isNull() ? "null" : "non-null");
-  std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>(decoded_image);
-  std::move(callback).Run(std::move(bitmap));
-}
-
-// static
-void ExploreSitesServiceImpl::DecodeImageBytes(BitmapCallback callback,
-                                               EncodedImageList images) {
-  // TODO(freedjm) Fix to handle multiple images when support is added for
-  // creating composite images for the NTP tiles.
-  DVLOG(1) << "Requested decoding for " << images.size() << " images";
-  if (images.size() == 0) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-
-  service_manager::mojom::ConnectorRequest connector_request;
-  std::unique_ptr<service_manager::Connector> connector =
-      service_manager::Connector::Create(&connector_request);
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindConnectorRequest(std::move(connector_request));
-
-  data_decoder::DecodeImage(connector.get(), *images[0],
-                            data_decoder::mojom::ImageCodec::DEFAULT, false,
-                            data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
-                            base::BindOnce(&OnDecodeDone, std::move(callback)));
-}
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/explore_sites_service_impl.h b/chrome/browser/android/explore_sites/explore_sites_service_impl.h
index 46eeaa4..dcfcfd5 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service_impl.h
+++ b/chrome/browser/android/explore_sites/explore_sites_service_impl.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/android/explore_sites/explore_sites_store.h"
 #include "chrome/browser/android/explore_sites/explore_sites_types.h"
 #include "chrome/browser/android/explore_sites/history_statistics_reporter.h"
+#include "chrome/browser/android/explore_sites/image_helper.h"
 #include "components/offline_pages/task/task_queue.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -34,8 +35,15 @@
 
   // ExploreSitesService implementation.
   void GetCatalog(CatalogCallback callback) override;
-  void GetCategoryImage(int category_id, BitmapCallback callback) override;
+  void GetCategoryImage(int category_id,
+                        int pixel_size,
+                        BitmapCallback callback) override;
+
+  // Compose a single site icon and return via |callback|.
   void GetSiteImage(int site_id, BitmapCallback callback) override;
+
+  // Compose a category icon containing [1 - 4] site icons and return via
+  // |callback|.
   void UpdateCatalogFromNetwork(bool is_immediate_fetch,
                                 const std::string& accept_languages,
                                 BooleanCallback callback) override;
@@ -51,22 +59,25 @@
                          std::unique_ptr<Catalog> catalog_proto,
                          BooleanCallback callback);
 
-  static void OnDecodeDone(BitmapCallback callback,
-                           const SkBitmap& decoded_image);
-  static void DecodeImageBytes(BitmapCallback callback,
-                               EncodedImageList images);
-
   // Callback returning from the UpdateCatalogFromNetwork operation.  It
   // passes along the call back to the bridge and eventually back to Java land.
   void OnCatalogFetched(BooleanCallback callback,
                         ExploreSitesRequestStatus status,
                         std::unique_ptr<std::string> serialized_protobuf);
 
+  // Wrappers to call ImageHelper::Compose[Site|Category]Image.
+  void ComposeSiteImage(BitmapCallback callback, EncodedImageList images);
+  void ComposeCategoryImage(BitmapCallback callback,
+                            int pixel_size,
+                            EncodedImageList images);
+
   // True when Chrome starts up, this is reset after the catalog is requested
   // the first time in Chrome. This prevents the ESP from changing out from
   // under a viewer.
   bool check_for_new_catalog_ = true;
 
+  ImageHelper image_helper_;
+
   // Used to control access to the ExploreSitesStore.
   TaskQueue task_queue_;
   std::unique_ptr<ExploreSitesStore> explore_sites_store_;
diff --git a/chrome/browser/android/explore_sites/explore_sites_types.h b/chrome/browser/android/explore_sites/explore_sites_types.h
index 185906c..4a16c34 100644
--- a/chrome/browser/android/explore_sites/explore_sites_types.h
+++ b/chrome/browser/android/explore_sites/explore_sites_types.h
@@ -62,6 +62,7 @@
 using EncodedImageBytes = std::vector<uint8_t>;
 using EncodedImageList = std::vector<std::unique_ptr<EncodedImageBytes>>;
 using EncodedImageListCallback = base::OnceCallback<void(EncodedImageList)>;
+using ImageJobFinishedCallback = base::OnceCallback<void(void)>;
 
 using BitmapCallback = base::OnceCallback<void(std::unique_ptr<SkBitmap>)>;
 
diff --git a/chrome/browser/android/explore_sites/image_helper.cc b/chrome/browser/android/explore_sites/image_helper.cc
new file mode 100644
index 0000000..5f88a0bf
--- /dev/null
+++ b/chrome/browser/android/explore_sites/image_helper.cc
@@ -0,0 +1,324 @@
+// 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/android/explore_sites/image_helper.h"
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/android/explore_sites/explore_sites_types.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/data_decoder/public/cpp/decode_image.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace explore_sites {
+namespace {
+const int kFaviconsPerCategoryImage = 4;
+
+// Ratio of icon size to the amount of padding between the icons.
+const int kIconPaddingScale = 8;
+}  // namespace
+
+// Class Job is used to manage multiple calls to the ImageHelper. Each request
+// to the ImageHelper is handled by a single Job, which is then destroyed after
+// it is finished.
+class ImageHelper::Job {
+ public:
+  // WARNING: When ImageJobFinishedCallback is called, |this| may be deleted.
+  // So nothing can be called after this callback.
+  Job(ImageJobType job_type,
+      ImageJobFinishedCallback job_finished_callback,
+      BitmapCallback bitmap_callback,
+      EncodedImageList images,
+      int pixel_size,
+      std::unique_ptr<service_manager::Connector> connector);
+  ~Job();
+
+  // Start begins the work that a Job performs (decoding and composition).
+  void Start();
+
+  void DecodeImageBytes(std::unique_ptr<EncodedImageBytes> image_bytes);
+  void OnDecodeSiteImageDone(const SkBitmap& decoded_image);
+  void OnDecodeCategoryImageDone(const SkBitmap& decoded_image);
+  std::unique_ptr<SkBitmap> CombineImages();
+
+ private:
+  // Used to inject connector in tests.
+  void SetupConnector();
+
+  const ImageJobType job_type_;
+  ImageJobFinishedCallback job_finished_callback_;
+  BitmapCallback bitmap_callback_;
+
+  EncodedImageList images_;
+  int num_icons_, pixel_size_;
+  std::vector<SkBitmap> bitmaps_;
+
+  std::unique_ptr<service_manager::Connector> connector_;
+
+  base::WeakPtrFactory<Job> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+ImageHelper::Job::Job(ImageJobType job_type,
+                      ImageJobFinishedCallback job_finished_callback,
+                      BitmapCallback bitmap_callback,
+                      EncodedImageList images,
+                      int pixel_size,
+                      std::unique_ptr<service_manager::Connector> connector)
+    : job_type_(job_type),
+      job_finished_callback_(std::move(job_finished_callback)),
+      bitmap_callback_(std::move(bitmap_callback)),
+      images_(std::move(images)),
+      pixel_size_(pixel_size),
+      connector_(std::move(connector)),
+      weak_ptr_factory_(this) {
+  num_icons_ = (images_.size() < kFaviconsPerCategoryImage)
+                   ? images_.size()
+                   : kFaviconsPerCategoryImage;
+}
+
+ImageHelper::Job::~Job() {}
+
+void ImageHelper::Job::Start() {
+  for (int i = 0; i < num_icons_; i++) {
+    // TODO(freedjm): preserve order of images.
+    DVLOG(1) << "Decoding image " << i + 1 << " of " << images_.size();
+    DecodeImageBytes(std::move(images_[i]));
+  }
+}
+
+void ImageHelper::Job::SetupConnector() {
+  service_manager::mojom::ConnectorRequest connector_request;
+  connector_ = service_manager::Connector::Create(&connector_request);
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindConnectorRequest(std::move(connector_request));
+}
+
+void ImageHelper::Job::DecodeImageBytes(
+    std::unique_ptr<EncodedImageBytes> image_bytes) {
+  if (!connector_) {
+    SetupConnector();
+  }
+
+  data_decoder::mojom::ImageDecoder::DecodeImageCallback callback;
+  if (job_type_ == ImageJobType::kSiteIcon) {
+    callback = base::BindOnce(&ImageHelper::Job::OnDecodeSiteImageDone,
+                              weak_ptr_factory_.GetWeakPtr());
+  } else {
+    callback = base::BindOnce(&ImageHelper::Job::OnDecodeCategoryImageDone,
+                              weak_ptr_factory_.GetWeakPtr());
+  }
+
+  data_decoder::DecodeImage(connector_.get(), *image_bytes,
+                            data_decoder::mojom::ImageCodec::DEFAULT, false,
+                            data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
+                            std::move(callback));
+}
+
+void ImageHelper::Job::OnDecodeSiteImageDone(const SkBitmap& decoded_image) {
+  DVLOG(1) << "Decoded site image, result "
+           << (decoded_image.isNull() ? "null" : "non-null");
+
+  if (decoded_image.isNull()) {
+    std::move(bitmap_callback_).Run(nullptr);
+  } else {
+    std::move(bitmap_callback_).Run(std::make_unique<SkBitmap>(decoded_image));
+  }
+  std::move(job_finished_callback_).Run();
+}
+
+void ImageHelper::Job::OnDecodeCategoryImageDone(
+    const SkBitmap& decoded_image) {
+  DVLOG(1) << "Decoded image for category, result "
+           << (decoded_image.isNull() ? "null" : "non-null");
+
+  if (decoded_image.isNull()) {
+    num_icons_--;
+  } else {
+    bitmaps_.push_back(decoded_image);
+  }
+
+  if ((int)bitmaps_.size() == num_icons_) {  // On last image for category.
+    std::unique_ptr<SkBitmap> category_bitmap = CombineImages();
+    std::move(bitmap_callback_).Run(std::move(category_bitmap));
+    std::move(job_finished_callback_).Run();
+  }
+}
+
+SkBitmap ScaleBitmap(int icon_size, SkBitmap* bitmap) {
+  DCHECK(bitmap);
+  SkBitmap temp_bitmap;
+  SkImageInfo scaledIconInfo = bitmap->info().makeWH(icon_size, icon_size);
+  temp_bitmap.setInfo(scaledIconInfo);
+  temp_bitmap.allocPixels();
+  bool did_scale =
+      bitmap->pixmap().scalePixels(temp_bitmap.pixmap(), kHigh_SkFilterQuality);
+  if (!did_scale) {
+    DLOG(ERROR) << "Unable to scale icon for category image.";
+    return SkBitmap();
+  }
+  return temp_bitmap;
+}
+
+std::unique_ptr<SkBitmap> ImageHelper::Job::CombineImages() {
+  DVLOG(1) << "num icons: " << num_icons_;
+  if (num_icons_ == 0) {
+    return nullptr;
+  }
+
+  DVLOG(1) << "pixel_size_: " << pixel_size_;
+  int icon_padding_pixel_size = pixel_size_ / kIconPaddingScale;
+  int size = pixel_size_ + icon_padding_pixel_size;
+  DVLOG(1) << "size: " << size;
+
+  SkBitmap composite_bitmap;
+  SkImageInfo image_info = bitmaps_[0].info().makeWH(size, size);
+  composite_bitmap.setInfo(image_info);
+  composite_bitmap.allocPixels();
+  composite_bitmap.eraseColor(gfx::kGoogleGrey100);  // Set background to grey.
+
+  int icon_size = pixel_size_ / 2;
+
+  // draw icons in correct areas
+  switch (num_icons_) {
+    case 1: {
+      // Centered.
+      SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[0]);
+      if (scaledBitmap.empty()) {
+        return nullptr;
+      }
+      composite_bitmap.writePixels(scaledBitmap.pixmap(),
+                                   (icon_size + icon_padding_pixel_size) / 2,
+                                   (icon_size + icon_padding_pixel_size) / 2);
+      break;
+    }
+    case 2: {
+      // Side by side.
+      for (int i = 0; i < 2; i++) {
+        SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]);
+        if (scaledBitmap.empty()) {
+          return nullptr;
+        }
+        composite_bitmap.writePixels(scaledBitmap.pixmap(),
+                                     i * (icon_size + icon_padding_pixel_size),
+                                     (icon_size + icon_padding_pixel_size) / 2);
+      }
+      break;
+    }
+    case 3: {
+      // Two on top, one on bottom.
+      for (int i = 0; i < 3; i++) {
+        SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]);
+        if (scaledBitmap.empty()) {
+          return nullptr;
+        }
+        switch (i) {
+          case 0:
+            composite_bitmap.writePixels(scaledBitmap.pixmap(), 0, 0);
+            break;
+          case 1:
+            composite_bitmap.writePixels(scaledBitmap.pixmap(),
+                                         (icon_size + icon_padding_pixel_size),
+                                         0);
+            break;
+          default:
+            composite_bitmap.writePixels(
+                scaledBitmap.pixmap(),
+                (icon_size + icon_padding_pixel_size) / 2,
+                (icon_size + icon_padding_pixel_size));
+            break;
+        }
+      }
+      break;
+    }
+    case 4: {
+      // One in each corner.
+      for (int i = 0; i < 2; i++) {
+        for (int j = 0; j < 2; j++) {
+          int index = i + 2 * j;
+          SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[index]);
+          if (scaledBitmap.empty()) {
+            return nullptr;
+          }
+          composite_bitmap.writePixels(
+              scaledBitmap.pixmap(), j * (icon_size + icon_padding_pixel_size),
+              i * (icon_size + icon_padding_pixel_size));
+        }
+      }
+      break;
+    }
+    default:
+      DLOG(ERROR) << "Invalid number of icons to combine: " << bitmaps_.size();
+      return nullptr;
+  }
+
+  return std::make_unique<SkBitmap>(composite_bitmap);
+}
+
+ImageHelper::ImageHelper() : last_used_job_id_(0), weak_factory_(this) {}
+
+ImageHelper::~ImageHelper() {}
+
+void ImageHelper::NewJob(
+    ImageJobType job_type,
+    ImageJobFinishedCallback job_finished_callback,
+    BitmapCallback bitmap_callback,
+    EncodedImageList images,
+    int pixel_size,
+    std::unique_ptr<service_manager::Connector> connector) {
+  auto job = std::make_unique<Job>(
+      job_type, std::move(job_finished_callback), std::move(bitmap_callback),
+      std::move(images), pixel_size, std::move(connector));
+  id_to_job_[last_used_job_id_] = std::move(job);
+  id_to_job_[last_used_job_id_]->Start();
+}
+
+void ImageHelper::OnJobFinished(int job_id) {
+  DVLOG(1) << "Erasing job " << job_id;
+  id_to_job_.erase(job_id);
+}
+
+void ImageHelper::ComposeSiteImage(
+    BitmapCallback callback,
+    EncodedImageList images,
+    std::unique_ptr<service_manager::Connector> connector) {
+  DVLOG(1) << "Requested decoding for site image";
+  if (images.size() == 0) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  NewJob(ImageJobType::kSiteIcon,
+         base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(),
+                        ++last_used_job_id_),
+         std::move(callback), std::move(images), -1, std::move(connector));
+}
+
+void ImageHelper::ComposeCategoryImage(
+    BitmapCallback callback,
+    int pixel_size,
+    EncodedImageList images,
+    std::unique_ptr<service_manager::Connector> connector) {
+  DVLOG(1) << "Requested decoding " << images.size()
+           << " images for category image";
+
+  if (images.size() == 0) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  NewJob(ImageJobType::kCategoryImage,
+         base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(),
+                        ++last_used_job_id_),
+         std::move(callback), std::move(images), pixel_size,
+         std::move(connector));
+}
+}  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/image_helper.h b/chrome/browser/android/explore_sites/image_helper.h
new file mode 100644
index 0000000..c410c855
--- /dev/null
+++ b/chrome/browser/android/explore_sites/image_helper.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ANDROID_EXPLORE_SITES_IMAGE_HELPER_H_
+#define CHROME_BROWSER_ANDROID_EXPLORE_SITES_IMAGE_HELPER_H_
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/android/explore_sites/explore_sites_types.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace explore_sites {
+
+enum class ImageJobType { kSiteIcon, kCategoryImage };
+
+// A helper for converting webp images to bitmaps for the frontend.
+//
+// The ComposeSiteImage() and ComposeCategoryImage() functions are used for
+// gettting the icons for the ESP and the NTP, respectively.
+class ImageHelper {
+ public:
+  ImageHelper();
+  virtual ~ImageHelper();
+
+  // Compose a single site icon and return via |callback|.
+  void ComposeSiteImage(
+      BitmapCallback callback,
+      EncodedImageList images,
+      std::unique_ptr<service_manager::Connector> connector = nullptr);
+  // Compose a category icon containing [1 - 4] site icons and return via
+  // |callback|.
+  void ComposeCategoryImage(
+      BitmapCallback callback,
+      int pixel_size,
+      EncodedImageList images,
+      std::unique_ptr<service_manager::Connector> connector = nullptr);
+
+ private:
+  class Job;
+
+  void NewJob(ImageJobType job_type,
+              ImageJobFinishedCallback job_finished_callback,
+              BitmapCallback bitmap_callback,
+              EncodedImageList images,
+              int pixel_size,
+              std::unique_ptr<service_manager::Connector> connector);
+
+  void OnJobFinished(int job_id);
+
+  std::map<int, std::unique_ptr<Job>> id_to_job_;
+  int last_used_job_id_;
+
+  base::WeakPtrFactory<ImageHelper> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageHelper);
+};
+
+}  // namespace explore_sites
+
+#endif  // CHROME_BROWSER_ANDROID_EXPLORE_SITES_IMAGE_HELPER_H_
diff --git a/chrome/browser/android/explore_sites/image_helper_unittest.cc b/chrome/browser/android/explore_sites/image_helper_unittest.cc
new file mode 100644
index 0000000..1879177
--- /dev/null
+++ b/chrome/browser/android/explore_sites/image_helper_unittest.cc
@@ -0,0 +1,277 @@
+// 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/android/explore_sites/image_helper.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "ui/gfx/color_palette.h"
+
+namespace explore_sites {
+
+const std::vector<unsigned char> kWebpBytes{
+    0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
+    0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00,
+    0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
+    0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00,
+};
+
+std::vector<unsigned char> kInvalidWebpBytes{
+    0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42,
+    0x50, 0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50,
+};
+
+const int kIconSize = 48;
+const int kPaddingScale = 8;
+const int kCompositeSize = kIconSize + (kIconSize / kPaddingScale);
+
+// Bounds for icon size of 48.
+const int kLowerBoundCenter = 14;
+const int kUpperBoundCenter = 39;
+const int kLowerBoundCorner = 24;
+const int kUpperBoundCorner = 29;
+
+class ExploreSitesImageHelperTest : public testing::Test {
+ public:
+  ExploreSitesImageHelperTest() {
+    std::unique_ptr<service_manager::Service> service =
+        data_decoder::DataDecoderService::Create();
+    connector_factory_ =
+        service_manager::TestConnectorFactory::CreateForUniqueService(
+            std::move(service));
+  }
+
+  ~ExploreSitesImageHelperTest() override{};
+
+  EncodedImageList GetEncodedImageList(int num_icons);
+
+  BitmapCallback StoreBitmap() {
+    return base::BindLambdaForTesting([&](std::unique_ptr<SkBitmap> bitmap) {
+      last_bitmap_list.push_back(std::move(bitmap));
+    });
+  }
+
+  std::vector<std::unique_ptr<SkBitmap>> last_bitmap_list;
+
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  std::unique_ptr<service_manager::Connector> GetConnector() {
+    return connector_factory_->CreateConnector();
+  }
+
+  std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
+};
+
+// 1x1 webp image with color value of 0.
+EncodedImageList ExploreSitesImageHelperTest::GetEncodedImageList(
+    int num_icons) {
+  EncodedImageList image_list;
+  for (int i = 0; i < num_icons; i++) {
+    image_list.push_back(std::make_unique<EncodedImageBytes>(kWebpBytes));
+  }
+  return image_list;
+}
+
+// Test that a single call to get a site icon works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_SiteIcon) {
+  ImageHelper image_helper;
+
+  image_helper.ComposeSiteImage(StoreBitmap(), GetEncodedImageList(1),
+                                GetConnector());
+
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  EXPECT_EQ(last_bitmap_list[0]->width(), 1);
+  EXPECT_EQ(last_bitmap_list[0]->height(), 1);
+  EXPECT_EQ(last_bitmap_list[0]->getColor(0, 0), (unsigned)0);
+}
+
+// Test that two sequential calls to get site icons works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_SiteIcon_MultipleCalls) {
+  ImageHelper image_helper;
+  image_helper.ComposeSiteImage(StoreBitmap(), GetEncodedImageList(1),
+                                GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+
+  image_helper.ComposeSiteImage(StoreBitmap(), GetEncodedImageList(1),
+                                GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[1]);
+  EXPECT_FALSE(last_bitmap_list[1]->isNull());
+}
+
+// Test that two concurrent calls to get site icons works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_SiteIcon_ConcurrentCalls) {
+  ImageHelper image_helper;
+  image_helper.ComposeSiteImage(StoreBitmap(), GetEncodedImageList(1),
+                                GetConnector());
+  image_helper.ComposeSiteImage(StoreBitmap(), GetEncodedImageList(1),
+                                GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  ASSERT_NE(nullptr, last_bitmap_list[1]);
+  EXPECT_FALSE(last_bitmap_list[1]->isNull());
+}
+
+// Test that call to get category image with one site icon works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_CategoryImage_One) {
+  ImageHelper image_helper;
+  image_helper.ComposeCategoryImage(StoreBitmap(), kIconSize,
+                                    GetEncodedImageList(1), GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  EXPECT_EQ(last_bitmap_list[0]->width(), kCompositeSize);
+  EXPECT_EQ(last_bitmap_list[0]->height(), kCompositeSize);
+
+  // One square in the center. If inside the bounds, the color should be 0.
+  // If outside of the bounds the color should be kGoogleGrey100.
+  for (int i = 0; i < last_bitmap_list[0]->width(); i++) {
+    for (int j = 0; j < last_bitmap_list[0]->height(); j++) {
+      if (j > kLowerBoundCenter && j < kUpperBoundCenter &&
+          i > kLowerBoundCenter &&
+          i < kUpperBoundCenter) {  // centered square is color 0
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), (unsigned)0);
+      } else {  // rest of bitmap is grey
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), gfx::kGoogleGrey100);
+      }
+    }
+  }
+}
+
+// Test that call to get category image with two site icons works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_CategoryImage_Two) {
+  ImageHelper image_helper;
+  image_helper.ComposeCategoryImage(StoreBitmap(), kIconSize,
+                                    GetEncodedImageList(2), GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  EXPECT_EQ(last_bitmap_list[0]->width(), kCompositeSize);
+  EXPECT_EQ(last_bitmap_list[0]->height(), kCompositeSize);
+
+  // Two squares, side by side. If inside the bounds, the color should be 0.
+  // If outside of the bounds the color should be kGoogleGrey100.
+  for (int i = 0; i < last_bitmap_list[0]->width(); i++) {
+    for (int j = 0; j < last_bitmap_list[0]->height(); j++) {
+      if ((j < kLowerBoundCorner || j > kUpperBoundCorner) &&
+          i > kLowerBoundCenter && i < kUpperBoundCenter) {
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), (unsigned)0);
+      } else {  // rest of bitmap is grey
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), gfx::kGoogleGrey100);
+      }
+    }
+  }
+}
+
+// Test that call to get category image with three site icons works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_CategoryImage_Three) {
+  ImageHelper image_helper;
+  image_helper.ComposeCategoryImage(StoreBitmap(), kIconSize,
+                                    GetEncodedImageList(3), GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  EXPECT_EQ(last_bitmap_list[0]->width(), kCompositeSize);
+  EXPECT_EQ(last_bitmap_list[0]->height(), kCompositeSize);
+
+  // Three squares, two on top and one on bottom. If inside the bounds, the
+  // color should be 0. If outside of the bounds the color should be
+  // kGoogleGrey100.
+  for (int i = 0; i < last_bitmap_list[0]->width(); i++) {
+    for (int j = 0; j < last_bitmap_list[0]->height(); j++) {
+      if ((i < kLowerBoundCorner && j < kLowerBoundCorner) ||  // top left
+          (i < kLowerBoundCorner && j > kUpperBoundCorner) ||  // top right
+          (i > kUpperBoundCorner && j > kLowerBoundCenter &&
+           j < kUpperBoundCenter)) {  // bottom
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), (unsigned)0);
+      } else {  // rest of bitmap is grey
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), gfx::kGoogleGrey100);
+      }
+    }
+  }
+}
+
+// Test that call to get category image with four site icons works.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_CategoryImage_Four) {
+  ImageHelper image_helper;
+  image_helper.ComposeCategoryImage(StoreBitmap(), kIconSize,
+                                    GetEncodedImageList(4), GetConnector());
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_NE(nullptr, last_bitmap_list[0]);
+  EXPECT_FALSE(last_bitmap_list[0]->isNull());
+  EXPECT_EQ(last_bitmap_list[0]->width(), kCompositeSize);
+  EXPECT_EQ(last_bitmap_list[0]->height(), kCompositeSize);
+
+  // Four squares in each corner. If inside the bounds, the color should be 0.
+  // If outside of the bounds the color should be kGoogleGrey100.
+  for (int i = 0; i < last_bitmap_list[0]->width(); i++) {
+    for (int j = 0; j < last_bitmap_list[0]->height(); j++) {
+      if ((i < kLowerBoundCorner && j < kLowerBoundCorner) ||  // top left
+          (i < kLowerBoundCorner && j > kUpperBoundCorner) ||  // top right
+          (i > kUpperBoundCorner && j < kLowerBoundCorner) ||  // bottom left
+          (i > kUpperBoundCorner && j > kUpperBoundCorner)) {  // bottom right
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), (unsigned)0);
+      } else {  // rest of bitmap is grey
+        EXPECT_EQ(last_bitmap_list[0]->getColor(j, i), gfx::kGoogleGrey100);
+      }
+    }
+  }
+}
+
+// Test that invalid webp in the image list is ok.
+TEST_F(ExploreSitesImageHelperTest, TestImageHelper_CategoryImage_InvalidWebP) {
+  ImageHelper image_helper;
+
+  // Try different combinations of invalid and valid webp lists.
+  // Trial cases are:
+  // [invalid] --> nullptr result
+  // [invalid, valid] --> good result
+  for (int i = 0; i < 2; i++) {
+    EncodedImageList image_list;
+    image_list.push_back(
+        std::make_unique<EncodedImageBytes>(kInvalidWebpBytes));
+    if (i == 1) {
+      image_list.push_back(std::make_unique<EncodedImageBytes>(kWebpBytes));
+    }
+    image_helper.ComposeCategoryImage(StoreBitmap(), kIconSize,
+                                      std::move(image_list), GetConnector());
+
+    scoped_task_environment_.RunUntilIdle();
+
+    if (i == 0) {
+      ASSERT_EQ(nullptr, last_bitmap_list[i]);
+    } else {
+      ASSERT_NE(nullptr, last_bitmap_list[i]);
+      EXPECT_FALSE(last_bitmap_list[i]->isNull());
+    }
+  }
+}
+
+}  // namespace explore_sites
diff --git a/chrome/browser/android/vr/arcore_device/arcore.h b/chrome/browser/android/vr/arcore_device/arcore.h
index c9ff25a..dcd6896f 100644
--- a/chrome/browser/android/vr/arcore_device/arcore.h
+++ b/chrome/browser/android/vr/arcore_device/arcore.h
@@ -15,11 +15,11 @@
 
 namespace device {
 
-// This allows a real or fake implementation of ARCore to
+// This allows a real or fake implementation of ArCore to
 // be used as appropriate (i.e. for testing).
-class ARCore {
+class ArCore {
  public:
-  virtual ~ARCore() = default;
+  virtual ~ArCore() = default;
 
   // Initializes the runtime and returns whether it was successful.
   // If successful, the runtime must be paused when this method returns.
@@ -34,7 +34,7 @@
       const base::span<const float> uvs) = 0;
   virtual gfx::Transform GetProjectionMatrix(float near, float far) = 0;
 
-  // Update ARCore state. This call blocks for up to 1/30s while waiting for a
+  // Update ArCore state. This call blocks for up to 1/30s while waiting for a
   // new camera image. The output parameter |camera_updated| must be non-null,
   // the stored value indicates if the camera image was updated successfully.
   // The returned pose is nullptr if tracking was lost, this can happen even
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.cc b/chrome/browser/android/vr/arcore_device/arcore_device.cc
index a7fbbc56..200cf4a4 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.cc
@@ -67,7 +67,7 @@
 
 }  // namespace
 
-ARCoreDevice::ARCoreDevice()
+ArCoreDevice::ArCoreDevice()
     : VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       mailbox_bridge_(std::make_unique<vr::MailboxToSurfaceBridge>()),
@@ -78,15 +78,14 @@
 
   // TODO(https://crbug.com/836524) clean up usage of mailbox bridge
   // and extract the methods in this class that interact with ARCore API
-  // into a separate class that implements the ARCore interface.
+  // into a separate class that implements the ArCore interface.
   mailbox_bridge_->CreateUnboundContextProvider(
-      base::BindOnce(&ARCoreDevice::OnMailboxBridgeReady, GetWeakPtr()));
+      base::BindOnce(&ArCoreDevice::OnMailboxBridgeReady, GetWeakPtr()));
 }
 
-ARCoreDevice::~ARCoreDevice() {
-}
+ArCoreDevice::~ArCoreDevice() {}
 
-void ARCoreDevice::PauseTracking() {
+void ArCoreDevice::PauseTracking() {
   DCHECK(IsOnMainThread());
 
   if (is_paused_)
@@ -98,10 +97,10 @@
     return;
 
   PostTaskToGlThread(base::BindOnce(
-      &ARCoreGl::Pause, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr()));
+      &ArCoreGl::Pause, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
 }
 
-void ARCoreDevice::ResumeTracking() {
+void ArCoreDevice::ResumeTracking() {
   DCHECK(IsOnMainThread());
 
   if (!is_paused_)
@@ -120,23 +119,23 @@
     return;
 
   PostTaskToGlThread(base::BindOnce(
-      &ARCoreGl::Resume, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr()));
+      &ArCoreGl::Resume, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
 }
 
-void ARCoreDevice::OnMailboxBridgeReady() {
+void ArCoreDevice::OnMailboxBridgeReady() {
   DCHECK(IsOnMainThread());
   DCHECK(!arcore_gl_thread_);
   // MailboxToSurfaceBridge's destructor's call to DestroyContext must
   // happen on the GL thread, so transferring it to that thread is appropriate.
   // TODO(https://crbug.com/836553): use same GL thread as GVR.
-  arcore_gl_thread_ = std::make_unique<ARCoreGlThread>(
+  arcore_gl_thread_ = std::make_unique<ArCoreGlThread>(
       std::move(mailbox_bridge_),
       CreateMainThreadCallback(base::BindOnce(
-          &ARCoreDevice::OnARCoreGlThreadInitialized, GetWeakPtr())));
+          &ArCoreDevice::OnArCoreGlThreadInitialized, GetWeakPtr())));
   arcore_gl_thread_->Start();
 }
 
-void ARCoreDevice::OnARCoreGlThreadInitialized() {
+void ArCoreDevice::OnArCoreGlThreadInitialized() {
   DCHECK(IsOnMainThread());
 
   is_arcore_gl_thread_initialized_ = true;
@@ -146,7 +145,7 @@
   }
 }
 
-void ARCoreDevice::RequestSession(
+void ArCoreDevice::RequestSession(
     mojom::XRRuntimeSessionOptionsPtr options,
     mojom::XRRuntime::RequestSessionCallback callback) {
   DCHECK(IsOnMainThread());
@@ -163,7 +162,7 @@
   // initialization at once on the first successful RequestSession call.
   if (!is_arcore_gl_thread_initialized_) {
     pending_request_ar_module_callback_ =
-        base::BindOnce(&ARCoreDevice::RequestArModule, GetWeakPtr(),
+        base::BindOnce(&ArCoreDevice::RequestArModule, GetWeakPtr(),
                        options->render_process_id, options->render_frame_id,
                        options->has_user_activation);
     return;
@@ -173,12 +172,12 @@
                   options->has_user_activation);
 }
 
-void ARCoreDevice::RequestArModule(int render_process_id,
+void ArCoreDevice::RequestArModule(int render_process_id,
                                    int render_frame_id,
                                    bool has_user_activation) {
   if (arcore_java_utils_->ShouldRequestInstallArModule()) {
     on_request_ar_module_result_callback_ =
-        base::BindOnce(&ARCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
+        base::BindOnce(&ArCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
                        render_process_id, render_frame_id, has_user_activation);
     arcore_java_utils_->RequestInstallArModule();
     return;
@@ -188,7 +187,7 @@
                           has_user_activation, true);
 }
 
-void ARCoreDevice::OnRequestArModuleResult(int render_process_id,
+void ArCoreDevice::OnRequestArModuleResult(int render_process_id,
                                            int render_frame_id,
                                            bool has_user_activation,
                                            bool success) {
@@ -201,7 +200,7 @@
                                has_user_activation);
 }
 
-void ARCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
+void ArCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
                                                 int render_frame_id,
                                                 bool has_user_activation) {
   DCHECK(IsOnMainThread());
@@ -212,7 +211,7 @@
     // ARCore is not installed or requires an update. Store the callback to be
     // processed later once installation/update is complete or got cancelled.
     on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
-        &ARCoreDevice::OnRequestArCoreInstallOrUpdateResult, GetWeakPtr(),
+        &ArCoreDevice::OnRequestArCoreInstallOrUpdateResult, GetWeakPtr(),
         render_process_id, render_frame_id, has_user_activation);
 
     content::RenderFrameHost* render_frame_host =
@@ -238,7 +237,7 @@
                                        has_user_activation, true);
 }
 
-void ARCoreDevice::OnRequestInstallArModuleResult(bool success) {
+void ArCoreDevice::OnRequestInstallArModuleResult(bool success) {
   DCHECK(IsOnMainThread());
 
   if (on_request_ar_module_result_callback_) {
@@ -246,7 +245,7 @@
   }
 }
 
-void ARCoreDevice::OnRequestInstallSupportedARCoreCanceled() {
+void ArCoreDevice::OnRequestInstallSupportedArCoreCanceled() {
   DCHECK(IsOnMainThread());
   DCHECK(is_arcore_gl_thread_initialized_);
   DCHECK(on_request_arcore_install_or_update_result_callback_);
@@ -254,7 +253,7 @@
   std::move(on_request_arcore_install_or_update_result_callback_).Run(false);
 }
 
-void ARCoreDevice::CallDeferredRequestSessionCallbacks(bool success) {
+void ArCoreDevice::CallDeferredRequestSessionCallbacks(bool success) {
   DCHECK(IsOnMainThread());
   DCHECK(is_arcore_gl_thread_initialized_);
   DCHECK(!deferred_request_session_callbacks_.empty());
@@ -283,7 +282,7 @@
   deferred_request_session_callbacks_.clear();
 }
 
-void ARCoreDevice::OnRequestArCoreInstallOrUpdateResult(
+void ArCoreDevice::OnRequestArCoreInstallOrUpdateResult(
     int render_process_id,
     int render_frame_id,
     bool has_user_activation,
@@ -301,12 +300,11 @@
   // ARCore sessions require camera permission.
   RequestCameraPermission(
       render_process_id, render_frame_id, has_user_activation,
-      base::BindOnce(&ARCoreDevice::OnRequestCameraPermissionComplete,
+      base::BindOnce(&ArCoreDevice::OnRequestCameraPermissionComplete,
                      GetWeakPtr()));
 }
 
-void ARCoreDevice::OnRequestCameraPermissionComplete(
-    bool success) {
+void ArCoreDevice::OnRequestCameraPermissionComplete(bool success) {
   DCHECK(IsOnMainThread());
   DCHECK(is_arcore_gl_thread_initialized_);
 
@@ -319,11 +317,11 @@
   RequestArCoreGlInitialization();
 }
 
-bool ARCoreDevice::ShouldPauseTrackingWhenFrameDataRestricted() {
+bool ArCoreDevice::ShouldPauseTrackingWhenFrameDataRestricted() {
   return true;
 }
 
-void ARCoreDevice::OnMagicWindowFrameDataRequest(
+void ArCoreDevice::OnMagicWindowFrameDataRequest(
     mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsOnMainThread());
@@ -353,31 +351,31 @@
   }
 
   PostTaskToGlThread(base::BindOnce(
-      &ARCoreGl::ProduceFrame, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr(),
+      &ArCoreGl::ProduceFrame, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
       max_size, rotation, CreateMainThreadCallback(std::move(callback))));
 }
 
-void ARCoreDevice::RequestHitTest(
+void ArCoreDevice::RequestHitTest(
     mojom::XRRayPtr ray,
     mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) {
   DCHECK(IsOnMainThread());
 
   PostTaskToGlThread(base::BindOnce(
-      &ARCoreGl::RequestHitTest, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr(),
+      &ArCoreGl::RequestHitTest, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
       std::move(ray), CreateMainThreadCallback(std::move(callback))));
 }
 
-void ARCoreDevice::PostTaskToGlThread(base::OnceClosure task) {
+void ArCoreDevice::PostTaskToGlThread(base::OnceClosure task) {
   DCHECK(IsOnMainThread());
-  arcore_gl_thread_->GetARCoreGl()->GetGlThreadTaskRunner()->PostTask(
+  arcore_gl_thread_->GetArCoreGl()->GetGlThreadTaskRunner()->PostTask(
       FROM_HERE, std::move(task));
 }
 
-bool ARCoreDevice::IsOnMainThread() {
+bool ArCoreDevice::IsOnMainThread() {
   return main_thread_task_runner_->BelongsToCurrentThread();
 }
 
-void ARCoreDevice::RequestCameraPermission(
+void ArCoreDevice::RequestCameraPermission(
     int render_process_id,
     int render_frame_id,
     bool has_user_activation,
@@ -403,11 +401,11 @@
   permission_manager->RequestPermission(
       CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, rfh, web_contents->GetURL(),
       has_user_activation,
-      base::BindRepeating(&ARCoreDevice::OnRequestCameraPermissionResult,
+      base::BindRepeating(&ArCoreDevice::OnRequestCameraPermissionResult,
                           GetWeakPtr(), web_contents, base::Passed(&callback)));
 }
 
-void ARCoreDevice::OnRequestCameraPermissionResult(
+void ArCoreDevice::OnRequestCameraPermissionResult(
     content::WebContents* web_contents,
     base::OnceCallback<void(bool)> callback,
     ContentSetting content_setting) {
@@ -436,7 +434,7 @@
       // Show the Android camera permission info bar.
       PermissionUpdateInfoBarDelegate::Create(
           web_contents, content_settings_types,
-          base::BindOnce(&ARCoreDevice::OnRequestAndroidCameraPermissionResult,
+          base::BindOnce(&ArCoreDevice::OnRequestAndroidCameraPermissionResult,
                          GetWeakPtr(), base::Passed(&callback)));
       return;
     case ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR:
@@ -447,23 +445,22 @@
   NOTREACHED() << "Unknown show permission infobar state.";
 }
 
-void ARCoreDevice::RequestArCoreGlInitialization() {
+void ArCoreDevice::RequestArCoreGlInitialization() {
   DCHECK(IsOnMainThread());
   DCHECK(is_arcore_gl_thread_initialized_);
 
   if (!is_arcore_gl_initialized_) {
     PostTaskToGlThread(base::BindOnce(
-        &ARCoreGl::Initialize, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr(),
+        &ArCoreGl::Initialize, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
         CreateMainThreadCallback(base::BindOnce(
-            &ARCoreDevice::OnARCoreGlInitializationComplete, GetWeakPtr()))));
+            &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
     return;
   }
 
-  OnARCoreGlInitializationComplete(true);
+  OnArCoreGlInitializationComplete(true);
 }
 
-void ARCoreDevice::OnARCoreGlInitializationComplete(
-    bool success) {
+void ArCoreDevice::OnArCoreGlInitializationComplete(bool success) {
   DCHECK(IsOnMainThread());
   DCHECK(is_arcore_gl_thread_initialized_);
 
@@ -476,13 +473,13 @@
 
   if (!is_paused_) {
     PostTaskToGlThread(base::BindOnce(
-        &ARCoreGl::Resume, arcore_gl_thread_->GetARCoreGl()->GetWeakPtr()));
+        &ArCoreGl::Resume, arcore_gl_thread_->GetArCoreGl()->GetWeakPtr()));
   }
 
   CallDeferredRequestSessionCallbacks(/*success=*/true);
 }
 
-void ARCoreDevice::OnRequestAndroidCameraPermissionResult(
+void ArCoreDevice::OnRequestAndroidCameraPermissionResult(
     base::OnceCallback<void(bool)> callback,
     bool was_android_camera_permission_granted) {
   DCHECK(IsOnMainThread());
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.h b/chrome/browser/android/vr/arcore_device/arcore_device.h
index e87aa77..1da2bd63 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.h
@@ -28,12 +28,12 @@
 
 namespace device {
 
-class ARCoreGlThread;
+class ArCoreGlThread;
 
-class ARCoreDevice : public VRDeviceBase {
+class ArCoreDevice : public VRDeviceBase {
  public:
-  ARCoreDevice();
-  ~ARCoreDevice() override;
+  ArCoreDevice();
+  ~ArCoreDevice() override;
 
   // VRDeviceBase implementation.
   void PauseTracking() override;
@@ -42,12 +42,12 @@
       mojom::XRRuntimeSessionOptionsPtr options,
       mojom::XRRuntime::RequestSessionCallback callback) override;
 
-  base::WeakPtr<ARCoreDevice> GetWeakPtr() {
+  base::WeakPtr<ArCoreDevice> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
 
   void OnRequestInstallArModuleResult(bool success);
-  void OnRequestInstallSupportedARCoreCanceled();
+  void OnRequestInstallSupportedArCoreCanceled();
 
  private:
   // VRDeviceBase implementation
@@ -60,7 +60,7 @@
       override;
 
   void OnMailboxBridgeReady();
-  void OnARCoreGlThreadInitialized();
+  void OnArCoreGlThreadInitialized();
   void OnRequestCameraPermissionComplete(
       bool success);
 
@@ -76,7 +76,7 @@
   template <typename... Args>
   base::OnceCallback<void(Args...)> CreateMainThreadCallback(
       base::OnceCallback<void(Args...)> callback) {
-    return base::BindOnce(&ARCoreDevice::RunCallbackOnTaskRunner<Args...>,
+    return base::BindOnce(&ArCoreDevice::RunCallbackOnTaskRunner<Args...>,
                           main_thread_task_runner_, std::move(callback));
   }
 
@@ -110,12 +110,11 @@
       base::OnceCallback<void(bool)> callback,
       bool was_android_camera_permission_granted);
   void RequestArCoreGlInitialization();
-  void OnARCoreGlInitializationComplete(
-      bool success);
+  void OnArCoreGlInitializationComplete(bool success);
 
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
   std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
-  std::unique_ptr<ARCoreGlThread> arcore_gl_thread_;
+  std::unique_ptr<ArCoreGlThread> arcore_gl_thread_;
   std::unique_ptr<vr::ArCoreJavaUtils> arcore_java_utils_;
 
   bool is_arcore_gl_thread_initialized_ = false;
@@ -140,8 +139,8 @@
   base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
 
   // Must be last.
-  base::WeakPtrFactory<ARCoreDevice> weak_ptr_factory_;
-  DISALLOW_COPY_AND_ASSIGN(ARCoreDevice);
+  base::WeakPtrFactory<ArCoreDevice> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDevice);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc b/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
index 51a4e428..d816ce3 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_provider.cc
@@ -8,17 +8,17 @@
 
 namespace device {
 
-ARCoreDeviceProvider::ARCoreDeviceProvider() = default;
+ArCoreDeviceProvider::ArCoreDeviceProvider() = default;
 
-ARCoreDeviceProvider::~ARCoreDeviceProvider() = default;
+ArCoreDeviceProvider::~ArCoreDeviceProvider() = default;
 
-void ARCoreDeviceProvider::Initialize(
+void ArCoreDeviceProvider::Initialize(
     base::RepeatingCallback<void(mojom::XRDeviceId,
                                  mojom::VRDisplayInfoPtr,
                                  mojom::XRRuntimePtr)> add_device_callback,
     base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
     base::OnceClosure initialization_complete) {
-  arcore_device_ = base::WrapUnique(new ARCoreDevice());
+  arcore_device_ = base::WrapUnique(new ArCoreDevice());
   add_device_callback.Run(arcore_device_->GetId(),
                           arcore_device_->GetVRDisplayInfo(),
                           arcore_device_->BindXRRuntimePtr());
@@ -26,7 +26,7 @@
   std::move(initialization_complete).Run();
 }
 
-bool ARCoreDeviceProvider::Initialized() {
+bool ArCoreDeviceProvider::Initialized() {
   return initialized_;
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_provider.h b/chrome/browser/android/vr/arcore_device/arcore_device_provider.h
index 47e1f00..6e5ea79 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_provider.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_provider.h
@@ -12,12 +12,12 @@
 
 namespace device {
 
-class ARCoreDevice;
+class ArCoreDevice;
 
-class ARCoreDeviceProvider : public VRDeviceProvider {
+class ArCoreDeviceProvider : public VRDeviceProvider {
  public:
-  ARCoreDeviceProvider();
-  ~ARCoreDeviceProvider() override;
+  ArCoreDeviceProvider();
+  ~ArCoreDeviceProvider() override;
   void Initialize(
       base::RepeatingCallback<void(mojom::XRDeviceId,
                                    mojom::VRDisplayInfoPtr,
@@ -27,9 +27,9 @@
   bool Initialized() override;
 
  private:
-  std::unique_ptr<ARCoreDevice> arcore_device_;
+  std::unique_ptr<ArCoreDevice> arcore_device_;
   bool initialized_ = false;
-  DISALLOW_COPY_AND_ASSIGN(ARCoreDeviceProvider);
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProvider);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index 8ddcad9..9c3715ae 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -35,7 +35,7 @@
 #include "ui/gl/init/gl_factory.h"
 
 namespace {
-// Input display coordinates (range 0..1) used with ARCore's
+// Input display coordinates (range 0..1) used with ArCore's
 // transformDisplayUvCoords to calculate the output matrix.
 constexpr std::array<float, 6> kDisplayCoordinatesForTransform = {
     0.f, 0.f, 1.f, 0.f, 0.f, 1.f};
@@ -45,14 +45,14 @@
   // screen-filling quad, origin at bottom left, u=1 at right, v=1 at top) to
   // camera texture UV coordinates. This matrix is used to compute texture
   // coordinates for copying an appropriately cropped and rotated subsection of
-  // the camera image. The SampleData is a bit unfortunate. ARCore doesn't
+  // the camera image. The SampleData is a bit unfortunate. ArCore doesn't
   // provide a way to get a matrix directly. There's a function to transform UV
   // vectors individually, which obviously can't be used from a shader, so we
   // run that on selected vectors and recreate the matrix from the result.
 
   // Assumes that |uvs| is the result of transforming the display coordinates
   // from kDisplayCoordinatesForTransform. This combines the solved matrix with
-  // a Y flip because ARCore's "normalized screen space" coordinates have the
+  // a Y flip because ArCore's "normalized screen space" coordinates have the
   // origin at the top left to match 2D Android APIs, so it needs a Y flip to
   // get an origin at bottom left as used for textures.
   DCHECK_EQ(uvs.size(), 6U);
@@ -78,26 +78,26 @@
 
 namespace device {
 
-struct ARCoreHitTestRequest {
-  ARCoreHitTestRequest() = default;
-  ~ARCoreHitTestRequest() = default;
+struct ArCoreHitTestRequest {
+  ArCoreHitTestRequest() = default;
+  ~ArCoreHitTestRequest() = default;
   mojom::XRRayPtr ray;
   mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ARCoreHitTestRequest);
+  DISALLOW_COPY_AND_ASSIGN(ArCoreHitTestRequest);
 };
 
-ARCoreGl::ARCoreGl(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge)
+ArCoreGl::ArCoreGl(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge)
     : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
-      arcore_(std::make_unique<ARCoreImpl>()),
+      arcore_(std::make_unique<ArCoreImpl>()),
       ar_image_transport_(
           std::make_unique<ARImageTransport>(std::move(mailbox_bridge))),
       weak_ptr_factory_(this) {}
 
-ARCoreGl::~ARCoreGl() {}
+ArCoreGl::~ArCoreGl() {}
 
-void ARCoreGl::Initialize(base::OnceCallback<void(bool)> callback) {
+void ArCoreGl::Initialize(base::OnceCallback<void(bool)> callback) {
   DCHECK(IsOnGlThread());
 
   // Do not DCHECK !is_initialized to allow multiple calls to correctly
@@ -116,12 +116,12 @@
   }
 
   if (!arcore_->Initialize()) {
-    DLOG(ERROR) << "ARCore failed to initialize";
+    DLOG(ERROR) << "ArCore failed to initialize";
     std::move(callback).Run(false);
     return;
   }
 
-  // Set the texture on ARCore to render the camera.
+  // Set the texture on ArCore to render the camera.
   arcore_->SetCameraTexture(ar_image_transport_->GetCameraTextureId());
   // Set the Geometry to ensure consistent behaviour.
   arcore_->SetDisplayGeometry(gfx::Size(0, 0), display::Display::ROTATE_0);
@@ -131,7 +131,7 @@
   std::move(callback).Run(true);
 }
 
-bool ARCoreGl::InitializeGl() {
+bool ArCoreGl::InitializeGl() {
   DCHECK(IsOnGlThread());
   DCHECK(!is_initialized_);
 
@@ -172,7 +172,7 @@
   return true;
 }
 
-void ARCoreGl::ProduceFrame(
+void ArCoreGl::ProduceFrame(
     const gfx::Size& frame_size,
     display::Display::Rotation display_rotation,
     mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
@@ -182,7 +182,7 @@
 
   // Check if the frame_size and display_rotation updated last frame.
   if (should_recalculate_uvs_) {
-    // Get the UV transform matrix from ARCore's UV transform.
+    // Get the UV transform matrix from ArCore's UV transform.
     std::vector<float> uvs_transformed =
         arcore_->TransformDisplayUvCoords(kDisplayCoordinatesForTransform);
     uv_transform_ = ConvertUvsToTransformMatrix(uvs_transformed);
@@ -211,10 +211,10 @@
     should_recalculate_uvs_ = true;
   }
 
-  TRACE_EVENT_BEGIN0("gpu", "ARCore Update");
+  TRACE_EVENT_BEGIN0("gpu", "ArCore Update");
   bool camera_updated = false;
   mojom::VRPosePtr pose = arcore_->Update(&camera_updated);
-  TRACE_EVENT_END0("gpu", "ARCore Update");
+  TRACE_EVENT_END0("gpu", "ArCore Update");
   if (!camera_updated) {
     DVLOG(1) << "arcore_->Update() failed";
     std::move(callback).Run(nullptr);
@@ -244,25 +244,25 @@
   // on the arcore_->Update() call above, can be processed in this frame.
   gl_thread_task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&ARCoreGl::ProcessFrame, weak_ptr_factory_.GetWeakPtr(),
+      base::BindOnce(&ArCoreGl::ProcessFrame, weak_ptr_factory_.GetWeakPtr(),
                      base::Passed(&frame_data), frame_size,
                      base::Passed(&callback)));
 }
 
-void ARCoreGl::RequestHitTest(
+void ArCoreGl::RequestHitTest(
     mojom::XRRayPtr ray,
     mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) {
   DCHECK(IsOnGlThread());
   DCHECK(is_initialized_);
 
-  std::unique_ptr<ARCoreHitTestRequest> request =
-      std::make_unique<ARCoreHitTestRequest>();
+  std::unique_ptr<ArCoreHitTestRequest> request =
+      std::make_unique<ArCoreHitTestRequest>();
   request->ray = std::move(ray);
   request->callback = std::move(callback);
   hit_test_requests_.push_back(std::move(request));
 }
 
-void ARCoreGl::ProcessFrame(
+void ArCoreGl::ProcessFrame(
     mojom::XRFrameDataPtr frame_data,
     const gfx::Size& frame_size,
     mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
@@ -300,25 +300,25 @@
   std::move(callback).Run(std::move(frame_data));
 }
 
-void ARCoreGl::Pause() {
+void ArCoreGl::Pause() {
   DCHECK(IsOnGlThread());
   DCHECK(is_initialized_);
 
   arcore_->Pause();
 }
 
-void ARCoreGl::Resume() {
+void ArCoreGl::Resume() {
   DCHECK(IsOnGlThread());
   DCHECK(is_initialized_);
 
   arcore_->Resume();
 }
 
-bool ARCoreGl::IsOnGlThread() const {
+bool ArCoreGl::IsOnGlThread() const {
   return gl_thread_task_runner_->BelongsToCurrentThread();
 }
 
-base::WeakPtr<ARCoreGl> ARCoreGl::GetWeakPtr() {
+base::WeakPtr<ArCoreGl> ArCoreGl::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 00ad774..b752784 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -34,16 +34,16 @@
 
 namespace device {
 
-class ARCore;
-struct ARCoreHitTestRequest;
+class ArCore;
+struct ArCoreHitTestRequest;
 class ARImageTransport;
 
 // All of this class's methods must be called on the same valid GL thread with
 // the exception of GetGlThreadTaskRunner() and GetWeakPtr().
-class ARCoreGl {
+class ArCoreGl {
  public:
-  explicit ARCoreGl(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge);
-  ~ARCoreGl();
+  explicit ArCoreGl(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge);
+  ~ArCoreGl();
 
   void Initialize(base::OnceCallback<void(bool)> callback);
 
@@ -61,7 +61,7 @@
       mojom::XRRayPtr,
       mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback);
 
-  base::WeakPtr<ARCoreGl> GetWeakPtr();
+  base::WeakPtr<ArCoreGl> GetWeakPtr();
 
  private:
   // TODO(https://crbug/835948): remove frame_size.
@@ -77,7 +77,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
 
   // Created on GL thread and should only be accessed on that thread.
-  std::unique_ptr<ARCore> arcore_;
+  std::unique_ptr<ArCore> arcore_;
   std::unique_ptr<ARImageTransport> ar_image_transport_;
 
   // Default dummy values to ensure consistent behaviour.
@@ -87,18 +87,18 @@
   gfx::Transform uv_transform_;
   gfx::Transform projection_;
   // The first run of ProduceFrame should set uv_transform_ and projection_
-  // using the default settings in ARCore.
+  // using the default settings in ArCore.
   bool should_recalculate_uvs_ = true;
 
   bool is_initialized_ = false;
 
   vr::FPSMeter fps_meter_;
 
-  std::vector<std::unique_ptr<ARCoreHitTestRequest>> hit_test_requests_;
+  std::vector<std::unique_ptr<ArCoreHitTestRequest>> hit_test_requests_;
 
   // Must be last.
-  base::WeakPtrFactory<ARCoreGl> weak_ptr_factory_;
-  DISALLOW_COPY_AND_ASSIGN(ARCoreGl);
+  base::WeakPtrFactory<ArCoreGl> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ArCoreGl);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc b/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc
index c49c012..260ff29b7 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl_thread.cc
@@ -11,31 +11,31 @@
 
 namespace device {
 
-ARCoreGlThread::ARCoreGlThread(
+ArCoreGlThread::ArCoreGlThread(
     std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge,
     base::OnceCallback<void()> initialized_callback)
-    : base::android::JavaHandlerThread("ARCoreGL"),
+    : base::android::JavaHandlerThread("ArCoreGL"),
       mailbox_bridge_(std::move(mailbox_bridge)),
       initialized_callback_(std::move(initialized_callback)) {}
 
-ARCoreGlThread::~ARCoreGlThread() {
+ArCoreGlThread::~ArCoreGlThread() {
   Stop();
 }
 
-ARCoreGl* ARCoreGlThread::GetARCoreGl() {
+ArCoreGl* ArCoreGlThread::GetArCoreGl() {
   return arcore_gl_.get();
 }
 
-void ARCoreGlThread::Init() {
+void ArCoreGlThread::Init() {
   DCHECK(!arcore_gl_);
 
   arcore_gl_ =
-      std::make_unique<ARCoreGl>(base::ResetAndReturn(&mailbox_bridge_));
+      std::make_unique<ArCoreGl>(base::ResetAndReturn(&mailbox_bridge_));
 
   std::move(initialized_callback_).Run();
 }
 
-void ARCoreGlThread::CleanUp() {
+void ArCoreGlThread::CleanUp() {
   arcore_gl_.reset();
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h b/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h
index b28745f..95f2580 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl_thread.h
@@ -14,14 +14,14 @@
 
 namespace device {
 
-class ARCoreGl;
+class ArCoreGl;
 
-class ARCoreGlThread : public base::android::JavaHandlerThread {
+class ArCoreGlThread : public base::android::JavaHandlerThread {
  public:
-  ARCoreGlThread(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge,
+  ArCoreGlThread(std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge,
                  base::OnceCallback<void()> initialized_callback);
-  ~ARCoreGlThread() override;
-  ARCoreGl* GetARCoreGl();
+  ~ArCoreGlThread() override;
+  ArCoreGl* GetArCoreGl();
 
  protected:
   void Init() override;
@@ -32,9 +32,9 @@
   base::OnceCallback<void()> initialized_callback_;
 
   // Created on GL thread.
-  std::unique_ptr<ARCoreGl> arcore_gl_;
+  std::unique_ptr<ArCoreGl> arcore_gl_;
 
-  DISALLOW_COPY_AND_ASSIGN(ARCoreGlThread);
+  DISALLOW_COPY_AND_ASSIGN(ArCoreGlThread);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index f645070fa..a138f0f6 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -21,13 +21,13 @@
 
 namespace device {
 
-ARCoreImpl::ARCoreImpl()
+ArCoreImpl::ArCoreImpl()
     : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       weak_ptr_factory_(this) {}
 
-ARCoreImpl::~ARCoreImpl() = default;
+ArCoreImpl::~ArCoreImpl() = default;
 
-bool ARCoreImpl::Initialize() {
+bool ArCoreImpl::Initialize() {
   DCHECK(IsOnGlThread());
   DCHECK(!arcore_session_.is_valid());
 
@@ -35,7 +35,7 @@
 
   JNIEnv* env = base::android::AttachCurrentThread();
   if (!env) {
-    DLOG(ERROR) << "Unable to get JNIEnv for ARCore";
+    DLOG(ERROR) << "Unable to get JNIEnv for ArCore";
     return false;
   }
 
@@ -48,7 +48,7 @@
   }
 
   if (!vr::ArCoreJavaUtils::EnsureLoaded()) {
-    DLOG(ERROR) << "ARCore could not be loaded.";
+    DLOG(ERROR) << "ArCore could not be loaded.";
     return false;
   }
 
@@ -97,24 +97,24 @@
   return true;
 }
 
-void ARCoreImpl::SetCameraTexture(GLuint camera_texture_id) {
+void ArCoreImpl::SetCameraTexture(GLuint camera_texture_id) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   ArSession_setCameraTextureName(arcore_session_.get(), camera_texture_id);
 }
 
-void ARCoreImpl::SetDisplayGeometry(
+void ArCoreImpl::SetDisplayGeometry(
     const gfx::Size& frame_size,
     display::Display::Rotation display_rotation) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   // Display::Rotation is the same as Android's rotation and is compatible with
-  // what ARCore is expecting.
+  // what ArCore is expecting.
   ArSession_setDisplayGeometry(arcore_session_.get(), display_rotation,
                                frame_size.width(), frame_size.height());
 }
 
-std::vector<float> ARCoreImpl::TransformDisplayUvCoords(
+std::vector<float> ArCoreImpl::TransformDisplayUvCoords(
     const base::span<const float> uvs) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
@@ -128,7 +128,7 @@
   return uvs_out;
 }
 
-mojom::VRPosePtr ARCoreImpl::Update(bool* camera_updated) {
+mojom::VRPosePtr ArCoreImpl::Update(bool* camera_updated) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   DCHECK(arcore_frame_.is_valid());
@@ -182,7 +182,7 @@
   return pose;
 }
 
-void ARCoreImpl::Pause() {
+void ArCoreImpl::Pause() {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   ArStatus status = ArSession_pause(arcore_session_.get());
@@ -190,7 +190,7 @@
       << "ArSession_pause failed: status = " << status;
 }
 
-void ARCoreImpl::Resume() {
+void ArCoreImpl::Resume() {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   ArStatus status = ArSession_resume(arcore_session_.get());
@@ -198,7 +198,7 @@
       << "ArSession_resume failed: status = " << status;
 }
 
-gfx::Transform ARCoreImpl::GetProjectionMatrix(float near, float far) {
+gfx::Transform ArCoreImpl::GetProjectionMatrix(float near, float far) {
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   DCHECK(arcore_frame_.is_valid());
@@ -209,7 +209,7 @@
   DCHECK(arcore_camera.is_valid())
       << "ArFrame_acquireCamera failed despite documentation saying it cannot";
 
-  // ARCore's projection matrix is 16 floats in column-major order.
+  // ArCore's projection matrix is 16 floats in column-major order.
   float matrix_4x4[16];
   ArCamera_getProjectionMatrix(arcore_session_.get(), arcore_camera.get(), near,
                                far, matrix_4x4);
@@ -219,7 +219,7 @@
 }
 
 // TODO(835948): remove image-size
-bool ARCoreImpl::RequestHitTest(
+bool ArCoreImpl::RequestHitTest(
     const mojom::XRRayPtr& ray,
     const gfx::Size& image_size,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
@@ -240,7 +240,7 @@
     return false;
   }
 
-  // ARCore returns hit-results in sorted order, thus providing the guarantee
+  // ArCore returns hit-results in sorted order, thus providing the guarantee
   // of sorted results promised by the WebXR spec for requestHitTest().
   ArFrame_hitTest(arcore_session_.get(), arcore_frame_.get(),
                   screen_point.x() * image_size.width(),
@@ -317,7 +317,7 @@
 }
 
 // TODO(835948): remove this method.
-bool ARCoreImpl::TransformRayToScreenSpace(const mojom::XRRayPtr& ray,
+bool ArCoreImpl::TransformRayToScreenSpace(const mojom::XRRayPtr& ray,
                                            const gfx::Size& image_size,
                                            gfx::PointF* screen_point) {
   DCHECK(IsOnGlThread());
@@ -370,7 +370,7 @@
   return true;
 }
 
-bool ARCoreImpl::IsOnGlThread() {
+bool ArCoreImpl::IsOnGlThread() {
   return gl_thread_task_runner_->BelongsToCurrentThread();
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.h b/chrome/browser/android/vr/arcore_device/arcore_impl.h
index e1ccc63..55ff8b6d 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.h
@@ -44,7 +44,7 @@
 
 template <>
 void inline ScopedGenericArObject<ArCamera*>::Free(ArCamera* ar_camera) {
-  // Do nothing - ArCamera has no destroy method and is managed by ARCore.
+  // Do nothing - ArCamera has no destroy method and is managed by ArCore.
 }
 
 template <>
@@ -65,10 +65,10 @@
 }  // namespace internal
 
 // This class should be created and accessed entirely on a Gl thread.
-class ARCoreImpl : public ARCore {
+class ArCoreImpl : public ArCore {
  public:
-  ARCoreImpl();
-  ~ARCoreImpl() override;
+  ArCoreImpl();
+  ~ArCoreImpl() override;
 
   bool Initialize() override;
   void SetDisplayGeometry(const gfx::Size& frame_size,
@@ -90,21 +90,21 @@
                                  gfx::PointF* screen_point);
 
   bool IsOnGlThread();
-  base::WeakPtr<ARCoreImpl> GetWeakPtr() {
+  base::WeakPtr<ArCoreImpl> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
 
   scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
 
-  // An ARCore session, which is distinct and independent of XRSessions.
+  // An ArCore session, which is distinct and independent of XRSessions.
   // There will only ever be one in Chrome even when supporting
   // multiple XRSessions.
   internal::ScopedArCoreObject<ArSession*> arcore_session_;
   internal::ScopedArCoreObject<ArFrame*> arcore_frame_;
 
   // Must be last.
-  base::WeakPtrFactory<ARCoreImpl> weak_ptr_factory_;
-  DISALLOW_COPY_AND_ASSIGN(ARCoreImpl);
+  base::WeakPtrFactory<ArCoreImpl> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ArCoreImpl);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
index e111d09..e8c6531 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
@@ -31,7 +31,7 @@
   return LoadArCoreSdk(base::android::ConvertJavaStringToUTF8(env, java_path));
 }
 
-ArCoreJavaUtils::ArCoreJavaUtils(device::ARCoreDevice* arcore_device)
+ArCoreJavaUtils::ArCoreJavaUtils(device::ArCoreDevice* arcore_device)
     : arcore_device_(arcore_device) {
   DCHECK(arcore_device_);
 
@@ -53,7 +53,7 @@
 void ArCoreJavaUtils::OnRequestInstallSupportedArCoreCanceled(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
-  arcore_device_->OnRequestInstallSupportedARCoreCanceled();
+  arcore_device_->OnRequestInstallSupportedArCoreCanceled();
 }
 
 bool ArCoreJavaUtils::ShouldRequestInstallArModule() {
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
index dcd87dd7..10619956 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
@@ -10,7 +10,7 @@
 #include "base/memory/weak_ptr.h"
 
 namespace device {
-class ARCoreDevice;
+class ArCoreDevice;
 }
 
 namespace vr {
@@ -19,7 +19,7 @@
  public:
   static base::android::ScopedJavaLocalRef<jobject> GetApplicationContext();
   static bool EnsureLoaded();
-  explicit ArCoreJavaUtils(device::ARCoreDevice* arcore_device);
+  explicit ArCoreJavaUtils(device::ArCoreDevice* arcore_device);
   ~ArCoreJavaUtils();
   bool ShouldRequestInstallArModule();
   void RequestInstallArModule();
@@ -37,7 +37,7 @@
       const base::android::JavaParamRef<jobject>& obj);
 
  private:
-  device::ARCoreDevice* arcore_device_;
+  device::ArCoreDevice* arcore_device_;
   base::android::ScopedJavaGlobalRef<jobject> j_arcore_java_utils_;
 };
 
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index 1b7eccb3..02fffd4 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -13,17 +13,17 @@
 
 namespace device {
 
-FakeARCore::FakeARCore()
+FakeArCore::FakeArCore()
     : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
 
-FakeARCore::~FakeARCore() = default;
+FakeArCore::~FakeArCore() = default;
 
-bool FakeARCore::Initialize() {
+bool FakeArCore::Initialize() {
   DCHECK(IsOnGlThread());
   return true;
 }
 
-void FakeARCore::SetDisplayGeometry(
+void FakeArCore::SetDisplayGeometry(
     const gfx::Size& frame_size,
     display::Display::Rotation display_rotation) {
   DCHECK(IsOnGlThread());
@@ -31,9 +31,9 @@
   frame_size_ = frame_size;
 }
 
-void FakeARCore::SetCameraTexture(GLuint texture) {
+void FakeArCore::SetCameraTexture(GLuint texture) {
   DCHECK(IsOnGlThread());
-  // We need a GL_TEXTURE_EXTERNAL_OES to be compatible with the real ARCore.
+  // We need a GL_TEXTURE_EXTERNAL_OES to be compatible with the real ArCore.
   // The content doesn't really matter, just create an AHardwareBuffer-backed
   // GLImage and bind it to the texture.
 
@@ -116,19 +116,19 @@
   glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
 }
 
-std::vector<float> FakeARCore::TransformDisplayUvCoords(
+std::vector<float> FakeArCore::TransformDisplayUvCoords(
     const base::span<const float> uvs) {
-  // Try to match ARCore's transfore values.
+  // Try to match ArCore's transfore values.
   //
-  // Sample ARCore input: width=1080, height=1795, rotation=0,
+  // Sample ArCore input: width=1080, height=1795, rotation=0,
   // vecs = (0, 0), (0, 1), (1, 0), (1, 1)
-  // Sample ARCore output:
+  // Sample ArCore output:
   //   (0.0325544, 1,
   //    0.967446, 1,
   //    0.0325543, 0,
   //    0.967446, 1.19209e-07)
   //
-  // FakeARCoreDriver test_arcore;
+  // FakeArCoreDriver test_arcore;
   // test_arcore.SetCameraAspect(16.f / 9.f);
   // test_arcore.SetDisplayGeometry(0, 1080, 1795);
   // float in[8] = {0, 0, 0, 1, 1, 0, 1, 1};
@@ -221,12 +221,12 @@
   return uvs_out;
 }
 
-gfx::Transform FakeARCore::GetProjectionMatrix(float near, float far) {
+gfx::Transform FakeArCore::GetProjectionMatrix(float near, float far) {
   DCHECK(IsOnGlThread());
   // Get a projection matrix matching the current screen orientation and
   // aspect. Currently, this uses a hardcoded FOV angle for the smaller screen
   // dimension, and adjusts the other angle to preserve the aspect. A better
-  // simulation of ARCore should apply cropping to the underlying fixed-aspect
+  // simulation of ArCore should apply cropping to the underlying fixed-aspect
   // simulated camera image.
   constexpr float fov_half_angle_degrees = 30.f;
   float base_tan = tanf(fov_half_angle_degrees * base::kPiFloat / 180.f);
@@ -253,7 +253,7 @@
   return result;
 }
 
-mojom::VRPosePtr FakeARCore::Update(bool* camera_updated) {
+mojom::VRPosePtr FakeArCore::Update(bool* camera_updated) {
   DCHECK(IsOnGlThread());
   DCHECK(camera_updated);
 
@@ -274,7 +274,7 @@
   return pose;
 }
 
-bool FakeARCore::RequestHitTest(
+bool FakeArCore::RequestHitTest(
     const mojom::XRRayPtr& ray,
     const gfx::Size& image_size,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
@@ -282,15 +282,15 @@
   return false;
 }
 
-void FakeARCore::Pause() {
+void FakeArCore::Pause() {
   DCHECK(IsOnGlThread());
 }
 
-void FakeARCore::Resume() {
+void FakeArCore::Resume() {
   DCHECK(IsOnGlThread());
 }
 
-bool FakeARCore::IsOnGlThread() const {
+bool FakeArCore::IsOnGlThread() const {
   return gl_thread_task_runner_->BelongsToCurrentThread();
 }
 
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h
index a7fc8f6b..70531e6 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.h
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -17,15 +17,15 @@
 
 namespace device {
 
-// Minimal fake ARCore implementation for testing. It can populate
+// Minimal fake ArCore implementation for testing. It can populate
 // the camera texture with a GL_TEXTURE_OES image and do UV transform
 // calculations.
-class FakeARCore : public ARCore {
+class FakeArCore : public ArCore {
  public:
-  FakeARCore();
-  ~FakeARCore() override;
+  FakeArCore();
+  ~FakeArCore() override;
 
-  // ARCoreDriverBase implementation.
+  // ArCoreDriverBase implementation.
   bool Initialize() override;
   void SetCameraTexture(GLuint texture) override;
   void SetDisplayGeometry(const gfx::Size& frame_size,
@@ -55,7 +55,7 @@
   // Storage for the testing placeholder image to keep it alive.
   scoped_refptr<gl::GLImageAHardwareBuffer> placeholder_camera_image_;
 
-  DISALLOW_COPY_AND_ASSIGN(FakeARCore);
+  DISALLOW_COPY_AND_ASSIGN(FakeArCore);
 };
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/vr_shell_delegate.cc b/chrome/browser/android/vr/vr_shell_delegate.cc
index c6c75e9..252e289d 100644
--- a/chrome/browser/android/vr/vr_shell_delegate.cc
+++ b/chrome/browser/android/vr/vr_shell_delegate.cc
@@ -53,20 +53,20 @@
 }
 
 #if BUILDFLAG(ENABLE_ARCORE)
-class ARCoreDeviceProviderFactoryImpl
-    : public device::ARCoreDeviceProviderFactory {
+class ArCoreDeviceProviderFactoryImpl
+    : public device::ArCoreDeviceProviderFactory {
  public:
-  ARCoreDeviceProviderFactoryImpl() = default;
-  ~ARCoreDeviceProviderFactoryImpl() override = default;
+  ArCoreDeviceProviderFactoryImpl() = default;
+  ~ArCoreDeviceProviderFactoryImpl() override = default;
   std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ARCoreDeviceProviderFactoryImpl);
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactoryImpl);
 };
 
 std::unique_ptr<device::VRDeviceProvider>
-ARCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
-  return std::make_unique<device::ARCoreDeviceProvider>();
+ArCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
+  return std::make_unique<device::ArCoreDeviceProvider>();
 }
 #endif
 
@@ -354,8 +354,8 @@
 #if BUILDFLAG(ENABLE_ARCORE)
   // TODO(https://crbug.com/837965): Move this to an ARCore-specific location
   // with similar timing (occurs before XRRuntimeManager is initialized).
-  device::ARCoreDeviceProviderFactory::Install(
-      std::make_unique<ARCoreDeviceProviderFactoryImpl>());
+  device::ArCoreDeviceProviderFactory::Install(
+      std::make_unique<ArCoreDeviceProviderFactoryImpl>());
 #endif
 }
 
diff --git a/chrome/browser/apps/platform_apps/api/BUILD.gn b/chrome/browser/apps/platform_apps/api/BUILD.gn
index 066162a..85842d0e 100644
--- a/chrome/browser/apps/platform_apps/api/BUILD.gn
+++ b/chrome/browser/apps/platform_apps/api/BUILD.gn
@@ -45,6 +45,7 @@
     "//chrome/browser/apps/platform_apps/api/music_manager_private",
     "//chrome/browser/extensions",
     "//chrome/common",
+    "//chrome/common/apps/platform_apps",
     "//chrome/common/apps/platform_apps/api",
     "//chrome/services/media_gallery_util/public/cpp",
     "//components/storage_monitor",
diff --git a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
index 1fd3b9b..d99a297 100644
--- a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
+++ b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_api.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/chrome_select_file_policy.h"
 #include "chrome/common/apps/platform_apps/api/media_galleries.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/services/media_gallery_util/public/cpp/safe_media_metadata_parser.h"
@@ -60,7 +61,6 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/api_permission.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "net/base/mime_sniffer.h"
 #include "storage/browser/blob/blob_data_handle.h"
@@ -152,18 +152,18 @@
   if (!rfh)
     return NULL;
 
-  extensions::MediaGalleriesPermission::CheckParam read_param(
-      extensions::MediaGalleriesPermission::kReadPermission);
+  MediaGalleriesPermission::CheckParam read_param(
+      MediaGalleriesPermission::kReadPermission);
   const extensions::PermissionsData* permissions_data =
       extension->permissions_data();
   bool has_read_permission = permissions_data->CheckAPIPermissionWithParam(
       extensions::APIPermission::kMediaGalleries, &read_param);
-  extensions::MediaGalleriesPermission::CheckParam copy_to_param(
-      extensions::MediaGalleriesPermission::kCopyToPermission);
+  MediaGalleriesPermission::CheckParam copy_to_param(
+      MediaGalleriesPermission::kCopyToPermission);
   bool has_copy_to_permission = permissions_data->CheckAPIPermissionWithParam(
       extensions::APIPermission::kMediaGalleries, &copy_to_param);
-  extensions::MediaGalleriesPermission::CheckParam delete_param(
-      extensions::MediaGalleriesPermission::kDeletePermission);
+  MediaGalleriesPermission::CheckParam delete_param(
+      MediaGalleriesPermission::kDeletePermission);
   bool has_delete_permission = permissions_data->CheckAPIPermissionWithParam(
       extensions::APIPermission::kMediaGalleries, &delete_param);
 
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
index 4f3936d..74142660 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
@@ -36,6 +36,8 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/omnibox/browser/autocomplete_classifier.h"
 #include "components/omnibox/browser/autocomplete_match.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/omnibox_pedal_provider.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "components/unified_consent/unified_consent_service.h"
@@ -95,7 +97,11 @@
               NewPersonalizedDataCollectionConsentHelper(
                   ProfileSyncServiceFactory::GetSyncServiceForBrowserContext(
                       profile_))),
-      storage_partition_(nullptr) {}
+      storage_partition_(nullptr) {
+  if (OmniboxFieldTrial::GetPedalSuggestionMode() !=
+      OmniboxFieldTrial::PedalSuggestionMode::NONE)
+    pedal_provider_ = std::make_unique<OmniboxPedalProvider>();
+}
 
 ChromeAutocompleteProviderClient::~ChromeAutocompleteProviderClient() {
 }
@@ -169,6 +175,15 @@
                                                           create_if_necessary);
 }
 
+OmniboxPedalProvider* ChromeAutocompleteProviderClient::GetPedalProvider()
+    const {
+  // If Pedals are disabled, we should never get here to use the provider.
+  DCHECK_NE(OmniboxFieldTrial::GetPedalSuggestionMode(),
+            OmniboxFieldTrial::PedalSuggestionMode::NONE);
+  DCHECK(pedal_provider_);
+  return pedal_provider_.get();
+}
+
 scoped_refptr<ShortcutsBackend>
 ChromeAutocompleteProviderClient::GetShortcutsBackend() {
   return ShortcutsBackendFactory::GetForProfile(profile_);
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
index 6eda7d2..23e917a 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.h
@@ -40,6 +40,7 @@
       bool create_if_necessary) const override;
   DocumentSuggestionsService* GetDocumentSuggestionsService(
       bool create_if_necessary) const override;
+  OmniboxPedalProvider* GetPedalProvider() const override;
   scoped_refptr<ShortcutsBackend> GetShortcutsBackend() override;
   scoped_refptr<ShortcutsBackend> GetShortcutsBackendIfExists() override;
   std::unique_ptr<KeywordExtensionsDelegate> GetKeywordExtensionsDelegate(
@@ -86,6 +87,7 @@
  private:
   Profile* profile_;
   ChromeAutocompleteSchemeClassifier scheme_classifier_;
+  std::unique_ptr<OmniboxPedalProvider> pedal_provider_;
   std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>
       url_consent_helper_;
 
diff --git a/chrome/browser/autofill/strike_database_factory.cc b/chrome/browser/autofill/strike_database_factory.cc
index c21d418..4972606 100644
--- a/chrome/browser/autofill/strike_database_factory.cc
+++ b/chrome/browser/autofill/strike_database_factory.cc
@@ -40,9 +40,4 @@
       profile->GetPath().Append(FILE_PATH_LITERAL("AutofillStrikeDatabase")));
 }
 
-content::BrowserContext* StrikeDatabaseFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
 }  // namespace autofill
diff --git a/chrome/browser/autofill/strike_database_factory.h b/chrome/browser/autofill/strike_database_factory.h
index 5e93e11..60e2959 100644
--- a/chrome/browser/autofill/strike_database_factory.h
+++ b/chrome/browser/autofill/strike_database_factory.h
@@ -38,8 +38,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* profile) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
 
   DISALLOW_COPY_AND_ASSIGN(StrikeDatabaseFactory);
 };
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index d86ddd51..a621529 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -89,6 +89,7 @@
         <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_HTML" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default_proxy.html" type="chrome_html" />
         <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_JS" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default_proxy.js" type="chrome_html" />
         <structure name="IDR_NUX_CHOOSER_SHARED_CSS" file="resources\welcome\onboarding_welcome\shared\chooser_shared_css.html" type="chrome_html" />
+        <structure name="IDR_WELCOME_ONBOARDING_WELCOME_SHARED_I18N_SETUP_HTML" file="resources\welcome\onboarding_welcome\shared\i18n_setup.html" type="chrome_html" />
       </if>
       <if expr="is_win">
         <structure name="IDR_WELCOME_WIN10_CSS" file="resources\welcome\welcome_win10.css" type="chrome_html" />
@@ -260,8 +261,6 @@
             <include name="IDR_MD_BOOKMARKS_DEBOUNCER_JS" file="resources\md_bookmarks\debouncer.js" type="BINDATA" />
             <include name="IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML" file="resources\md_bookmarks\dialog_focus_manager.html" type="BINDATA" />
             <include name="IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS" file="resources\md_bookmarks\dialog_focus_manager.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DND_CHIP_HTML" file="resources\md_bookmarks\dnd_chip.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DND_CHIP_JS" file="resources\md_bookmarks\dnd_chip.js" type="BINDATA" />
             <include name="IDR_MD_BOOKMARKS_DND_MANAGER_HTML" file="resources\md_bookmarks\dnd_manager.html" type="BINDATA" />
             <include name="IDR_MD_BOOKMARKS_DND_MANAGER_JS" file="resources\md_bookmarks\dnd_manager.js" type="BINDATA" />
             <include name="IDR_MD_BOOKMARKS_EDIT_DIALOG_HTML" file="resources\md_bookmarks\edit_dialog.html" type="BINDATA" />
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index aa5f263..335f1f4 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4004,7 +4004,11 @@
               MaybeCreateThrottleFor(handle);
       if (bookmark_app_experimental_throttle)
         throttles.push_back(std::move(bookmark_app_experimental_throttle));
-    } else {
+    } else if (!base::FeatureList::IsEnabled(
+                   features::kDesktopPWAsStayInWindow)) {
+      // Only add the bookmark app navigation throttle if the stay in
+      // window flag is not set, as the navigation throttle controls
+      // opening out of scope links in the browser.
       auto bookmark_app_throttle =
           extensions::BookmarkAppNavigationThrottle::MaybeCreateThrottleFor(
               handle);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index f2d4129..892dbad 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -393,6 +393,8 @@
     "arc/auth/arc_robot_auth_code_fetcher.h",
     "arc/bluetooth/arc_bluetooth_bridge.cc",
     "arc/bluetooth/arc_bluetooth_bridge.h",
+    "arc/bluetooth/arc_bluetooth_task_queue.cc",
+    "arc/bluetooth/arc_bluetooth_task_queue.h",
     "arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.cc",
     "arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h",
     "arc/boot_phase_monitor/arc_instance_throttle.cc",
@@ -1968,6 +1970,8 @@
     "app_mode/test_kiosk_extension_builder.h",
     "crostini/crostini_test_helper.cc",
     "crostini/crostini_test_helper.h",
+    "drive/drivefs_test_support.cc",
+    "drive/drivefs_test_support.h",
     "extensions/test_external_cache.cc",
     "extensions/test_external_cache.h",
     "lock_screen_apps/fake_lock_screen_profile_creator.cc",
@@ -1998,7 +2002,10 @@
     ":chromeos",
     "//chrome/test:test_support",
     "//chromeos",
+    "//chromeos/components/drivefs",
+    "//chromeos/components/drivefs:test_support",
     "//components/crx_file",
+    "//components/drive",
     "//components/policy/proto",
     "//crypto:platform",
     "//google_apis",
@@ -2040,6 +2047,7 @@
     "arc/arc_support_host_unittest.cc",
     "arc/arc_util_unittest.cc",
     "arc/bluetooth/arc_bluetooth_bridge_unittest.cc",
+    "arc/bluetooth/arc_bluetooth_task_queue_unittest.cc",
     "arc/boot_phase_monitor/arc_boot_phase_monitor_bridge_unittest.cc",
     "arc/downloads_watcher/arc_downloads_watcher_service_unittest.cc",
     "arc/extensions/arc_support_message_host_unittest.cc",
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc b/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
index b0faf95..fa71541 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
@@ -192,13 +192,7 @@
 }
 
 base::Version GetPlatformVersion() {
-  int32_t major_version;
-  int32_t minor_version;
-  int32_t bugfix_version;
-  base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
-                                               &bugfix_version);
-  return base::Version(base::StringPrintf("%d.%d.%d", major_version,
-                                          minor_version, bugfix_version));
+  return base::Version(base::SysInfo::OperatingSystemVersion());
 }
 
 // Converts a flag constant to actual command line switch value.
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
index e15f884..65e8c215 100644
--- a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
+++ b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
@@ -327,11 +327,16 @@
     auto enrollment_token_fetcher =
         std::make_unique<ArcActiveDirectoryEnrollmentTokenFetcher>(
             ArcSessionManager::Get()->support_host());
-    enrollment_token_fetcher->Fetch(
+
+    // Add the request to |pending_token_requests_| first, before starting a
+    // token fetch. In case the callback is called immediately, we do not want
+    // to add an already completed request to |pending_token_requests_|.
+    auto* enrollment_token_fetcher_ptr = enrollment_token_fetcher.get();
+    pending_token_requests_.emplace_back(std::move(enrollment_token_fetcher));
+    enrollment_token_fetcher_ptr->Fetch(
         base::BindOnce(&ArcAuthService::OnActiveDirectoryEnrollmentTokenFetched,
                        weak_ptr_factory_.GetWeakPtr(),
-                       enrollment_token_fetcher.get(), std::move(callback)));
-    pending_token_requests_.emplace_back(std::move(enrollment_token_fetcher));
+                       enrollment_token_fetcher_ptr, std::move(callback)));
     return;
   }
 
@@ -358,11 +363,16 @@
     auth_code_fetcher = CreateArcBackgroundAuthCodeFetcher(
         signin_manager->GetAuthenticatedAccountId(), initial_signin);
   }
-  auth_code_fetcher->Fetch(
-      base::BindOnce(&ArcAuthService::OnPrimaryAccountAuthCodeFetched,
-                     weak_ptr_factory_.GetWeakPtr(), auth_code_fetcher.get(),
-                     std::move(callback)));
+
+  // Add the request to |pending_token_requests_| first, before starting a token
+  // fetch. In case the callback is called immediately, we do not want to add an
+  // already completed request to |pending_token_requests_|.
+  auto* auth_code_fetcher_ptr = auth_code_fetcher.get();
   pending_token_requests_.emplace_back(std::move(auth_code_fetcher));
+  auth_code_fetcher_ptr->Fetch(
+      base::BindOnce(&ArcAuthService::OnPrimaryAccountAuthCodeFetched,
+                     weak_ptr_factory_.GetWeakPtr(), auth_code_fetcher_ptr,
+                     std::move(callback)));
 }
 
 void ArcAuthService::OnTokenUpserted(
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
index 5e4a03dd..430a3a7 100644
--- a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.cc
@@ -1197,6 +1197,11 @@
 }
 
 void ArcBluetoothBridge::StartDiscovery() {
+  discovery_queue_.Push(base::BindOnce(&ArcBluetoothBridge::StartDiscoveryImpl,
+                                       weak_factory_.GetWeakPtr(), false));
+}
+
+void ArcBluetoothBridge::StartDiscoveryImpl(bool le_scan) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(bluetooth_adapter_);
 
@@ -1206,10 +1211,14 @@
                                base::Bind(&ArcBluetoothBridge::CancelDiscovery,
                                           weak_factory_.GetWeakPtr()));
     SendCachedDevicesFound();
+    discovery_queue_.Pop();
     return;
   }
 
-  bluetooth_adapter_->StartDiscoverySession(
+  bluetooth_adapter_->StartDiscoverySessionWithFilter(
+      le_scan ? std::make_unique<BluetoothDiscoveryFilter>(
+                    device::BLUETOOTH_TRANSPORT_LE)
+              : nullptr,
       base::Bind(&ArcBluetoothBridge::OnDiscoveryStarted,
                  weak_factory_.GetWeakPtr()),
       base::Bind(&ArcBluetoothBridge::OnDiscoveryError,
@@ -1217,14 +1226,20 @@
 }
 
 void ArcBluetoothBridge::CancelDiscovery() {
-  if (!discovery_session_) {
-    return;
-  }
+  discovery_queue_.Push(base::BindOnce(&ArcBluetoothBridge::CancelDiscoveryImpl,
+                                       weak_factory_.GetWeakPtr()));
+}
 
-  discovery_session_->Stop(base::Bind(&ArcBluetoothBridge::OnDiscoveryStopped,
-                                      weak_factory_.GetWeakPtr()),
-                           base::Bind(&ArcBluetoothBridge::OnDiscoveryError,
-                                      weak_factory_.GetWeakPtr()));
+void ArcBluetoothBridge::CancelDiscoveryImpl() {
+  discovery_off_timer_.Stop();
+  discovery_session_.reset();
+  auto* bluetooth_instance = ARC_GET_INSTANCE_FOR_METHOD(
+      arc_bridge_service_->bluetooth(), OnDiscoveryStateChanged);
+  if (bluetooth_instance != nullptr) {
+    bluetooth_instance->OnDiscoveryStateChanged(
+        mojom::BluetoothDiscoveryState::STOPPED);
+  }
+  discovery_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnPoweredOn(
@@ -1260,37 +1275,22 @@
     std::unique_ptr<BluetoothDiscoverySession> session) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  auto* bluetooth_instance = ARC_GET_INSTANCE_FOR_METHOD(
-      arc_bridge_service_->bluetooth(), OnDiscoveryStateChanged);
-  if (!bluetooth_instance)
-    return;
-
-  discovery_session_ = std::move(session);
-
   // We need to set timer to turn device discovery off because of the difference
   // between Android API (do device discovery once) and Chrome API (do device
   // discovery until user turns it off).
   discovery_off_timer_.Start(FROM_HERE, kDiscoveryTimeout,
                              base::Bind(&ArcBluetoothBridge::CancelDiscovery,
                                         weak_factory_.GetWeakPtr()));
+  discovery_session_ = std::move(session);
 
-  bluetooth_instance->OnDiscoveryStateChanged(
-      mojom::BluetoothDiscoveryState::STARTED);
-
-  SendCachedDevicesFound();
-}
-
-void ArcBluetoothBridge::OnDiscoveryStopped() {
   auto* bluetooth_instance = ARC_GET_INSTANCE_FOR_METHOD(
       arc_bridge_service_->bluetooth(), OnDiscoveryStateChanged);
-  if (!bluetooth_instance)
-    return;
-
-  discovery_session_.reset();
-  discovery_off_timer_.Stop();
-
-  bluetooth_instance->OnDiscoveryStateChanged(
-      mojom::BluetoothDiscoveryState::STOPPED);
+  if (bluetooth_instance != nullptr) {
+    bluetooth_instance->OnDiscoveryStateChanged(
+        mojom::BluetoothDiscoveryState::STARTED);
+    SendCachedDevicesFound();
+  }
+  discovery_queue_.Pop();
 }
 
 void ArcBluetoothBridge::CreateBond(mojom::BluetoothAddressPtr addr,
@@ -1363,19 +1363,8 @@
 }
 
 void ArcBluetoothBridge::StartLEScan() {
-  DCHECK(bluetooth_adapter_);
-  if (discovery_session_) {
-    LOG(WARNING) << "Discovery session already running; leaving alone";
-    SendCachedDevicesFound();
-    return;
-  }
-  bluetooth_adapter_->StartDiscoverySessionWithFilter(
-      std::make_unique<BluetoothDiscoveryFilter>(
-          device::BLUETOOTH_TRANSPORT_LE),
-      base::Bind(&ArcBluetoothBridge::OnDiscoveryStarted,
-                 weak_factory_.GetWeakPtr()),
-      base::Bind(&ArcBluetoothBridge::OnDiscoveryError,
-                 weak_factory_.GetWeakPtr()));
+  discovery_queue_.Push(base::BindOnce(&ArcBluetoothBridge::StartDiscoveryImpl,
+                                       weak_factory_.GetWeakPtr(), true));
 }
 
 void ArcBluetoothBridge::StopLEScan() {
@@ -2183,31 +2172,6 @@
       base::Bind(&OnRemoveServiceRecordError, repeating_callback));
 }
 
-template <typename... Args>
-void ArcBluetoothBridge::AddAdvertisementTask(
-    base::OnceCallback<void(base::OnceCallback<void(Args...)>)> task,
-    base::OnceCallback<void(Args...)> callback) {
-  advertisement_task_queue_.emplace(base::BindOnce(
-      std::move(task),
-      base::BindOnce(&ArcBluetoothBridge::CompleteAdvertisementTask<Args...>,
-                     weak_factory_.GetWeakPtr(), std::move(callback))));
-  if (advertisement_task_queue_.size() != 1)
-    return;
-  // No task pending, run immediately.
-  std::move(advertisement_task_queue_.front()).Run();
-}
-
-template <typename... Args>
-void ArcBluetoothBridge::CompleteAdvertisementTask(
-    base::OnceCallback<void(Args...)> callback,
-    Args... args) {
-  std::move(callback).Run(std::forward<Args>(args)...);
-  advertisement_task_queue_.pop();  // Current task is done. Pop it from queue.
-  if (advertisement_task_queue_.empty())
-    return;
-  std::move(advertisement_task_queue_.front()).Run();  // Run next task.
-}
-
 bool ArcBluetoothBridge::GetAdvertisementHandle(int32_t* adv_handle) {
   for (int i = 0; i < kMaxAdvertisements; i++) {
     if (advertisements_.find(i) == advertisements_.end()) {
@@ -2220,10 +2184,9 @@
 
 void ArcBluetoothBridge::ReserveAdvertisementHandle(
     ReserveAdvertisementHandleCallback callback) {
-  AddAdvertisementTask(
+  advertisement_queue_.Push(
       base::BindOnce(&ArcBluetoothBridge::ReserveAdvertisementHandleImpl,
-                     weak_factory_.GetWeakPtr()),
-      std::move(callback));
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void ArcBluetoothBridge::ReserveAdvertisementHandleImpl(
@@ -2236,6 +2199,7 @@
     LOG(WARNING) << "Out of space for advertisement data";
     std::move(callback).Run(mojom::BluetoothGattStatus::GATT_FAILURE,
                             kInvalidAdvertisementHandle);
+    advertisement_queue_.Pop();
     return;
   }
 
@@ -2245,17 +2209,16 @@
   // The advertisement will be registered when we get the call
   // to SetAdvertisingData. For now, just return the adv_handle.
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_SUCCESS, adv_handle);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::EnableAdvertisement(
     int32_t adv_handle,
     std::unique_ptr<device::BluetoothAdvertisement::Data> advertisement,
     EnableAdvertisementCallback callback) {
-  AddAdvertisementTask(
-      base::BindOnce(&ArcBluetoothBridge::EnableAdvertisementImpl,
-                     weak_factory_.GetWeakPtr(), adv_handle,
-                     std::move(advertisement)),
-      std::move(callback));
+  advertisement_queue_.Push(base::BindOnce(
+      &ArcBluetoothBridge::EnableAdvertisementImpl, weak_factory_.GetWeakPtr(),
+      adv_handle, std::move(advertisement), std::move(callback)));
 }
 
 void ArcBluetoothBridge::EnableAdvertisementImpl(
@@ -2268,7 +2231,7 @@
   // updating the callee interface.
   auto repeating_callback =
       base::AdaptCallbackForRepeating(std::move(callback));
-  base::Callback<void(void)> done_callback =
+  base::Closure done_callback =
       base::Bind(&ArcBluetoothBridge::OnReadyToRegisterAdvertisement,
                  weak_factory_.GetWeakPtr(), repeating_callback, adv_handle,
                  base::Passed(std::move(advertisement)));
@@ -2278,7 +2241,8 @@
 
   auto it = advertisements_.find(adv_handle);
   if (it == advertisements_.end()) {
-    repeating_callback.Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+    error_callback.Run(
+        BluetoothAdvertisement::ErrorCode::ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
     return;
   }
   if (it->second == nullptr) {
@@ -2291,10 +2255,9 @@
 void ArcBluetoothBridge::DisableAdvertisement(
     int32_t adv_handle,
     EnableAdvertisementCallback callback) {
-  AddAdvertisementTask(
-      base::BindOnce(&ArcBluetoothBridge::DisableAdvertisementImpl,
-                     weak_factory_.GetWeakPtr(), adv_handle),
-      std::move(callback));
+  advertisement_queue_.Push(base::BindOnce(
+      &ArcBluetoothBridge::DisableAdvertisementImpl, weak_factory_.GetWeakPtr(),
+      adv_handle, std::move(callback)));
 }
 
 void ArcBluetoothBridge::DisableAdvertisementImpl(
@@ -2306,7 +2269,7 @@
   // updating the callee interface.
   auto repeating_callback =
       base::AdaptCallbackForRepeating(std::move(callback));
-  base::Callback<void(void)> done_callback =
+  base::Closure done_callback =
       base::Bind(&ArcBluetoothBridge::OnUnregisterAdvertisementDone,
                  weak_factory_.GetWeakPtr(), repeating_callback, adv_handle);
   base::Callback<void(BluetoothAdvertisement::ErrorCode)> error_callback =
@@ -2315,7 +2278,8 @@
 
   auto it = advertisements_.find(adv_handle);
   if (it == advertisements_.end()) {
-    repeating_callback.Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+    error_callback.Run(
+        BluetoothAdvertisement::ErrorCode::ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
     return;
   }
   if (it->second == nullptr) {
@@ -2328,10 +2292,9 @@
 void ArcBluetoothBridge::ReleaseAdvertisementHandle(
     int32_t adv_handle,
     ReleaseAdvertisementHandleCallback callback) {
-  AddAdvertisementTask(
-      base::BindOnce(&ArcBluetoothBridge::ReleaseAdvertisementHandleImpl,
-                     weak_factory_.GetWeakPtr(), adv_handle),
-      std::move(callback));
+  advertisement_queue_.Push(base::BindOnce(
+      &ArcBluetoothBridge::ReleaseAdvertisementHandleImpl,
+      weak_factory_.GetWeakPtr(), adv_handle, std::move(callback)));
 }
 
 void ArcBluetoothBridge::ReleaseAdvertisementHandleImpl(
@@ -2340,12 +2303,14 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (advertisements_.find(adv_handle) == advertisements_.end()) {
     std::move(callback).Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+    advertisement_queue_.Pop();
     return;
   }
 
   if (!advertisements_[adv_handle]) {
     advertisements_.erase(adv_handle);
     std::move(callback).Run(mojom::BluetoothGattStatus::GATT_SUCCESS);
+    advertisement_queue_.Pop();
     return;
   }
 
@@ -2384,6 +2349,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   advertisements_[adv_handle] = std::move(advertisement);
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_SUCCESS);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnRegisterAdvertisementError(
@@ -2395,6 +2361,7 @@
                << ", error code = " << error_code;
   advertisements_[adv_handle] = nullptr;
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnUnregisterAdvertisementDone(
@@ -2403,6 +2370,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   advertisements_[adv_handle] = nullptr;
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_SUCCESS);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnUnregisterAdvertisementError(
@@ -2414,6 +2382,7 @@
                << ", error code = " << error_code;
   advertisements_[adv_handle] = nullptr;
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnReleaseAdvertisementHandleDone(
@@ -2422,6 +2391,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   advertisements_.erase(adv_handle);
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_SUCCESS);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnReleaseAdvertisementHandleError(
@@ -2433,10 +2403,12 @@
                << ", error code = " << error_code;
   advertisements_.erase(adv_handle);
   std::move(callback).Run(mojom::BluetoothGattStatus::GATT_FAILURE);
+  advertisement_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnDiscoveryError() {
   LOG(WARNING) << "failed to change discovery state";
+  discovery_queue_.Pop();
 }
 
 void ArcBluetoothBridge::OnPairing(mojom::BluetoothAddressPtr addr) const {
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
index 08dbb24..6efc659 100644
--- a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_bridge.h
@@ -15,9 +15,9 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "base/containers/queue.h"
 #include "base/threading/thread_checker.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h"
 #include "components/arc/common/bluetooth.mojom.h"
 #include "components/arc/common/intent_helper.mojom.h"
 #include "components/arc/connection_observer.h"
@@ -331,13 +331,6 @@
       ReleaseAdvertisementHandleCallback callback) override;
 
  private:
-  template <typename... Args>
-  void AddAdvertisementTask(
-      base::OnceCallback<void(base::OnceCallback<void(Args...)>)> task,
-      base::OnceCallback<void(Args...)> callback);
-  template <typename... Args>
-  void CompleteAdvertisementTask(base::OnceCallback<void(Args...)> callback,
-                                 Args... args);
   void ReserveAdvertisementHandleImpl(
       ReserveAdvertisementHandleCallback callback);
   void EnableAdvertisementImpl(
@@ -350,6 +343,9 @@
       int32_t adv_handle,
       ReleaseAdvertisementHandleCallback callback);
 
+  void StartDiscoveryImpl(bool le_scan);
+  void CancelDiscoveryImpl();
+
   template <typename InstanceType, typename HostType>
   class ConnectionObserverImpl;
 
@@ -362,7 +358,6 @@
   void OnPoweredError(AdapterStateCallback callback) const;
   void OnDiscoveryStarted(
       std::unique_ptr<device::BluetoothDiscoverySession> session);
-  void OnDiscoveryStopped();
   void OnDiscoveryError();
   void OnPairing(mojom::BluetoothAddressPtr addr) const;
   void OnPairedDone(mojom::BluetoothAddressPtr addr) const;
@@ -619,7 +614,8 @@
   enum { kMaxAdvertisements = 1 };
   std::map<int32_t, scoped_refptr<device::BluetoothAdvertisement>>
       advertisements_;
-  base::queue<base::OnceClosure> advertisement_task_queue_;
+  ArcBluetoothTaskQueue advertisement_queue_;
+  ArcBluetoothTaskQueue discovery_queue_;
 
   THREAD_CHECKER(thread_checker_);
 
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.cc b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.cc
new file mode 100644
index 0000000..c897484
--- /dev/null
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.cc
@@ -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.
+
+#include <utility>
+
+#include "chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h"
+
+namespace arc {
+
+ArcBluetoothTaskQueue::ArcBluetoothTaskQueue() = default;
+ArcBluetoothTaskQueue::~ArcBluetoothTaskQueue() = default;
+
+void ArcBluetoothTaskQueue::Push(base::OnceClosure task) {
+  queue_.emplace(std::move(task));
+  if (queue_.size() == 1)
+    std::move(queue_.front()).Run();
+}
+
+void ArcBluetoothTaskQueue::Pop() {
+  queue_.pop();
+  if (!queue_.empty())
+    std::move(queue_.front()).Run();
+}
+
+}  // namespace arc
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h
new file mode 100644
index 0000000..ee010fae
--- /dev/null
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h
@@ -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.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ARC_BLUETOOTH_ARC_BLUETOOTH_TASK_QUEUE_H_
+#define CHROME_BROWSER_CHROMEOS_ARC_BLUETOOTH_ARC_BLUETOOTH_TASK_QUEUE_H_
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+
+namespace arc {
+
+// A queue to ensure serial and ordered execution of tasks.
+class ArcBluetoothTaskQueue {
+ public:
+  ArcBluetoothTaskQueue();
+  ~ArcBluetoothTaskQueue();
+
+  // Pushes |task| into this queue.
+  void Push(base::OnceClosure task);
+
+  // Pops the current task from this queue, indicating its completion.
+  void Pop();
+
+ private:
+  base::queue<base::OnceClosure> queue_;
+  DISALLOW_COPY_AND_ASSIGN(ArcBluetoothTaskQueue);
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_CHROMEOS_ARC_BLUETOOTH_ARC_BLUETOOTH_TASK_QUEUE_H_
diff --git a/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue_unittest.cc b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue_unittest.cc
new file mode 100644
index 0000000..d79fea38
--- /dev/null
+++ b/chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/arc/bluetooth/arc_bluetooth_task_queue.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace arc {
+
+TEST(ArcBluetoothTaskQueueTest, Serial) {
+  bool done = false;
+  ArcBluetoothTaskQueue task_queue;
+  task_queue.Push(base::DoNothing());
+  task_queue.Push(base::BindOnce([](bool* done) { *done = true; }, &done));
+  EXPECT_FALSE(done);
+  task_queue.Pop();
+  EXPECT_TRUE(done);
+  task_queue.Pop();
+}
+
+TEST(ArcBluetoothTaskQueueTest, Order) {
+  std::string str;
+  ArcBluetoothTaskQueue task_queue;
+  task_queue.Push(base::BindOnce([](std::string* str) { *str += '1'; }, &str));
+  task_queue.Push(base::BindOnce([](std::string* str) { *str += '2'; }, &str));
+  task_queue.Push(base::BindOnce([](std::string* str) { *str += '3'; }, &str));
+  task_queue.Pop();
+  task_queue.Pop();
+  task_queue.Pop();
+  EXPECT_EQ("123", str);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/chromeos/drive/drivefs_test_support.cc b/chrome/browser/chromeos/drive/drivefs_test_support.cc
new file mode 100644
index 0000000..c1e154d
--- /dev/null
+++ b/chrome/browser/chromeos/drive/drivefs_test_support.cc
@@ -0,0 +1,72 @@
+// 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/chromeos/drive/drivefs_test_support.h"
+
+#include <string>
+#include <utility>
+
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "base/test/bind_test_util.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/drive/drive_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace drive {
+
+const char FakeDriveFsHelper::kPredefinedProfileSalt[] = "salt";
+
+FakeDriveFsHelper::FakeDriveFsHelper(Profile* profile,
+                                     const base::FilePath& mount_path)
+    : mount_path_(mount_path), fake_drivefs_(mount_path_) {
+  profile->GetPrefs()->SetString(
+      drive::prefs::kDriveFsProfileSalt,
+      drive::FakeDriveFsHelper::kPredefinedProfileSalt);
+  fake_drivefs_.RegisterMountingForAccountId(
+      base::BindLambdaForTesting([profile]() {
+        auto* user = chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
+        if (!user)
+          return std::string();
+
+        return base::MD5String(FakeDriveFsHelper::kPredefinedProfileSalt +
+                               ("-" + user->GetAccountId().GetAccountIdKey()));
+      }));
+}
+FakeDriveFsHelper::~FakeDriveFsHelper() = default;
+
+base::RepeatingCallback<
+    std::unique_ptr<drivefs::DriveFsHost::MojoConnectionDelegate>()>
+FakeDriveFsHelper::CreateFakeDriveFsConnectionDelegateFactory() {
+  return base::BindRepeating(&drivefs::FakeDriveFs::CreateConnectionDelegate,
+                             base::Unretained(&fake_drivefs_));
+}
+
+bool SetUpUserDataDirectoryForDriveFsTest() {
+  auto known_users_list = std::make_unique<base::ListValue>();
+  auto user_dict = std::make_unique<base::DictionaryValue>();
+  user_dict->SetString("account_type", "google");
+  user_dict->SetString("email", "testuser@gmail.com");
+  user_dict->SetString("gaia_id", "123456");
+  known_users_list->Append(std::move(user_dict));
+
+  base::DictionaryValue local_state;
+  local_state.SetList("KnownUsers", std::move(known_users_list));
+
+  std::string local_state_json;
+  if (!base::JSONWriter::Write(local_state, &local_state_json))
+    return false;
+
+  base::FilePath local_state_file;
+  if (!base::PathService::Get(chrome::DIR_USER_DATA, &local_state_file))
+    return false;
+  local_state_file = local_state_file.Append(chrome::kLocalStateFilename);
+  return base::WriteFile(local_state_file, local_state_json.data(),
+                         local_state_json.size()) != -1;
+}
+
+}  // namespace drive
diff --git a/chrome/browser/chromeos/drive/drivefs_test_support.h b/chrome/browser/chromeos/drive/drivefs_test_support.h
new file mode 100644
index 0000000..5966b1398
--- /dev/null
+++ b/chrome/browser/chromeos/drive/drivefs_test_support.h
@@ -0,0 +1,44 @@
+// 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_CHROMEOS_DRIVE_DRIVEFS_TEST_SUPPORT_H_
+#define CHROME_BROWSER_CHROMEOS_DRIVE_DRIVEFS_TEST_SUPPORT_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "chromeos/components/drivefs/drivefs_host.h"
+#include "chromeos/components/drivefs/fake_drivefs.h"
+
+class Profile;
+
+namespace drive {
+
+bool SetUpUserDataDirectoryForDriveFsTest();
+
+class FakeDriveFsHelper {
+ public:
+  static const char kPredefinedProfileSalt[];
+
+  FakeDriveFsHelper(Profile* profile, const base::FilePath& mount_path);
+  ~FakeDriveFsHelper();
+
+  base::RepeatingCallback<
+      std::unique_ptr<drivefs::DriveFsHost::MojoConnectionDelegate>()>
+  CreateFakeDriveFsConnectionDelegateFactory();
+
+  const base::FilePath& mount_path() { return mount_path_; }
+  drivefs::FakeDriveFs& fake_drivefs() { return fake_drivefs_; }
+
+ private:
+  const base::FilePath mount_path_;
+  drivefs::FakeDriveFs fake_drivefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeDriveFsHelper);
+};
+
+}  // namespace drive
+
+#endif  // CHROME_BROWSER_CHROMEOS_DRIVE_DRIVEFS_TEST_SUPPORT_H_
diff --git a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
index 41045f4..3dc5a951 100644
--- a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
+++ b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
@@ -11,6 +11,7 @@
 #include "base/path_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
+#include "chrome/browser/chromeos/drive/drivefs_test_support.h"
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/file_manager/mount_test_util.h"
 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
@@ -23,6 +24,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths.h"
+#include "chromeos/chromeos_features.h"
 #include "chromeos/chromeos_switches.h"
 #include "components/drive/service/fake_drive_service.h"
 #include "components/session_manager/core/session_manager.h"
@@ -86,8 +88,10 @@
 class FakeSelectFileDialog : public ui::SelectFileDialog {
  public:
   FakeSelectFileDialog(ui::SelectFileDialog::Listener* listener,
-                       std::unique_ptr<ui::SelectFilePolicy> policy)
-      : ui::SelectFileDialog(listener, std::move(policy)) {}
+                       std::unique_ptr<ui::SelectFilePolicy> policy,
+                       const base::FilePath& drivefs_root)
+      : ui::SelectFileDialog(listener, std::move(policy)),
+        drivefs_root_(drivefs_root) {}
 
   void SelectFileImpl(Type type,
                       const base::string16& title,
@@ -97,8 +101,12 @@
                       const base::FilePath::StringType& default_extension,
                       gfx::NativeWindow owning_window,
                       void* params) override {
-    listener_->FileSelected(base::FilePath("/special/drive-user/root/test_dir"),
-                            0, nullptr);
+    listener_->FileSelected(
+        (base::FeatureList::IsEnabled(chromeos::features::kDriveFs)
+             ? drivefs_root_
+             : base::FilePath("/special/drive-user"))
+            .Append("root/test_dir"),
+        0, nullptr);
   }
 
   bool IsRunning(gfx::NativeWindow owning_window) const override {
@@ -111,15 +119,23 @@
 
  private:
   ~FakeSelectFileDialog() override = default;
+
+  const base::FilePath drivefs_root_;
 };
 
 class FakeSelectFileDialogFactory : public ui::SelectFileDialogFactory {
+ public:
+  explicit FakeSelectFileDialogFactory(const base::FilePath& drivefs_root)
+      : drivefs_root_(drivefs_root) {}
+
  private:
   ui::SelectFileDialog* Create(
       ui::SelectFileDialog::Listener* listener,
       std::unique_ptr<ui::SelectFilePolicy> policy) override {
-    return new FakeSelectFileDialog(listener, std::move(policy));
+    return new FakeSelectFileDialog(listener, std::move(policy), drivefs_root_);
   }
+
+  const base::FilePath drivefs_root_;
 };
 
 // Sets up the initial file system state for native local and restricted native
@@ -283,6 +299,10 @@
   FileSystemExtensionApiTestBase() = default;
   ~FileSystemExtensionApiTestBase() override = default;
 
+  bool SetUpUserDataDirectory() override {
+    return drive::SetUpUserDataDirectoryForDriveFsTest();
+  }
+
   void SetUp() override {
     InitTestFileSystem();
     extensions::ExtensionApiTest::SetUp();
@@ -485,13 +505,21 @@
     // could exist simultaneously.
     DCHECK(!fake_drive_service_);
     fake_drive_service_ = CreateDriveService();
+    base::FilePath drivefs_mount_point;
+    InitializeLocalFileSystem("drive-user/root", &drivefs_root_,
+                              &drivefs_mount_point);
+    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
+        profile, drivefs_mount_point.DirName());
     return new drive::DriveIntegrationService(
         profile, nullptr, fake_drive_service_, "", test_cache_root_.GetPath(),
-        nullptr);
+        nullptr,
+        fake_drivefs_helper_->CreateFakeDriveFsConnectionDelegateFactory());
   }
 
   base::ScopedTempDir test_cache_root_;
+  base::ScopedTempDir drivefs_root_;
   drive::FakeDriveService* fake_drive_service_ = nullptr;
+  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
   DriveIntegrationServiceFactory::FactoryCallback
       create_drive_integration_service_;
   std::unique_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
@@ -635,10 +663,26 @@
   // DriveIntegrationService factory function for this test.
   drive::DriveIntegrationService* CreateDriveIntegrationService(
       Profile* profile) {
+    // Ignore signin and lock screen apps profile.
+    if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
+        profile->GetPath() ==
+            chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
+      return nullptr;
+    }
+
+    // LocalAndDriveFileSystemExtensionApiTest doesn't expect that several user
+    // profiles could exist simultaneously.
+    DCHECK(!fake_drive_service_);
     fake_drive_service_ = CreateDriveService();
+    base::FilePath drivefs_mount_point;
+    InitializeLocalFileSystem("drive-user/root", &drivefs_root_,
+                              &drivefs_mount_point);
+    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
+        profile, drivefs_mount_point.DirName());
     return new drive::DriveIntegrationService(
-        profile, nullptr, fake_drive_service_, "drive",
-        test_cache_root_.GetPath(), nullptr);
+        profile, nullptr, fake_drive_service_, "", test_cache_root_.GetPath(),
+        nullptr,
+        fake_drivefs_helper_->CreateFakeDriveFsConnectionDelegateFactory());
   }
 
  private:
@@ -648,7 +692,9 @@
 
   // For drive volume.
   base::ScopedTempDir test_cache_root_;
+  base::ScopedTempDir drivefs_root_;
   drive::FakeDriveService* fake_drive_service_ = nullptr;
+  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
   DriveIntegrationServiceFactory::FactoryCallback
       create_drive_integration_service_;
   std::unique_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
@@ -765,7 +811,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DriveFileSystemExtensionApiTest, RetainEntry) {
-  ui::SelectFileDialog::SetFactory(new FakeSelectFileDialogFactory());
+  ui::SelectFileDialog::SetFactory(new FakeSelectFileDialogFactory(
+      drivefs_root_.GetPath().Append("drive-user")));
   EXPECT_TRUE(RunFileSystemExtensionApiTest("file_browser/retain_entry",
                                             FILE_PATH_LITERAL("manifest.json"),
                                             "",
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 67f487b..cdb0c4e0 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -388,7 +388,7 @@
                       TestCase("openQuickView").TabletMode(),
                       TestCase("openQuickViewImage"),
                       TestCase("openQuickViewVideo"),
-// QuickView PDF test fails on MSAN, crbug.com/768070 crbug.com/891150
+// QuickView PDF test fails on MSAN, crbug.com/768070
 #if !defined(MEMORY_SANITIZER)
                       TestCase("openQuickViewPdf"),
 #endif
@@ -398,6 +398,7 @@
                       TestCase("openQuickViewBackgroundColorHtml"),
                       TestCase("openQuickViewDrive"),
                       TestCase("openQuickViewDrive").EnableDriveFs(),
+                      TestCase("openQuickViewCrostini"),
                       TestCase("openQuickViewUsb"),
                       TestCase("openQuickViewMtp"),
                       TestCase("closeQuickView")));
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index 943699e..e068abd8 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/drive/drivefs_test_support.h"
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/file_manager/app_id.h"
 #include "chrome/browser/chromeos/file_manager/file_manager_test_util.h"
@@ -35,9 +36,7 @@
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/sync_file_system/mock_remote_file_sync_service.h"
 #include "chrome/browser/sync_file_system/sync_file_system_service_factory.h"
-#include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
-#include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
 #include "chromeos/chromeos_features.h"
@@ -48,7 +47,6 @@
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_cros_disks_client.h"
 #include "components/drive/chromeos/file_system_interface.h"
-#include "components/drive/drive_pref_names.h"
 #include "components/drive/service/fake_drive_service.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
@@ -423,8 +421,6 @@
   DISALLOW_COPY_AND_ASSIGN(OfflineGetDriveConnectionState);
 };
 
-constexpr char kPredefinedProfileSalt[] = "salt";
-
 }  // anonymous namespace
 
 // LocalTestVolume: test volume for a local drive.
@@ -854,7 +850,7 @@
     const base::FilePath target_path = GetTargetPathForTestEntry(entry);
 
     entries_.insert(std::make_pair(target_path, entry));
-    fake_drivefs_->SetMetadata(
+    fake_drivefs_helper_->fake_drivefs().SetMetadata(
         GetRelativeDrivePathForTestEntry(entry), entry.mime_type,
         base::FilePath(entry.target_path).BaseName().value(), entry.pinned,
         entry.shared_option == AddEntriesMessage::SharedOption::SHARED ||
@@ -897,25 +893,12 @@
     CHECK(base::CreateDirectory(GetMyDrivePath()));
     CHECK(base::CreateDirectory(GetTeamDriveGrandRoot()));
 
-    InitializeFakeDriveFs();
-    return base::BindRepeating(&drivefs::FakeDriveFs::CreateConnectionDelegate,
-                               base::Unretained(fake_drivefs_.get()));
-  }
+    if (!fake_drivefs_helper_) {
+      fake_drivefs_helper_ =
+          std::make_unique<drive::FakeDriveFsHelper>(profile_, mount_path());
+    }
 
-  void InitializeFakeDriveFs() {
-    fake_drivefs_ = std::make_unique<drivefs::FakeDriveFs>(mount_path());
-    fake_drivefs_->RegisterMountingForAccountId(base::BindRepeating(
-        [](Profile* profile) {
-          auto* user =
-              chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
-          if (!user)
-            return std::string();
-
-          return base::MD5String(
-              kPredefinedProfileSalt +
-              ("-" + user->GetAccountId().GetAccountIdKey()));
-        },
-        profile_));
+    return fake_drivefs_helper_->CreateFakeDriveFsConnectionDelegateFactory();
   }
 
   // Updates the ModifiedTime of the entry, and its parent directories if
@@ -988,7 +971,7 @@
 
   Profile* const profile_;
   std::map<base::FilePath, const AddEntriesMessage::TestEntryInfo> entries_;
-  std::unique_ptr<drivefs::FakeDriveFs> fake_drivefs_;
+  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
 
   DISALLOW_COPY_AND_ASSIGN(DriveFsTestVolume);
 };
@@ -1037,6 +1020,7 @@
   }
 
   std::vector<base::Feature> enabled_features;
+  std::vector<base::Feature> disabled_features;
   if (!IsGuestModeTest()) {
     enabled_features.emplace_back(features::kCrostini);
     enabled_features.emplace_back(features::kExperimentalCrostiniUI);
@@ -1044,8 +1028,10 @@
   }
   if (IsDriveFsTest()) {
     enabled_features.emplace_back(chromeos::features::kDriveFs);
+  } else {
+    disabled_features.emplace_back(chromeos::features::kDriveFs);
   }
-  feature_list_.InitWithFeatures(enabled_features, {});
+  feature_list_.InitWithFeatures(enabled_features, disabled_features);
 
   extensions::ExtensionApiTest::SetUpCommandLine(command_line);
 }
@@ -1054,26 +1040,7 @@
   if (IsGuestModeTest())
     return true;
 
-  auto known_users_list = std::make_unique<base::ListValue>();
-  auto user_dict = std::make_unique<base::DictionaryValue>();
-  user_dict->SetString("account_type", "google");
-  user_dict->SetString("email", "testuser@gmail.com");
-  user_dict->SetString("gaia_id", "123456");
-  known_users_list->Append(std::move(user_dict));
-
-  base::DictionaryValue local_state;
-  local_state.SetList("KnownUsers", std::move(known_users_list));
-
-  std::string local_state_json;
-  if (!base::JSONWriter::Write(local_state, &local_state_json))
-    return false;
-
-  base::FilePath local_state_file;
-  if (!base::PathService::Get(chrome::DIR_USER_DATA, &local_state_file))
-    return false;
-  local_state_file = local_state_file.Append(chrome::kLocalStateFilename);
-  return base::WriteFile(local_state_file, local_state_json.data(),
-                         local_state_json.size()) != -1;
+  return drive::SetUpUserDataDirectoryForDriveFsTest();
 }
 
 void FileManagerBrowserTestBase::SetUpInProcessBrowserTestFixture() {
@@ -1422,8 +1389,6 @@
 drive::DriveIntegrationService*
 FileManagerBrowserTestBase::CreateDriveIntegrationService(Profile* profile) {
   if (base::FeatureList::IsEnabled(chromeos::features::kDriveFs)) {
-    profile->GetPrefs()->SetString(drive::prefs::kDriveFsProfileSalt,
-                                   kPredefinedProfileSalt);
     drive_volumes_[profile->GetOriginalProfile()] =
         std::make_unique<DriveFsTestVolume>(profile->GetOriginalProfile());
     if (!IsIncognitoModeTest() &&
diff --git a/chrome/browser/chromeos/file_manager/gallery_browsertest.cc b/chrome/browser/chromeos/file_manager/gallery_browsertest.cc
index 0b167e7326b..9713c90 100644
--- a/chrome/browser/chromeos/file_manager/gallery_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/gallery_browsertest.cc
@@ -330,4 +330,9 @@
   StartTest();
 }
 
+IN_PROC_BROWSER_TEST_F(GalleryBrowserTest, DeleteSingleOpenPhotoOnDownloads) {
+  set_test_case_name("deleteSingleOpenPhotoOnDownloads");
+  StartTest();
+}
+
 }  // namespace file_manager
diff --git a/chrome/browser/chromeos/policy/device_status_collector.cc b/chrome/browser/chromeos/policy/device_status_collector.cc
index 80fc32d3..33835005 100644
--- a/chrome/browser/chromeos/policy/device_status_collector.cc
+++ b/chrome/browser/chromeos/policy/device_status_collector.cc
@@ -281,13 +281,7 @@
 }
 
 base::Version GetPlatformVersion() {
-  int32_t major_version;
-  int32_t minor_version;
-  int32_t bugfix_version;
-  base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
-                                               &bugfix_version);
-  return base::Version(base::StringPrintf("%d.%d.%d", major_version,
-                                          minor_version, bugfix_version));
+  return base::Version(base::SysInfo::OperatingSystemVersion());
 }
 
 // Helper routine to convert from Shill-provided signal strength (percent)
diff --git a/chrome/browser/chromeos/policy/signin_profile_apps_policy_browsertest.cc b/chrome/browser/chromeos/policy/signin_profile_apps_policy_browsertest.cc
index c18c4cf..30058f0 100644
--- a/chrome/browser/chromeos/policy/signin_profile_apps_policy_browsertest.cc
+++ b/chrome/browser/chromeos/policy/signin_profile_apps_policy_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/path_service.h"
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/browser_process.h"
@@ -18,8 +19,8 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/net/url_request_mock_util.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/chromeos_switches.h"
@@ -39,7 +40,8 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/features/feature_channel.h"
-#include "net/test/url_request/url_request_mock_http_job.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -54,17 +56,17 @@
 //   profile:
 const char kManualTestAppId[] = "bjaiihebfngildkcjkjckolinodhliff";
 const char kManualTestAppUpdateManifestPath[] =
-    "extensions/signin_screen_manual_test_app/update_manifest.xml";
+    "/extensions/signin_screen_manual_test_app/update_manifest.xml";
 // * A trivial test app which is NOT whitelisted for running in the sign-in
 //   profile:
 const char kTrivialAppId[] = "mockapnacjbcdncmpkjngjalkhphojek";
 const char kTrivialAppUpdateManifestPath[] =
-    "extensions/trivial_platform_app/update_manifest.xml";
+    "/extensions/trivial_platform_app/update_manifest.xml";
 // * A trivial test extension (note that extensions cannot be whitelisted for
 //   running in the sign-in profile):
 const char kTrivialExtensionId[] = "mockepjebcnmhmhcahfddgfcdgkdifnc";
 const char kTrivialExtensionUpdateManifestPath[] =
-    "extensions/trivial_extension/update_manifest.xml";
+    "/extensions/trivial_extension/update_manifest.xml";
 
 // Observer that allows waiting for an installation failure of a specific
 // extension/app.
@@ -172,15 +174,19 @@
   }
 
   void SetUpOnMainThread() override {
-    EnableUrlRequestMocks();
     DevicePolicyCrosBrowserTest::SetUpOnMainThread();
+
+    embedded_test_server()->RegisterRequestHandler(
+        base::BindRepeating(&SigninProfileAppsPolicyTestBase::InterceptMockHttp,
+                            base::Unretained(this)));
+    ASSERT_TRUE(embedded_test_server()->Start());
   }
 
   void AddExtensionForForceInstallation(
       const std::string& extension_id,
-      const base::FilePath& update_manifest_relative_path) {
-    const GURL update_manifest_url = net::URLRequestMockHTTPJob::GetMockUrl(
-        update_manifest_relative_path.MaybeAsASCII());
+      const std::string& update_manifest_relative_path) {
+    const GURL update_manifest_url =
+        embedded_test_server()->GetURL(update_manifest_relative_path);
     const std::string policy_item_value = base::ReplaceStringPlaceholders(
         "$1;$2", {extension_id, update_manifest_url.spec()}, nullptr);
     device_policy()
@@ -193,12 +199,32 @@
   const version_info::Channel channel_;
 
  private:
-  // Enables URL request mocks for making the test data files (extensions'
-  // update manifests and packages) available under corresponding URLs.
-  static void EnableUrlRequestMocks() {
-    base::PostTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::IO},
-        base::BindOnce(&chrome_browser_net::SetUrlRequestMocksEnabled, true));
+  // Replace "mock.http" with "127.0.0.1:<port>" on "update_manifest.xml" files.
+  // Host resolver doesn't work here because the test file doesn't know the
+  // correct port number.
+  std::unique_ptr<net::test_server::HttpResponse> InterceptMockHttp(
+      const net::test_server::HttpRequest& request) {
+    const std::string kFileNameToIntercept = "update_manifest.xml";
+    if (request.GetURL().ExtractFileName() != kFileNameToIntercept)
+      return nullptr;
+
+    base::FilePath test_data_dir;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
+    // Remove the leading '/'.
+    std::string relative_manifest_path = request.GetURL().path().substr(1);
+    std::string manifest_response;
+    CHECK(base::ReadFileToString(test_data_dir.Append(relative_manifest_path),
+                                 &manifest_response));
+
+    base::ReplaceSubstringsAfterOffset(
+        &manifest_response, 0, "mock.http",
+        embedded_test_server()->host_port_pair().ToString());
+
+    std::unique_ptr<net::test_server::BasicHttpResponse> response(
+        new net::test_server::BasicHttpResponse());
+    response->set_content_type("text/xml");
+    response->set_content(manifest_response);
+    return response;
   }
 
   const extensions::ScopedCurrentChannel scoped_current_channel_;
@@ -226,8 +252,8 @@
   extensions::TestExtensionRegistryObserver registry_observer(
       extensions::ExtensionRegistry::Get(GetProfile()), kManualTestAppId);
 
-  AddExtensionForForceInstallation(
-      kManualTestAppId, base::FilePath(kManualTestAppUpdateManifestPath));
+  AddExtensionForForceInstallation(kManualTestAppId,
+                                   kManualTestAppUpdateManifestPath);
 
   registry_observer.WaitForExtensionLoaded();
   EXPECT_TRUE(extensions::ExtensionRegistry::Get(GetProfile())
@@ -244,8 +270,8 @@
   ExtensionInstallErrorObserver install_error_observer(GetProfile(),
                                                        kTrivialAppId);
 
-  AddExtensionForForceInstallation(
-      kTrivialAppId, base::FilePath(kTrivialAppUpdateManifestPath));
+  AddExtensionForForceInstallation(kTrivialAppId,
+                                   kTrivialAppUpdateManifestPath);
 
   switch (channel_) {
     case version_info::Channel::UNKNOWN:
@@ -271,10 +297,8 @@
                        ExtensionInstallation) {
   ExtensionInstallErrorObserver install_error_observer(GetProfile(),
                                                        kTrivialExtensionId);
-
-  AddExtensionForForceInstallation(
-      kTrivialExtensionId, base::FilePath(kTrivialExtensionUpdateManifestPath));
-
+  AddExtensionForForceInstallation(kTrivialExtensionId,
+                                   kTrivialExtensionUpdateManifestPath);
   install_error_observer.Wait();
   EXPECT_FALSE(extensions::ExtensionRegistry::Get(GetProfile())
                    ->GetInstalledExtension(kTrivialExtensionId));
@@ -315,8 +339,8 @@
 // app.
 IN_PROC_BROWSER_TEST_F(SigninProfileAppsPolicyTest, BackgroundPage) {
   ExtensionBackgroundPageReadyObserver page_observer(kTrivialAppId);
-  AddExtensionForForceInstallation(
-      kTrivialAppId, base::FilePath(kTrivialAppUpdateManifestPath));
+  AddExtensionForForceInstallation(kTrivialAppId,
+                                   kTrivialAppUpdateManifestPath);
   page_observer.Wait();
 }
 
@@ -327,10 +351,10 @@
   extensions::TestExtensionRegistryObserver registry_observer2(
       extensions::ExtensionRegistry::Get(GetProfile()), kTrivialAppId);
 
-  AddExtensionForForceInstallation(
-      kManualTestAppId, base::FilePath(kManualTestAppUpdateManifestPath));
-  AddExtensionForForceInstallation(
-      kTrivialAppId, base::FilePath(kTrivialAppUpdateManifestPath));
+  AddExtensionForForceInstallation(kManualTestAppId,
+                                   kManualTestAppUpdateManifestPath);
+  AddExtensionForForceInstallation(kTrivialAppId,
+                                   kTrivialAppUpdateManifestPath);
 
   registry_observer1.WaitForExtensionLoaded();
   registry_observer2.WaitForExtensionLoaded();
diff --git a/chrome/browser/chromeos/printing/cups_print_job_manager.cc b/chrome/browser/chromeos/printing/cups_print_job_manager.cc
index 65d1444..dbb15853 100644
--- a/chrome/browser/chromeos/printing/cups_print_job_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_print_job_manager.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "base/metrics/histogram_macros.h"
 #include "chrome/browser/chromeos/printing/cups_print_job_notification_manager.h"
 
 namespace chromeos {
@@ -36,6 +37,9 @@
 }
 
 void CupsPrintJobManager::NotifyJobStarted(base::WeakPtr<CupsPrintJob> job) {
+  DCHECK(job);
+  print_job_start_times_[job->GetUniqueId()] = base::TimeTicks::Now();
+
   for (Observer& observer : observers_)
     observer.OnPrintJobStarted(job);
 }
@@ -56,6 +60,8 @@
 }
 
 void CupsPrintJobManager::NotifyJobCanceled(base::WeakPtr<CupsPrintJob> job) {
+  RecordJobDuration(job);
+
   for (Observer& observer : observers_)
     observer.OnPrintJobCancelled(job);
 }
@@ -66,8 +72,37 @@
 }
 
 void CupsPrintJobManager::NotifyJobDone(base::WeakPtr<CupsPrintJob> job) {
+  RecordJobDuration(job);
+
   for (Observer& observer : observers_)
     observer.OnPrintJobDone(job);
 }
 
+// TODO(jschettler): In some instances, Chrome doesn't receive an error state
+// from the printer (crbug.com/883966). For that reason, the job duration is
+// currently recorded for done and cancelled print jobs without accounting
+// for the added time a job may spend in a suspended or error state.
+void CupsPrintJobManager::RecordJobDuration(base::WeakPtr<CupsPrintJob> job) {
+  DCHECK(job);
+
+  auto it = print_job_start_times_.find(job->GetUniqueId());
+  if (it == print_job_start_times_.end())
+    return;
+
+  base::TimeDelta duration = base::TimeTicks::Now() - it->second;
+  switch (job->state()) {
+    case CupsPrintJob::State::STATE_DOCUMENT_DONE:
+      UMA_HISTOGRAM_LONG_TIMES_100("Printing.CUPS.JobDuration.JobDone",
+                                   duration);
+      break;
+    case CupsPrintJob::State::STATE_CANCELLED:
+      UMA_HISTOGRAM_LONG_TIMES_100("Printing.CUPS.JobDuration.JobCancelled",
+                                   duration);
+      break;
+    default:
+      break;
+  }
+  print_job_start_times_.erase(job->GetUniqueId());
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/cups_print_job_manager.h b/chrome/browser/chromeos/printing/cups_print_job_manager.h
index a60b797..90a1750 100644
--- a/chrome/browser/chromeos/printing/cups_print_job_manager.h
+++ b/chrome/browser/chromeos/printing/cups_print_job_manager.h
@@ -5,10 +5,13 @@
 #ifndef CHROME_BROWSER_CHROMEOS_PRINTING_CUPS_PRINT_JOB_MANAGER_H_
 #define CHROME_BROWSER_CHROMEOS_PRINTING_CUPS_PRINT_JOB_MANAGER_H_
 
+#include <map>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/observer_list.h"
+#include "base/time/time.h"
 #include "chrome/browser/chromeos/printing/cups_print_job.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -76,6 +79,11 @@
   base::ObserverList<Observer>::Unchecked observers_;
 
  private:
+  void RecordJobDuration(base::WeakPtr<CupsPrintJob> job);
+
+  // Keyed by CupsPrintJob's unique ID
+  std::map<std::string, base::TimeTicks> print_job_start_times_;
+
   DISALLOW_COPY_AND_ASSIGN(CupsPrintJobManager);
 };
 
diff --git a/chrome/browser/chromeos/smb_client/smb_provider.cc b/chrome/browser/chromeos/smb_client/smb_provider.cc
index 2d5ef62..6319ede 100644
--- a/chrome/browser/chromeos/smb_client/smb_provider.cc
+++ b/chrome/browser/chromeos/smb_client/smb_provider.cc
@@ -25,8 +25,6 @@
                     false /* watchable */,
                     true /* multiple_mounts */,
                     extensions::SOURCE_NETWORK),
-      // TODO(baileyberro): Localize this string, so it shows correctly in all
-      // languages. See l10n_util::GetStringUTF8.
       name_(l10n_util::GetStringUTF8(IDS_SMB_SHARES_ADD_SERVICE_MENU_OPTION)),
       unmount_callback_(std::move(unmount_callback)) {
   icon_set_.SetIcon(IconSet::IconSize::SIZE_16x16,
diff --git a/chrome/browser/chromeos/smb_client/smb_service.cc b/chrome/browser/chromeos/smb_client/smb_service.cc
index fecb9960..b5db0cb 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service.cc
@@ -113,11 +113,6 @@
   CallMount(options, share_path, username, password, std::move(callback));
 }
 
-void SmbService::GatherSharesInNetwork(GatherSharesResponse shares_callback) {
-  share_finder_->GatherSharesInNetwork(base::DoNothing(),
-                                       std::move(shares_callback));
-}
-
 void SmbService::GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,
                                        GatherSharesResponse shares_callback) {
   share_finder_->GatherSharesInNetwork(std::move(discovery_callback),
diff --git a/chrome/browser/chromeos/smb_client/smb_service.h b/chrome/browser/chromeos/smb_client/smb_service.h
index ecad4999..4afc8dcb 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.h
+++ b/chrome/browser/chromeos/smb_client/smb_service.h
@@ -75,7 +75,6 @@
   // for each of the hosts found. |discovery_callback| is called as soon as host
   // discovery is complete. |shares_callback| is called once per host and will
   // contain the URLs to the shares found.
-  void GatherSharesInNetwork(GatherSharesResponse shares_callback);
   void GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,
                              GatherSharesResponse shares_callback);
 
diff --git a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
index afc81fc..b0745c7d 100644
--- a/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
+++ b/chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
+#include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
@@ -565,8 +566,13 @@
   if (params->is_from_touch)
     source = ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH;
 
-  chrome::DragBookmarks(
-      GetProfile(), nodes, web_contents->GetNativeView(), source);
+  chrome::DragBookmarks(GetProfile(),
+                        {
+                            std::move(nodes), params->drag_node_index,
+                            platform_util::GetViewForWindow(
+                                web_contents->GetTopLevelNativeWindow()),
+                            source,
+                        });
 
   return true;
 }
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
index 3825045..80d9228 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
@@ -49,6 +49,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
+#include "extensions/browser/value_store/value_store_factory.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "printing/buildflags/buildflags.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
index d1753118..1f47cbf 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
 
 #include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -222,6 +223,53 @@
   }
 }
 
+// Creates and returns a SpecificSiteControls object for the given
+// |granted_permissions| and |withheld_permissions|.
+std::unique_ptr<developer::SpecificSiteControls> GetSpecificSiteControls(
+    const PermissionSet& runtime_granted_permissions,
+    const PermissionSet& withheld_permissions) {
+  auto controls = std::make_unique<developer::SpecificSiteControls>();
+  constexpr bool kIncludeApiPermissions = false;
+  controls->has_all_hosts =
+      withheld_permissions.ShouldWarnAllHosts(kIncludeApiPermissions) ||
+      runtime_granted_permissions.ShouldWarnAllHosts(kIncludeApiPermissions);
+
+  auto get_distinct_hosts = [](const URLPatternSet& patterns) {
+    std::set<std::string> distinct_hosts;
+    for (URLPattern pattern : patterns) {
+      // We only allow addition/removal of full hosts (since from a
+      // permissions point of view, path is irrelevant). We always make the
+      // path wildcard when adding through this UI, but the optional
+      // permissions API may allow adding permissions with paths.
+      // TODO(devlin): Investigate, and possibly change the optional
+      // permissions API.
+      pattern.SetPath("/*");
+      distinct_hosts.insert(pattern.GetAsString());
+    }
+    return distinct_hosts;
+  };
+
+  std::set<std::string> distinct_granted =
+      get_distinct_hosts(runtime_granted_permissions.effective_hosts());
+  std::set<std::string> distinct_withheld =
+      get_distinct_hosts(withheld_permissions.effective_hosts());
+  controls->hosts.reserve(distinct_granted.size() + distinct_withheld.size());
+  for (auto& host : distinct_granted) {
+    developer::SiteControl host_control;
+    host_control.host = std::move(host);
+    host_control.granted = true;
+    controls->hosts.push_back(std::move(host_control));
+  }
+  for (auto& host : distinct_withheld) {
+    developer::SiteControl host_control;
+    host_control.host = std::move(host);
+    host_control.granted = false;
+    controls->hosts.push_back(std::move(host_control));
+  }
+
+  return controls;
+}
+
 // Populates the |permissions| data for the given |extension|.
 void AddPermissionsInfo(content::BrowserContext* browser_context,
                         const Extension& extension,
@@ -284,22 +332,9 @@
       permissions->host_access = developer::HOST_ACCESS_ON_CLICK;
     } else {
       permissions->host_access = developer::HOST_ACCESS_ON_SPECIFIC_SITES;
-      std::set<std::string> distinct_hosts;
-      for (auto pattern : runtime_granted_permissions->effective_hosts()) {
-        // We only allow addition/removal of full hosts (since from a
-        // permissions point of view, path is irrelevant). We always make the
-        // path wildcard when adding through this UI, but the optional
-        // permissions API may allow adding permissions with paths.
-        // TODO(devlin): Investigate, and possibly change the optional
-        // permissions API.
-        pattern.SetPath("/*");
-        distinct_hosts.insert(pattern.GetAsString());
-      }
-
-      permissions->runtime_host_permissions =
-          std::make_unique<std::vector<std::string>>(
-              std::make_move_iterator(distinct_hosts.begin()),
-              std::make_move_iterator(distinct_hosts.end()));
+      permissions->specific_site_controls = GetSpecificSiteControls(
+          *runtime_granted_permissions,
+          extension.permissions_data()->withheld_permissions());
     }
   }
 }
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
index 07743cc3..12a9a3e 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
@@ -71,6 +71,22 @@
   return nullptr;
 }
 
+// Converts the SiteControls hosts list to a JSON string. This makes test
+// validation considerably more concise and readable.
+std::string SiteControlsToString(
+    const std::vector<developer::SiteControl>& controls) {
+  base::Value list(base::Value::Type::LIST);
+  list.GetList().reserve(controls.size());
+  for (const auto& control : controls) {
+    std::unique_ptr<base::Value> control_value = control.ToValue();
+    list.GetList().push_back(std::move(*control_value));
+  }
+
+  std::string json;
+  CHECK(base::JSONWriter::Write(list, &json));
+  return json;
+}
+
 }  // namespace
 
 class ExtensionInfoGeneratorUnitTest : public ExtensionServiceTestBase {
@@ -296,7 +312,7 @@
     ++i;
   }
   EXPECT_EQ(developer::HOST_ACCESS_NONE, info->permissions.host_access);
-  EXPECT_FALSE(info->permissions.runtime_host_permissions);
+  EXPECT_FALSE(info->permissions.specific_site_controls);
 
   ASSERT_EQ(2u, info->runtime_errors.size());
   const api::developer_private::RuntimeError& runtime_error =
@@ -411,7 +427,7 @@
 
   // The extension should be set to run on all sites.
   EXPECT_EQ(developer::HOST_ACCESS_ON_ALL_SITES, info->permissions.host_access);
-  EXPECT_FALSE(info->permissions.runtime_host_permissions);
+  EXPECT_FALSE(info->permissions.specific_site_controls);
   // With runtime host permissions, no host permissions are added to
   // |simple_permissions|.
   EXPECT_THAT(info->permissions.simple_permissions, testing::IsEmpty());
@@ -423,18 +439,21 @@
   permissions_modifier.SetWithholdHostPermissions(true);
   info = GenerateExtensionInfo(all_urls_extension->id());
   EXPECT_EQ(developer::HOST_ACCESS_ON_CLICK, info->permissions.host_access);
-  EXPECT_FALSE(info->permissions.runtime_host_permissions);
+  EXPECT_FALSE(info->permissions.specific_site_controls);
   EXPECT_THAT(info->permissions.simple_permissions, testing::IsEmpty());
 
   // Granting a host permission should set the extension to run on specific
-  // sites, and those sites should be in the runtime_host_permissions set.
+  // sites, and those sites should be in the specific_site_controls.hosts set.
   permissions_modifier.GrantHostPermission(GURL("https://example.com"));
   info = GenerateExtensionInfo(all_urls_extension->id());
   EXPECT_EQ(developer::HOST_ACCESS_ON_SPECIFIC_SITES,
             info->permissions.host_access);
-  ASSERT_TRUE(info->permissions.runtime_host_permissions);
-  EXPECT_THAT(*info->permissions.runtime_host_permissions,
-              testing::UnorderedElementsAre("https://example.com/*"));
+  ASSERT_TRUE(info->permissions.specific_site_controls);
+  EXPECT_EQ(
+      R"([{"granted":true,"host":"https://example.com/*"},)"
+      R"({"granted":false,"host":"*://*/*"}])",
+      SiteControlsToString(info->permissions.specific_site_controls->hosts));
+  EXPECT_TRUE(info->permissions.specific_site_controls->has_all_hosts);
   EXPECT_THAT(info->permissions.simple_permissions, testing::IsEmpty());
 
   // An extension that doesn't request any host permissions should not have
@@ -443,7 +462,7 @@
       CreateExtension("no urls", ListBuilder().Build(), Manifest::INTERNAL);
   info = GenerateExtensionInfo(no_urls_extension->id());
   EXPECT_EQ(developer::HOST_ACCESS_NONE, info->permissions.host_access);
-  EXPECT_FALSE(info->permissions.runtime_host_permissions);
+  EXPECT_FALSE(info->permissions.specific_site_controls);
 }
 
 TEST_F(ExtensionInfoGeneratorUnitTest, RuntimeHostPermissionsWithoutFeature) {
@@ -455,13 +474,13 @@
   std::unique_ptr<developer::ExtensionInfo> info =
       GenerateExtensionInfo(all_urls_extension->id());
   EXPECT_EQ(developer::HOST_ACCESS_NONE, info->permissions.host_access);
-  EXPECT_FALSE(info->permissions.runtime_host_permissions);
+  EXPECT_FALSE(info->permissions.specific_site_controls);
   ASSERT_EQ(1u, info->permissions.simple_permissions.size());
   EXPECT_EQ("Read and change all your data on the websites you visit",
             info->permissions.simple_permissions[0].message);
 }
 
-// Tests that runtime_host_permissions is correctly populated when permissions
+// Tests that specific_site_controls is correctly populated when permissions
 // are granted by the user beyond what the extension originally requested in the
 // manifest.
 TEST_F(ExtensionInfoGeneratorUnitTest,
@@ -501,9 +520,54 @@
   info = GenerateExtensionInfo(extension->id());
   EXPECT_EQ(developer::HOST_ACCESS_ON_SPECIFIC_SITES,
             info->permissions.host_access);
-  ASSERT_TRUE(info->permissions.runtime_host_permissions);
-  EXPECT_THAT(*info->permissions.runtime_host_permissions,
-              testing::UnorderedElementsAre("*://chromium.org/*"));
+  ASSERT_TRUE(info->permissions.specific_site_controls);
+  EXPECT_EQ(
+      R"([{"granted":true,"host":"*://chromium.org/*"},)"
+      R"({"granted":false,"host":"http://*/*"}])",
+      SiteControlsToString(info->permissions.specific_site_controls->hosts));
+  EXPECT_TRUE(info->permissions.specific_site_controls->has_all_hosts);
+}
+
+// Tests that specific_site_controls is correctly populated when the extension
+// requests access to specific hosts.
+TEST_F(ExtensionInfoGeneratorUnitTest, RuntimeHostPermissionsSpecificHosts) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  scoped_refptr<const Extension> extension =
+      CreateExtension("extension",
+                      ListBuilder()
+                          .Append("https://example.com/*")
+                          .Append("https://chromium.org/*")
+                          .Build(),
+                      Manifest::INTERNAL);
+
+  std::unique_ptr<developer::ExtensionInfo> info =
+      GenerateExtensionInfo(extension->id());
+
+  // Withhold permissions, and grant *://chromium.org/*.
+  ScriptingPermissionsModifier permissions_modifier(profile(), extension);
+  permissions_modifier.SetWithholdHostPermissions(true);
+  URLPattern all_chromium(Extension::kValidHostPermissionSchemes,
+                          "https://chromium.org/*");
+  PermissionSet all_chromium_set(APIPermissionSet(), ManifestPermissionSet(),
+                                 URLPatternSet({all_chromium}),
+                                 URLPatternSet({all_chromium}));
+  PermissionsUpdater(profile()).GrantRuntimePermissions(*extension,
+                                                        all_chromium_set);
+
+  // The generated info should use the entirety of the granted permission,
+  // which is *://chromium.org/*.
+  info = GenerateExtensionInfo(extension->id());
+  EXPECT_EQ(developer::HOST_ACCESS_ON_SPECIFIC_SITES,
+            info->permissions.host_access);
+  ASSERT_TRUE(info->permissions.specific_site_controls);
+  EXPECT_EQ(
+      R"([{"granted":true,"host":"https://chromium.org/*"},)"
+      R"({"granted":false,"host":"https://example.com/*"}])",
+      SiteControlsToString(info->permissions.specific_site_controls->hosts));
+  EXPECT_FALSE(info->permissions.specific_site_controls->has_all_hosts);
 }
 
 // Test that file:// access checkbox does not show up when the user can't
diff --git a/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc b/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc
index 12967558..e8b2c1c 100644
--- a/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc
+++ b/chrome/browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_apitest.cc
@@ -5,6 +5,7 @@
 #include <memory>
 
 #include "base/json/json_writer.h"
+#include "base/path_service.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "base/values.h"
@@ -14,7 +15,7 @@
 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #include "chrome/browser/chromeos/settings/stub_install_attributes.h"
 #include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/browser/net/url_request_mock_util.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/dbus/fake_session_manager_client.h"
 #include "chromeos/system/fake_statistics_provider.h"
@@ -33,7 +34,8 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/test/result_catcher.h"
-#include "net/test/url_request/url_request_mock_http_job.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 
 namespace {
 
@@ -41,10 +43,8 @@
 constexpr char kSerialNumber[] = "serial_number";
 constexpr char kAssetId[] = "asset_id";
 constexpr char kAnnotatedLocation[] = "annotated_location";
-constexpr base::FilePath::CharType kTestExtensionDir[] =
-    FILE_PATH_LITERAL("extensions/api_test/enterprise_device_attributes");
-constexpr base::FilePath::CharType kUpdateManifestFileName[] =
-    FILE_PATH_LITERAL("update_manifest.xml");
+constexpr char kUpdateManifestPath[] =
+    "/extensions/api_test/enterprise_device_attributes/update_manifest.xml";
 
 constexpr char kAffiliatedUserEmail[] = "user@example.com";
 constexpr char kAffiliatedUserGaiaId[] = "1029384756";
@@ -98,6 +98,34 @@
     set_chromeos_user_ = false;
   }
 
+  // Replace "mock.http" with "127.0.0.1:<port>" on "update_manifest.xml" files.
+  // Host resolver doesn't work here because the test file doesn't know the
+  // correct port number.
+  std::unique_ptr<net::test_server::HttpResponse> InterceptMockHttp(
+      const net::test_server::HttpRequest& request) {
+    const std::string kFileNameToIntercept = "update_manifest.xml";
+    if (request.GetURL().ExtractFileName() != kFileNameToIntercept)
+      return nullptr;
+
+    base::FilePath test_data_dir;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
+    // Remove the leading '/'.
+    std::string relative_manifest_path = request.GetURL().path().substr(1);
+    std::string manifest_response;
+    CHECK(base::ReadFileToString(test_data_dir.Append(relative_manifest_path),
+                                 &manifest_response));
+
+    base::ReplaceSubstringsAfterOffset(
+        &manifest_response, 0, "mock.http",
+        embedded_test_server()->host_port_pair().ToString());
+
+    std::unique_ptr<net::test_server::BasicHttpResponse> response(
+        new net::test_server::BasicHttpResponse());
+    response->set_content_type("text/xml");
+    response->set_content(manifest_response);
+    return response;
+  }
+
  protected:
   // ExtensionApiTest
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -170,10 +198,8 @@
     // Extensions that are force-installed come from an update URL, which
     // defaults to the webstore. Use a mock URL for this test with an update
     // manifest that includes the crx file of the test extension.
-    base::FilePath update_manifest_path =
-        base::FilePath(kTestExtensionDir).Append(kUpdateManifestFileName);
-    GURL update_manifest_url(net::URLRequestMockHTTPJob::GetMockUrl(
-        update_manifest_path.MaybeAsASCII()));
+    GURL update_manifest_url(
+        embedded_test_server()->GetURL(kUpdateManifestPath));
 
     std::unique_ptr<base::ListValue> forcelist(new base::ListValue);
     forcelist->AppendString(base::StringPrintf(
@@ -238,10 +264,12 @@
 }
 
 IN_PROC_BROWSER_TEST_P(EnterpriseDeviceAttributesTest, Success) {
-  base::PostTaskWithTraits(
-      FROM_HERE, {content::BrowserThread::IO},
-      base::BindOnce(chrome_browser_net::SetUrlRequestMocksEnabled, true));
-
+  // Setup |URLLoaderInterceptor|, which is required for force-installing the
+  // test extension through policy.
+  embedded_test_server()->RegisterRequestHandler(
+      base::BindRepeating(&EnterpriseDeviceAttributesTest::InterceptMockHttp,
+                          base::Unretained(this)));
+  ASSERT_TRUE(embedded_test_server()->Start());
   SetPolicy();
 
   EXPECT_EQ(GetParam().affiliated, user_manager::UserManager::Get()
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc
index 343df1e..2febb6e0 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc
@@ -8,12 +8,13 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/extensions/api/platform_keys/platform_keys_test_base.h"
 #include "chrome/browser/net/nss_context.h"
-#include "chrome/browser/net/url_request_mock_util.h"
+#include "chrome/common/chrome_paths.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -24,7 +25,8 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "net/cert/nss_cert_database.h"
-#include "net/test/url_request/url_request_mock_http_job.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -104,10 +106,8 @@
     0xb5, 0x6f, 0xe9, 0x1b, 0x32, 0x91, 0x34, 0x38
 };
 
-const base::FilePath::CharType kTestExtensionDir[] =
-    FILE_PATH_LITERAL("extensions/api_test/enterprise_platform_keys");
-const base::FilePath::CharType kUpdateManifestFileName[] =
-    FILE_PATH_LITERAL("update_manifest.xml");
+const char kUpdateManifestPath[] =
+    "/extensions/api_test/enterprise_platform_keys/update_manifest.xml";
 
 void ImportPrivateKeyPKCS8ToSlot(const unsigned char* pkcs8_der,
                                  size_t pkcs8_der_size,
@@ -166,6 +166,13 @@
         switches::kEnableExperimentalWebPlatformFeatures);
   }
 
+  void SetUpOnMainThread() override {
+    embedded_test_server()->RegisterRequestHandler(
+        base::BindRepeating(&EnterprisePlatformKeysTest::InterceptMockHttp,
+                            base::Unretained(this)));
+    PlatformKeysTestBase::SetUpOnMainThread();
+  }
+
   void DidGetCertDatabase(const base::Closure& done_callback,
                           net::NSSCertDatabase* cert_db) {
     // In order to use a prepared certificate, import a private key to the
@@ -180,10 +187,8 @@
     // Extensions that are force-installed come from an update URL, which
     // defaults to the webstore. Use a mock URL for this test with an update
     // manifest that includes the crx file of the test extension.
-    base::FilePath update_manifest_path =
-        base::FilePath(kTestExtensionDir).Append(kUpdateManifestFileName);
-    GURL update_manifest_url(net::URLRequestMockHTTPJob::GetMockUrl(
-        update_manifest_path.MaybeAsASCII()));
+    GURL update_manifest_url(
+        embedded_test_server()->GetURL(kUpdateManifestPath));
 
     std::unique_ptr<base::ListValue> forcelist(new base::ListValue);
     forcelist->AppendString(base::StringPrintf(
@@ -201,6 +206,34 @@
   }
 
  private:
+  // Replace "mock.http" with "127.0.0.1:<port>" on "update_manifest.xml" files.
+  // Host resolver doesn't work here because the test file doesn't know the
+  // correct port number.
+  std::unique_ptr<net::test_server::HttpResponse> InterceptMockHttp(
+      const net::test_server::HttpRequest& request) {
+    const std::string kFileNameToIntercept = "update_manifest.xml";
+    if (request.GetURL().ExtractFileName() != kFileNameToIntercept)
+      return nullptr;
+
+    base::FilePath test_data_dir;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
+    // Remove the leading '/'.
+    std::string relative_manifest_path = request.GetURL().path().substr(1);
+    std::string manifest_response;
+    CHECK(base::ReadFileToString(test_data_dir.Append(relative_manifest_path),
+                                 &manifest_response));
+
+    base::ReplaceSubstringsAfterOffset(
+        &manifest_response, 0, "mock.http",
+        embedded_test_server()->host_port_pair().ToString());
+
+    std::unique_ptr<net::test_server::BasicHttpResponse> response(
+        new net::test_server::BasicHttpResponse());
+    response->set_content_type("text/xml");
+    response->set_content(manifest_response);
+    return response;
+  }
+
   void PrepareTestSystemSlotOnIO(
       crypto::ScopedTestSystemNSSKeySlot* system_slot) override {
     // Import a private key to the system slot.  The Javascript part of this
@@ -220,12 +253,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(EnterprisePlatformKeysTest, Basic) {
-  // Enable the URLRequestMock, which is required for force-installing the
-  // test extension through policy.
-  base::PostTaskWithTraits(
-      FROM_HERE, {content::BrowserThread::IO},
-      base::BindOnce(chrome_browser_net::SetUrlRequestMocksEnabled, true));
-
   {
    base::RunLoop loop;
    GetNSSCertDatabaseForProfile(
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index d4c7f93b..86aeb8b 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -12,6 +12,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
+#include "chrome/browser/chromeos/drive/drivefs_test_support.h"
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
@@ -19,6 +20,7 @@
 #include "chrome/browser/extensions/api/file_system/consent_provider.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/common/chrome_paths.h"
+#include "chromeos/chromeos_features.h"
 #include "components/drive/chromeos/file_system_interface.h"
 #include "components/drive/service/fake_drive_service.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -117,6 +119,10 @@
  public:
   FileSystemApiTestForDrive() {}
 
+  bool SetUpUserDataDirectory() override {
+    return drive::SetUpUserDataDirectoryForDriveFsTest();
+  }
+
   // Sets up fake Drive service for tests (this has to be injected before the
   // real DriveIntegrationService instance is created.)
   void SetUpInProcessBrowserTestFixture() override {
@@ -138,13 +144,15 @@
   void SetUpOnMainThread() override {
     PlatformAppBrowserTest::SetUpOnMainThread();
 
-    std::unique_ptr<drive::ResourceEntry> entry;
-    drive::FileError error = drive::FILE_ERROR_FAILED;
-    integration_service_->file_system()->GetResourceEntry(
-        base::FilePath::FromUTF8Unsafe("drive/root"),  // whatever
-        google_apis::test_util::CreateCopyResultCallback(&error, &entry));
-    content::RunAllTasksUntilIdle();
-    ASSERT_EQ(drive::FILE_ERROR_OK, error);
+    if (!base::FeatureList::IsEnabled(chromeos::features::kDriveFs)) {
+      std::unique_ptr<drive::ResourceEntry> entry;
+      drive::FileError error = drive::FILE_ERROR_FAILED;
+      integration_service_->file_system()->GetResourceEntry(
+          base::FilePath::FromUTF8Unsafe("drive/root"),  // whatever
+          google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+      content::RunAllTasksUntilIdle();
+      ASSERT_EQ(drive::FILE_ERROR_OK, error);
+    }
   }
 
   void TearDown() override {
@@ -152,10 +160,18 @@
     PlatformAppBrowserTest::TearDown();
   };
 
+  base::FilePath GetDriveMountPoint() {
+    if (base::FeatureList::IsEnabled(chromeos::features::kDriveFs)) {
+      return drivefs_mount_point_;
+    } else {
+      return drive::util::GetDriveMountPointPath(browser()->profile());
+    }
+  }
+
  private:
   drive::DriveIntegrationService* CreateDriveIntegrationService(
       Profile* profile) {
-    // Ignore signin profile.
+    // Ignore signin and lock screen apps profile.
     if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
         profile->GetPath() ==
             chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
@@ -167,15 +183,22 @@
     DCHECK(!fake_drive_service_);
     fake_drive_service_ = new drive::FakeDriveService;
 
+    CHECK(drivefs_root_.CreateUniqueTempDir());
+    drivefs_mount_point_ = drivefs_root_.GetPath().Append("drive-user");
+    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
+        profile, drivefs_mount_point_);
+
     SetUpTestFileHierarchy();
 
     integration_service_ = new drive::DriveIntegrationService(
-        profile, nullptr, fake_drive_service_, std::string(),
-        test_cache_root_.GetPath(), nullptr);
+        profile, nullptr, fake_drive_service_, "", test_cache_root_.GetPath(),
+        nullptr,
+        fake_drivefs_helper_->CreateFakeDriveFsConnectionDelegateFactory());
     return integration_service_;
   }
 
   void SetUpTestFileHierarchy() {
+    CHECK(base::CreateDirectory(drivefs_mount_point_.Append("root")));
     const std::string root = fake_drive_service_->GetRootResourceId();
     AddTestFile("open_existing.txt", "Can you see me?", root);
     AddTestFile("open_existing1.txt", "Can you see me?", root);
@@ -192,6 +215,11 @@
                    const std::string& parent_id) {
     fake_drive_service_->AddNewFile("text/plain", data, parent_id, title, false,
                                     base::Bind(&IgnoreDriveEntryResult));
+    base::FilePath parent_path = drivefs_mount_point_.Append("root");
+    if (parent_id == "subdir_resource_id") {
+      parent_path = parent_path.Append("subdir");
+    }
+    CHECK(base::WriteFile(parent_path.Append(title), data.data(), data.size()));
   }
 
   void AddTestDirectory(const std::string& resource_id,
@@ -200,10 +228,15 @@
     fake_drive_service_->AddNewDirectoryWithResourceId(
         resource_id, parent_id, title, drive::AddNewDirectoryOptions(),
         base::Bind(&IgnoreDriveEntryResult));
+    CHECK(base::CreateDirectory(
+        drivefs_mount_point_.Append("root").Append(title)));
   }
 
   base::ScopedTempDir test_cache_root_;
+  base::ScopedTempDir drivefs_root_;
+  base::FilePath drivefs_mount_point_;
   drive::FakeDriveService* fake_drive_service_ = nullptr;
+  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
   drive::DriveIntegrationService* integration_service_ = nullptr;
   drive::DriveIntegrationServiceFactory::FactoryCallback
       create_drive_integration_service_;
@@ -216,12 +249,30 @@
  public:
   FileSystemApiTestForRequestFileSystem() : fake_user_manager_(nullptr) {}
 
+  bool SetUpUserDataDirectory() override {
+    return drive::SetUpUserDataDirectoryForDriveFsTest();
+  }
+
   void SetUpCommandLine(base::CommandLine* command_line) override {
     PlatformAppBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(
         extensions::switches::kWhitelistedExtensionID, kTestingExtensionId);
   }
 
+  // Sets up fake Drive service for tests (this has to be injected before the
+  // real DriveIntegrationService instance is created.)
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformAppBrowserTest::SetUpInProcessBrowserTestFixture();
+    extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
+
+    create_drive_integration_service_ = base::BindRepeating(
+        &FileSystemApiTestForRequestFileSystem::CreateDriveIntegrationService,
+        base::Unretained(this));
+    service_factory_for_test_.reset(
+        new drive::DriveIntegrationServiceFactory::ScopedFactoryForTest(
+            &create_drive_integration_service_));
+  }
+
   void SetUpOnMainThread() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     CreateTestingFileSystem(kWritableMountPointName,
@@ -289,12 +340,38 @@
     fake_user_manager_->AddKioskAppUser(kiosk_app_account_id);
     fake_user_manager_->LoginUser(kiosk_app_account_id);
   }
+
+ private:
+  drive::DriveIntegrationService* CreateDriveIntegrationService(
+      Profile* profile) {
+    // Ignore signin and lock screen apps profile.
+    if (profile->GetPath() == chromeos::ProfileHelper::GetSigninProfileDir() ||
+        profile->GetPath() ==
+            chromeos::ProfileHelper::GetLockScreenAppProfilePath()) {
+      return nullptr;
+    }
+
+    CHECK(drivefs_root_.CreateUniqueTempDir());
+    fake_drivefs_helper_ = std::make_unique<drive::FakeDriveFsHelper>(
+        profile, drivefs_root_.GetPath().Append("drive-user"));
+
+    return new drive::DriveIntegrationService(
+        profile, nullptr, nullptr, "", {}, nullptr,
+        fake_drivefs_helper_->CreateFakeDriveFsConnectionDelegateFactory());
+  }
+
+  base::ScopedTempDir drivefs_root_;
+  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
+  drive::DriveIntegrationServiceFactory::FactoryCallback
+      create_drive_integration_service_;
+  std::unique_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>
+      service_factory_for_test_;
 };
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenExistingFileTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/open_existing.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/open_existing.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/open_existing"))
@@ -303,8 +380,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenExistingFileWithWriteTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/open_existing.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/open_existing.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest(
@@ -313,8 +390,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenMultipleSuggested) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/open_existing.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/open_existing.txt");
   ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
       chrome::DIR_USER_DOCUMENTS, test_file.DirName(), true, false));
   FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest();
@@ -325,10 +402,10 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenMultipleExistingFilesTest) {
-  base::FilePath test_file1 = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/open_existing1.txt");
-  base::FilePath test_file2 = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/open_existing2.txt");
+  base::FilePath test_file1 =
+      GetDriveMountPoint().AppendASCII("root/open_existing1.txt");
+  base::FilePath test_file2 =
+      GetDriveMountPoint().AppendASCII("root/open_existing2.txt");
   std::vector<base::FilePath> test_files;
   test_files.push_back(test_file1);
   test_files.push_back(test_file2);
@@ -341,8 +418,7 @@
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenDirectoryTest) {
   base::FilePath test_directory =
-      drive::util::GetDriveMountPointPath(browser()->profile()).AppendASCII(
-          "root/subdir");
+      GetDriveMountPoint().AppendASCII("root/subdir");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_directory);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/open_directory"))
@@ -360,8 +436,7 @@
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        MAYBE_FileSystemApiOpenDirectoryWithWriteTest) {
   base::FilePath test_directory =
-      drive::util::GetDriveMountPointPath(browser()->profile()).AppendASCII(
-          "root/subdir");
+      GetDriveMountPoint().AppendASCII("root/subdir");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_directory);
   ASSERT_TRUE(
@@ -372,8 +447,7 @@
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenDirectoryWithoutPermissionTest) {
   base::FilePath test_directory =
-      drive::util::GetDriveMountPointPath(browser()->profile()).AppendASCII(
-          "root/subdir");
+      GetDriveMountPoint().AppendASCII("root/subdir");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_directory);
   ASSERT_TRUE(RunPlatformAppTest(
@@ -384,8 +458,7 @@
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiOpenDirectoryWithOnlyWritePermissionTest) {
   base::FilePath test_directory =
-      drive::util::GetDriveMountPointPath(browser()->profile()).AppendASCII(
-          "root/subdir");
+      GetDriveMountPoint().AppendASCII("root/subdir");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_directory);
   ASSERT_TRUE(RunPlatformAppTest(
@@ -395,8 +468,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiSaveNewFileTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/save_new.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/save_new.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/save_new"))
@@ -405,8 +478,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
                        FileSystemApiSaveExistingFileTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/save_existing.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/save_existing.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/save_existing"))
@@ -415,8 +488,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
     FileSystemApiSaveNewFileWithWriteTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/save_new.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/save_new.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/save_new_with_write"))
@@ -425,8 +498,8 @@
 
 IN_PROC_BROWSER_TEST_F(FileSystemApiTestForDrive,
     FileSystemApiSaveExistingFileWithWriteTest) {
-  base::FilePath test_file = drive::util::GetDriveMountPointPath(
-      browser()->profile()).AppendASCII("root/save_existing.txt");
+  base::FilePath test_file =
+      GetDriveMountPoint().AppendASCII("root/save_existing.txt");
   FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
       &test_file);
   ASSERT_TRUE(RunPlatformAppTest(
diff --git a/chrome/browser/extensions/api/storage/managed_value_store_cache.cc b/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
index 420ffe2..ba3dfb2c 100644
--- a/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
+++ b/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
@@ -235,15 +235,15 @@
 
 ManagedValueStoreCache::ManagedValueStoreCache(
     BrowserContext* context,
-    const scoped_refptr<ValueStoreFactory>& factory,
-    const scoped_refptr<SettingsObserverList>& observers)
+    scoped_refptr<ValueStoreFactory> factory,
+    scoped_refptr<SettingsObserverList> observers)
     : profile_(Profile::FromBrowserContext(context)),
       policy_domain_(GetPolicyDomain(profile_)),
       policy_service_(
           policy::ProfilePolicyConnectorFactory::GetForBrowserContext(context)
               ->policy_service()),
-      storage_factory_(factory),
-      observers_(observers) {
+      storage_factory_(std::move(factory)),
+      observers_(std::move(observers)) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   policy_service_->AddObserver(policy_domain_, this);
diff --git a/chrome/browser/extensions/api/storage/managed_value_store_cache.h b/chrome/browser/extensions/api/storage/managed_value_store_cache.h
index ef4c7b32..954b34b 100644
--- a/chrome/browser/extensions/api/storage/managed_value_store_cache.h
+++ b/chrome/browser/extensions/api/storage/managed_value_store_cache.h
@@ -44,8 +44,8 @@
   // |observers| is the list of SettingsObservers to notify when a ValueStore
   // changes.
   ManagedValueStoreCache(content::BrowserContext* context,
-                         const scoped_refptr<ValueStoreFactory>& factory,
-                         const scoped_refptr<SettingsObserverList>& observers);
+                         scoped_refptr<ValueStoreFactory> factory,
+                         scoped_refptr<SettingsObserverList> observers);
   ~ManagedValueStoreCache() override;
 
  private:
diff --git a/chrome/browser/extensions/api/storage/policy_value_store.cc b/chrome/browser/extensions/api/storage/policy_value_store.cc
index a431343..51b4e2f 100644
--- a/chrome/browser/extensions/api/storage/policy_value_store.cc
+++ b/chrome/browser/extensions/api/storage/policy_value_store.cc
@@ -27,10 +27,10 @@
 
 PolicyValueStore::PolicyValueStore(
     const std::string& extension_id,
-    const scoped_refptr<SettingsObserverList>& observers,
+    scoped_refptr<SettingsObserverList> observers,
     std::unique_ptr<ValueStore> delegate)
     : extension_id_(extension_id),
-      observers_(observers),
+      observers_(std::move(observers)),
       delegate_(std::move(delegate)) {}
 
 PolicyValueStore::~PolicyValueStore() {}
diff --git a/chrome/browser/extensions/api/storage/policy_value_store.h b/chrome/browser/extensions/api/storage/policy_value_store.h
index 69e22a47..da8da845e5 100644
--- a/chrome/browser/extensions/api/storage/policy_value_store.h
+++ b/chrome/browser/extensions/api/storage/policy_value_store.h
@@ -31,7 +31,7 @@
 class PolicyValueStore : public ValueStore {
  public:
   PolicyValueStore(const std::string& extension_id,
-                   const scoped_refptr<SettingsObserverList>& observers,
+                   scoped_refptr<SettingsObserverList> observers,
                    std::unique_ptr<ValueStore> delegate);
   ~PolicyValueStore() override;
 
diff --git a/chrome/browser/extensions/api/storage/sync_storage_backend.cc b/chrome/browser/extensions/api/storage/sync_storage_backend.cc
index 477b091e..57c384a5 100644
--- a/chrome/browser/extensions/api/storage/sync_storage_backend.cc
+++ b/chrome/browser/extensions/api/storage/sync_storage_backend.cc
@@ -48,14 +48,14 @@
 }  // namespace
 
 SyncStorageBackend::SyncStorageBackend(
-    const scoped_refptr<ValueStoreFactory>& storage_factory,
+    scoped_refptr<ValueStoreFactory> storage_factory,
     const SettingsStorageQuotaEnforcer::Limits& quota,
-    const scoped_refptr<SettingsObserverList>& observers,
+    scoped_refptr<SettingsObserverList> observers,
     syncer::ModelType sync_type,
     const syncer::SyncableService::StartSyncFlare& flare)
-    : storage_factory_(storage_factory),
+    : storage_factory_(std::move(storage_factory)),
       quota_(quota),
-      observers_(observers),
+      observers_(std::move(observers)),
       sync_type_(sync_type),
       flare_(flare) {
   DCHECK(IsOnBackendSequence());
diff --git a/chrome/browser/extensions/api/storage/sync_storage_backend.h b/chrome/browser/extensions/api/storage/sync_storage_backend.h
index 6eeaadb..5147da38 100644
--- a/chrome/browser/extensions/api/storage/sync_storage_backend.h
+++ b/chrome/browser/extensions/api/storage/sync_storage_backend.h
@@ -36,9 +36,9 @@
  public:
   // |storage_factory| is use to create leveldb storage areas.
   // |observers| is the list of observers to settings changes.
-  SyncStorageBackend(const scoped_refptr<ValueStoreFactory>& storage_factory,
+  SyncStorageBackend(scoped_refptr<ValueStoreFactory> storage_factory,
                      const SettingsStorageQuotaEnforcer::Limits& quota,
-                     const scoped_refptr<SettingsObserverList>& observers,
+                     scoped_refptr<SettingsObserverList> observers,
                      syncer::ModelType sync_type,
                      const syncer::SyncableService::StartSyncFlare& flare);
 
diff --git a/chrome/browser/extensions/api/storage/sync_value_store_cache.cc b/chrome/browser/extensions/api/storage/sync_value_store_cache.cc
index e3c5d31..a02cad4 100644
--- a/chrome/browser/extensions/api/storage/sync_value_store_cache.cc
+++ b/chrome/browser/extensions/api/storage/sync_value_store_cache.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <utility>
+
 #include "chrome/browser/extensions/api/storage/sync_storage_backend.h"
 #include "chrome/browser/sync/glue/sync_start_util.h"
 #include "content/public/browser/browser_thread.h"
@@ -33,8 +35,8 @@
 }  // namespace
 
 SyncValueStoreCache::SyncValueStoreCache(
-    const scoped_refptr<ValueStoreFactory>& factory,
-    const scoped_refptr<SettingsObserverList>& observers,
+    scoped_refptr<ValueStoreFactory> factory,
+    scoped_refptr<SettingsObserverList> observers,
     const base::FilePath& profile_path)
     : initialized_(false) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -43,9 +45,9 @@
   // same message loop, and any potential post of a deletion task must come
   // after the constructor returns.
   GetBackendTaskRunner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&SyncValueStoreCache::InitOnBackend,
-                     base::Unretained(this), factory, observers, profile_path));
+      FROM_HERE, base::BindOnce(&SyncValueStoreCache::InitOnBackend,
+                                base::Unretained(this), std::move(factory),
+                                std::move(observers), profile_path));
 }
 
 SyncValueStoreCache::~SyncValueStoreCache() {
@@ -85,23 +87,18 @@
 }
 
 void SyncValueStoreCache::InitOnBackend(
-    const scoped_refptr<ValueStoreFactory>& factory,
-    const scoped_refptr<SettingsObserverList>& observers,
+    scoped_refptr<ValueStoreFactory> factory,
+    scoped_refptr<SettingsObserverList> observers,
     const base::FilePath& profile_path) {
   DCHECK(IsOnBackendSequence());
   DCHECK(!initialized_);
-  app_backend_.reset(new SyncStorageBackend(
-      factory,
-      GetSyncQuotaLimits(),
-      observers,
-      syncer::APP_SETTINGS,
-      sync_start_util::GetFlareForSyncableService(profile_path)));
-  extension_backend_.reset(new SyncStorageBackend(
-      factory,
-      GetSyncQuotaLimits(),
-      observers,
+  app_backend_ = std::make_unique<SyncStorageBackend>(
+      factory, GetSyncQuotaLimits(), observers, syncer::APP_SETTINGS,
+      sync_start_util::GetFlareForSyncableService(profile_path));
+  extension_backend_ = std::make_unique<SyncStorageBackend>(
+      std::move(factory), GetSyncQuotaLimits(), std::move(observers),
       syncer::EXTENSION_SETTINGS,
-      sync_start_util::GetFlareForSyncableService(profile_path)));
+      sync_start_util::GetFlareForSyncableService(profile_path));
   initialized_ = true;
 }
 
diff --git a/chrome/browser/extensions/api/storage/sync_value_store_cache.h b/chrome/browser/extensions/api/storage/sync_value_store_cache.h
index fb0543b..ac65c995 100644
--- a/chrome/browser/extensions/api/storage/sync_value_store_cache.h
+++ b/chrome/browser/extensions/api/storage/sync_value_store_cache.h
@@ -31,8 +31,8 @@
 // another for extensions. Each backend takes care of persistence and syncing.
 class SyncValueStoreCache : public ValueStoreCache {
  public:
-  SyncValueStoreCache(const scoped_refptr<ValueStoreFactory>& factory,
-                      const scoped_refptr<SettingsObserverList>& observers,
+  SyncValueStoreCache(scoped_refptr<ValueStoreFactory> factory,
+                      scoped_refptr<SettingsObserverList> observers,
                       const base::FilePath& profile_path);
   ~SyncValueStoreCache() override;
 
@@ -45,8 +45,8 @@
   void DeleteStorageSoon(const std::string& extension_id) override;
 
  private:
-  void InitOnBackend(const scoped_refptr<ValueStoreFactory>& factory,
-                     const scoped_refptr<SettingsObserverList>& observers,
+  void InitOnBackend(scoped_refptr<ValueStoreFactory> factory,
+                     scoped_refptr<SettingsObserverList> observers,
                      const base::FilePath& profile_path);
 
   bool initialized_;
diff --git a/chrome/browser/extensions/api/storage/syncable_settings_storage.cc b/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
index 3778715..7088ea82 100644
--- a/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
+++ b/chrome/browser/extensions/api/storage/syncable_settings_storage.cc
@@ -17,13 +17,12 @@
 namespace extensions {
 
 SyncableSettingsStorage::SyncableSettingsStorage(
-    const scoped_refptr<base::ObserverListThreadSafe<SettingsObserver>>&
-        observers,
+    scoped_refptr<base::ObserverListThreadSafe<SettingsObserver>> observers,
     const std::string& extension_id,
     ValueStore* delegate,
     syncer::ModelType sync_type,
     const syncer::SyncableService::StartSyncFlare& flare)
-    : observers_(observers),
+    : observers_(std::move(observers)),
       extension_id_(extension_id),
       delegate_(delegate),
       sync_type_(sync_type),
diff --git a/chrome/browser/extensions/api/storage/syncable_settings_storage.h b/chrome/browser/extensions/api/storage/syncable_settings_storage.h
index f382dd1..a94680ab 100644
--- a/chrome/browser/extensions/api/storage/syncable_settings_storage.h
+++ b/chrome/browser/extensions/api/storage/syncable_settings_storage.h
@@ -29,13 +29,12 @@
 // Decorates a ValueStore with sync behaviour.
 class SyncableSettingsStorage : public ValueStore {
  public:
-  SyncableSettingsStorage(
-      const scoped_refptr<SettingsObserverList>& observers,
-      const std::string& extension_id,
-      // Ownership taken.
-      ValueStore* delegate,
-      syncer::ModelType sync_type,
-      const syncer::SyncableService::StartSyncFlare& flare);
+  SyncableSettingsStorage(scoped_refptr<SettingsObserverList> observers,
+                          const std::string& extension_id,
+                          // Ownership taken.
+                          ValueStore* delegate,
+                          syncer::ModelType sync_type,
+                          const syncer::SyncableService::StartSyncFlare& flare);
 
   ~SyncableSettingsStorage() override;
 
diff --git a/chrome/browser/extensions/component_extensions_whitelist/OWNERS b/chrome/browser/extensions/component_extensions_whitelist/OWNERS
index 297afad..0f3791b57 100644
--- a/chrome/browser/extensions/component_extensions_whitelist/OWNERS
+++ b/chrome/browser/extensions/component_extensions_whitelist/OWNERS
@@ -1,8 +1,8 @@
-# Adding new component extensions requires approval from
-# chrome-eng-review@google.com. See comment in whitelist.h
+# Adding new component extensions requires approval from the Extensions Tech
+# Lead. See comment in whitelist.h
 set noparent
 
-file://ENG_REVIEW_OWNERS
+rdevlin.cronin@chromium.org
 
 # TEAM: extensions-dev@chromium.org
 # COMPONENT: Platform>Extensions
diff --git a/chrome/browser/extensions/component_extensions_whitelist/whitelist.h b/chrome/browser/extensions/component_extensions_whitelist/whitelist.h
index f39be914..fd87c57 100644
--- a/chrome/browser/extensions/component_extensions_whitelist/whitelist.h
+++ b/chrome/browser/extensions/component_extensions_whitelist/whitelist.h
@@ -11,7 +11,8 @@
 
 // =============================================================================
 //
-// ADDING NEW EXTENSIONS REQUIRES APPROVAL from chrome-eng-review@google.com
+// ADDING NEW EXTENSIONS REQUIRES APPROVAL from Extensions Tech Lead:
+// rdevlin.cronin@chromium.org
 //
 // The main acceptable use of extensions in the default Chrome experience (i.e.
 // not installed explicitly by the user) are to implement things like the
diff --git a/chrome/browser/extensions/convert_web_app.cc b/chrome/browser/extensions/convert_web_app.cc
index 95080f7..ce49f80 100644
--- a/chrome/browser/extensions/convert_web_app.cc
+++ b/chrome/browser/extensions/convert_web_app.cc
@@ -142,7 +142,7 @@
   // Create the manifest
   std::unique_ptr<base::DictionaryValue> root(new base::DictionaryValue);
   root->SetString(keys::kPublicKey,
-                  web_app::GenerateExtensionKeyFromURL(web_app.app_url));
+                  web_app::GenerateAppKeyFromURL(web_app.app_url));
   root->SetString(keys::kName, base::UTF16ToUTF8(web_app.title));
   root->SetString(keys::kVersion, ConvertTimeToExtensionVersion(create_time));
   root->SetString(keys::kDescription, base::UTF16ToUTF8(web_app.description));
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ea84174..9b9d96b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -509,6 +509,12 @@
     "URLs that are in-scope of Desktop PWAs will open in a window. Requires "
     "#enable-desktop-pwas.";
 
+const char kDesktopPWAsStayInWindowName[] =
+    "Desktop PWAs out-of-scope links open in the app window";
+const char kDesktopPWAsStayInWindowDescription[] =
+    "Links to sites in a different scope will open in a custom "
+    "tab (inside the PWA window) as opposed to in the browser.";
+
 const char kEnableSystemWebAppsName[] = "System Web Apps";
 const char kEnableSystemWebAppsDescription[] =
     "Experimental system for using the Desktop PWA framework for running System"
@@ -558,6 +564,13 @@
     "Enable showing the Previews UI in the Omnibox on Android instead of an "
     "InfoBar. This has no effect on other platforms.";
 
+const char kEnableLitePageServerPreviewsName[] = "Lite Page Server Previews";
+const char kEnableLitePageServerPreviewsDescription[] =
+    "Enable showing Lite Page Previews served from the Chrome Data Proxy "
+    "service. This feature will cause Chrome to redirect eligible navigations "
+    "to a Google-owned domain that serves a pre-rendered version of the "
+    "original page.";
+
 const char kEnableHttpFormWarningName[] =
     "Show in-form warnings for sensitive fields when the top-level page is not "
     "HTTPS";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 29b7251d..ed33e60 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -338,6 +338,9 @@
 extern const char kEnableDesktopPWAsLinkCapturingName[];
 extern const char kEnableDesktopPWAsLinkCapturingDescription[];
 
+extern const char kDesktopPWAsStayInWindowName[];
+extern const char kDesktopPWAsStayInWindowDescription[];
+
 extern const char kEnableSystemWebAppsName[];
 extern const char kEnableSystemWebAppsDescription[];
 
@@ -365,6 +368,9 @@
 extern const char kEnablePreviewsAndroidOmniboxUIName[];
 extern const char kEnablePreviewsAndroidOmniboxUIDescription[];
 
+extern const char kEnableLitePageServerPreviewsName[];
+extern const char kEnableLitePageServerPreviewsDescription[];
+
 extern const char kEnableHttpFormWarningName[];
 extern const char kEnableHttpFormWarningDescription[];
 
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc
index b0f9e9fc..d26d6655 100644
--- a/chrome/browser/media/encrypted_media_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_browsertest.cc
@@ -556,6 +556,13 @@
 }
 
 IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VP9Profile2Video_WebM) {
+#if BUILDFLAG(ENABLE_WIDEVINE)
+  // TODO(crbug.com/707128): Update Widevine CDM to support VP9 profile 1/2/3.
+  if (IsWidevine(CurrentKeySystem())) {
+    DVLOG(0) << "Skipping test - Widevine CDM does not support VP9 profile 2";
+    return;
+  }
+#endif
   // TODO(crbug.com/707127): Support VP9 Profile2 query and update mime type.
   TestSimplePlayback("bear-320x240-v-vp9_profile2_subsample_cenc-v.webm",
                      kWebMVp9VideoOnly);
@@ -595,6 +602,13 @@
     DVLOG(0) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
     return;
   }
+#if BUILDFLAG(ENABLE_WIDEVINE)
+  // TODO(crbug.com/707128): Update Widevine CDM to support VP9 profile 1/2/3.
+  if (IsWidevine(CurrentKeySystem())) {
+    DVLOG(0) << "Skipping test - Widevine CDM does not support VP9 profile 2";
+    return;
+  }
+#endif
   // TODO(crbug.com/707127): Support VP9 Profile2 query and update mime type.
   TestSimplePlayback("bear-320x240-v-vp9_profile2_subsample_cenc-v.mp4",
                      kMp4Vp9VideoOnly);
diff --git a/chrome/browser/media/webrtc/webrtc_log_uploader.cc b/chrome/browser/media/webrtc/webrtc_log_uploader.cc
index 3bf8c7a..f3b5ef01 100644
--- a/chrome/browser/media/webrtc/webrtc_log_uploader.cc
+++ b/chrome/browser/media/webrtc/webrtc_log_uploader.cc
@@ -24,6 +24,7 @@
 #include "components/webrtc_logging/common/partial_circular_buffer.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "net/base/load_flags.h"
 #include "net/base/mime_util.h"
 #include "net/http/http_status_code.h"
@@ -31,6 +32,7 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/zlib/zlib.h"
 
 using content::BrowserThread;
@@ -327,6 +329,33 @@
   NotifyUploadDone(response_code, report_id, upload_done_data);
 }
 
+void WebRtcLogUploader::InitURLLoaderFactoryIfNeeded() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(!shutting_down_);
+
+  if (!url_loader_factory_.is_bound())
+    return;
+
+  // Clone UI thread URLLoaderFactory for use on the IO thread.
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(
+          [](network::mojom::URLLoaderFactoryRequest loader_factory_request) {
+            g_browser_process->shared_url_loader_factory()->Clone(
+                std::move(loader_factory_request));
+          },
+          mojo::MakeRequest(&url_loader_factory_)));
+  // Need to set an error handler so that the class will monitor the state of
+  // the Mojo pipe. Without this, an errored out pipe would only be noticed
+  // after the URLLoaderFactory is used, and a request failed as a result.
+  url_loader_factory_.set_connection_error_handler(base::BindOnce(
+      &WebRtcLogUploader::OnFactoryConnectionClosed, base::Unretained(this)));
+}
+
+void WebRtcLogUploader::OnFactoryConnectionClosed() {
+  url_loader_factory_.reset();
+}
+
 void WebRtcLogUploader::SetupMultipart(
     std::string* post_data,
     const std::string& compressed_log,
@@ -488,10 +517,9 @@
   auto it = pending_uploads_.insert(pending_uploads_.begin(),
                                     std::move(simple_url_loader));
   network::SimpleURLLoader* raw_loader = it->get();
+  InitURLLoaderFactoryIfNeeded();
   raw_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-      g_browser_process->system_network_context_manager()
-          ->GetSharedURLLoaderFactory()
-          .get(),
+      url_loader_factory_.get(),
       base::BindOnce(&WebRtcLogUploader::OnSimpleLoaderComplete,
                      base::Unretained(this), std::move(it), upload_done_data));
 }
@@ -507,6 +535,7 @@
 
   // Clear the pending uploads list, which will reset all URL loaders.
   pending_uploads_.clear();
+  url_loader_factory_.reset();
   shutting_down_ = true;
 }
 
diff --git a/chrome/browser/media/webrtc/webrtc_log_uploader.h b/chrome/browser/media/webrtc/webrtc_log_uploader.h
index 81841b8..43453a9 100644
--- a/chrome/browser/media/webrtc/webrtc_log_uploader.h
+++ b/chrome/browser/media/webrtc/webrtc_log_uploader.h
@@ -16,6 +16,7 @@
 #include "base/sequenced_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/media/webrtc/webrtc_logging_handler_host.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 
 namespace network {
 class SimpleURLLoader;
@@ -104,6 +105,10 @@
   FRIEND_TEST_ALL_PREFIXES(WebRtcLogUploaderTest,
                            AddUploadedLogInfoToUploadListFile);
 
+  void InitURLLoaderFactoryIfNeeded();
+
+  void OnFactoryConnectionClosed();
+
   // Sets up a multipart body to be uploaded. The body is produced according
   // to RFC 2046.
   void SetupMultipart(std::string* post_data,
@@ -188,6 +193,9 @@
   // When shutting down, don't create new URL loaders.
   bool shutting_down_;
 
+  // URLLoaderFactory bound to the IO thread.
+  network::mojom::URLLoaderFactoryPtr url_loader_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(WebRtcLogUploader);
 };
 
diff --git a/chrome/browser/media_galleries/gallery_watch_manager_unittest.cc b/chrome/browser/media_galleries/gallery_watch_manager_unittest.cc
index 5169442..d7d347ff 100644
--- a/chrome/browser/media_galleries/gallery_watch_manager_unittest.cc
+++ b/chrome/browser/media_galleries/gallery_watch_manager_unittest.cc
@@ -20,13 +20,13 @@
 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
 #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
 #include "chrome/browser/media_galleries/media_galleries_test_util.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/storage_monitor/test_storage_monitor.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_CHROMEOS)
@@ -87,7 +87,7 @@
 
     std::vector<std::string> read_permissions;
     read_permissions.push_back(
-        extensions::MediaGalleriesPermission::kReadPermission);
+        chrome_apps::MediaGalleriesPermission::kReadPermission);
     extension_ = AddMediaGalleriesApp("read", read_permissions, profile_.get());
 
     manager_.reset(new GalleryWatchManager);
diff --git a/chrome/browser/media_galleries/media_galleries_permission_controller.cc b/chrome/browser/media_galleries/media_galleries_permission_controller.cc
index 8a860a35..2bd7d4651 100644
--- a/chrome/browser/media_galleries/media_galleries_permission_controller.cc
+++ b/chrome/browser/media_galleries/media_galleries_permission_controller.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/media_galleries/media_gallery_context_menu.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/chrome_select_file_policy.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/storage_monitor/storage_info.h"
 #include "components/storage_monitor/storage_monitor.h"
@@ -22,7 +23,6 @@
 #include "extensions/browser/api/file_system/file_system_api.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -125,10 +125,10 @@
 }
 
 base::string16 MediaGalleriesPermissionController::GetSubtext() const {
-  extensions::MediaGalleriesPermission::CheckParam copy_to_param(
-      extensions::MediaGalleriesPermission::kCopyToPermission);
-  extensions::MediaGalleriesPermission::CheckParam delete_param(
-      extensions::MediaGalleriesPermission::kDeletePermission);
+  chrome_apps::MediaGalleriesPermission::CheckParam copy_to_param(
+      chrome_apps::MediaGalleriesPermission::kCopyToPermission);
+  chrome_apps::MediaGalleriesPermission::CheckParam delete_param(
+      chrome_apps::MediaGalleriesPermission::kDeletePermission);
   const extensions::PermissionsData* permission_data =
       extension_->permissions_data();
   bool has_copy_to_permission = permission_data->CheckAPIPermissionWithParam(
diff --git a/chrome/browser/media_galleries/media_galleries_permission_controller_unittest.cc b/chrome/browser/media_galleries/media_galleries_permission_controller_unittest.cc
index decf180..06c1933 100644
--- a/chrome/browser/media_galleries/media_galleries_permission_controller_unittest.cc
+++ b/chrome/browser/media_galleries/media_galleries_permission_controller_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/media_galleries/media_galleries_permission_controller.h"
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
@@ -13,14 +15,13 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/media_galleries/media_galleries_dialog_controller_test_util.h"
-#include "chrome/browser/media_galleries/media_galleries_permission_controller.h"
 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
 #include "chrome/browser/media_galleries/media_galleries_test_util.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/storage_monitor/storage_info.h"
 #include "components/storage_monitor/test_storage_monitor.h"
 #include "content/public/test/test_browser_thread_bundle.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_CHROMEOS)
@@ -71,7 +72,7 @@
 
     std::vector<std::string> read_permissions;
     read_permissions.push_back(
-        extensions::MediaGalleriesPermission::kReadPermission);
+        chrome_apps::MediaGalleriesPermission::kReadPermission);
     extension_ = AddMediaGalleriesApp("read", read_permissions, profile_.get());
   }
 
diff --git a/chrome/browser/media_galleries/media_galleries_preferences.cc b/chrome/browser/media_galleries/media_galleries_preferences.cc
index a0bcf8d5..80f1b9c 100644
--- a/chrome/browser/media_galleries/media_galleries_preferences.cc
+++ b/chrome/browser/media_galleries/media_galleries_preferences.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/media_galleries/media_file_system_registry.h"
 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
@@ -42,7 +43,6 @@
 #include "extensions/browser/pref_names.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/permissions/api_permission.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -326,8 +326,8 @@
 }
 
 bool HasAutoDetectedGalleryPermission(const extensions::Extension& extension) {
-  extensions::MediaGalleriesPermission::CheckParam param(
-      extensions::MediaGalleriesPermission::kAllAutoDetectedPermission);
+  chrome_apps::MediaGalleriesPermission::CheckParam param(
+      chrome_apps::MediaGalleriesPermission::kAllAutoDetectedPermission);
   return extension.permissions_data()->CheckAPIPermissionWithParam(
       extensions::APIPermission::kMediaGalleries, &param);
 }
diff --git a/chrome/browser/media_galleries/media_galleries_preferences_unittest.cc b/chrome/browser/media_galleries/media_galleries_preferences_unittest.cc
index 8acf0ac4..194e7e88 100644
--- a/chrome/browser/media_galleries/media_galleries_preferences_unittest.cc
+++ b/chrome/browser/media_galleries/media_galleries_preferences_unittest.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/media_galleries/media_file_system_registry.h"
 #include "chrome/browser/media_galleries/media_galleries_test_util.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
@@ -37,7 +38,6 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/background_info.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -137,12 +137,12 @@
 
     std::vector<std::string> all_permissions;
     all_permissions.push_back(
-        extensions::MediaGalleriesPermission::kReadPermission);
+        chrome_apps::MediaGalleriesPermission::kReadPermission);
     all_permissions.push_back(
-        extensions::MediaGalleriesPermission::kAllAutoDetectedPermission);
+        chrome_apps::MediaGalleriesPermission::kAllAutoDetectedPermission);
     std::vector<std::string> read_permissions;
     read_permissions.push_back(
-        extensions::MediaGalleriesPermission::kReadPermission);
+        chrome_apps::MediaGalleriesPermission::kReadPermission);
 
     all_permission_extension =
         AddMediaGalleriesApp("all", all_permissions, profile_.get());
diff --git a/chrome/browser/net/spdyproxy/data_reduction_proxy_browsertest.cc b/chrome/browser/net/spdyproxy/data_reduction_proxy_browsertest.cc
index 10cef1e..3865f87 100644
--- a/chrome/browser/net/spdyproxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/net/spdyproxy/data_reduction_proxy_browsertest.cc
@@ -3,12 +3,15 @@
 // found in the LICENSE file.
 
 #include "base/strings/strcat.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_test_utils.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
 #include "components/prefs/pref_service.h"
@@ -63,6 +66,10 @@
   }
 
   void SetUpOnMainThread() override {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kDataReductionProxyRobustConnection,
+        {{params::GetMissingViaBypassParamName(), "true"},
+         {params::GetWarmupCallbackParamName(), "true"}});
     host_resolver()->AddRule(kMockHost, "127.0.0.1");
     EnableDataSaver(true);
   }
@@ -92,6 +99,9 @@
     EXPECT_TRUE(base_url.is_valid()) << base_url.possibly_invalid_spec();
     return base_url.Resolve(relative_url);
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(DataReductionProxyBrowsertest, ChromeProxyHeaderSet) {
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index 8ebbac6..812c850 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -324,8 +324,14 @@
 
 // Tests that when an active WebContents accurately tracks whether a video
 // is in Picture-in-Picture.
+// Flaky failures on ChromeOS - http://crbug.com/892310
+#if defined(OS_CHROMEOS)
+#define MAYBE_TabIconUpdated DISABLED_TabIconUpdated
+#else
+#define MAYBE_TabIconUpdated TabIconUpdated
+#endif
 IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
-                       TabIconUpdated) {
+                       MAYBE_TabIconUpdated) {
   GURL test_page_url = ui_test_utils::GetTestUrl(
       base::FilePath(base::FilePath::kCurrentDirectory),
       base::FilePath(
diff --git a/chrome/browser/platform_util_mac.mm b/chrome/browser/platform_util_mac.mm
index c560537..a4ad8f3 100644
--- a/chrome/browser/platform_util_mac.mm
+++ b/chrome/browser/platform_util_mac.mm
@@ -17,6 +17,7 @@
 #include "chrome/browser/platform_util_internal.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "ui/views/widget/widget.h"
 #include "url/gurl.h"
 
 namespace platform_util {
@@ -97,14 +98,29 @@
 }
 
 bool IsWindowActive(gfx::NativeWindow window) {
+  // If |window| is a doppelganger NSWindow being used to track an NSWindow that
+  // is being hosted in another process, then use the views::Widget interface to
+  // interact with it.
+  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+  if (widget)
+    return widget->IsActive();
+
   return [window isKeyWindow] || [window isMainWindow];
 }
 
 void ActivateWindow(gfx::NativeWindow window) {
+  views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+  if (widget)
+    return widget->Activate();
+
   [window makeKeyAndOrderFront:nil];
 }
 
 bool IsVisible(gfx::NativeView view) {
+  views::Widget* widget = views::Widget::GetWidgetForNativeView(view);
+  if (widget)
+    return widget->IsVisible();
+
   // A reasonable approximation of how you'd expect this to behave.
   return (view &&
           ![view isHiddenOrHasHiddenAncestor] &&
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 04b7edb..e9acffd8 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -586,6 +586,27 @@
     histogram_tester.ExpectBucketCount("Previews.ServerLitePage.Triggered",
                                        false, 2);
   }
+
+  {
+    // Verify a preview is only shown on slow networks.
+    base::HistogramTester histogram_tester;
+    g_browser_process->network_quality_tracker()
+        ->ReportEffectiveConnectionTypeForTesting(
+            net::EFFECTIVE_CONNECTION_TYPE_3G);
+
+    ui_test_utils::NavigateToURL(browser(), HttpLitePageURL(200));
+
+    VerifyPreviewNotLoaded();
+    histogram_tester.ExpectBucketCount(
+        "Previews.ServerLitePage.IneligibleReasons",
+        PreviewsLitePageNavigationThrottle::IneligibleReason::kNetworkNotSlow,
+        1);
+
+    // Reset ECT for future tests.
+    g_browser_process->network_quality_tracker()
+        ->ReportEffectiveConnectionTypeForTesting(
+            net::EFFECTIVE_CONNECTION_TYPE_2G);
+  }
 }
 
 // Previews InfoBar (which these tests trigger) does not work on Mac.
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
index 81ee2c8..43f955c 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
@@ -181,6 +181,13 @@
   if (manager_->IsServerUnavailable())
     ineligible_reasons.push_back(IneligibleReason::kServerUnavailable);
 
+  if (g_browser_process->network_quality_tracker()
+          ->GetEffectiveConnectionType() >
+      previews::params::GetECTThresholdForPreview(
+          previews::PreviewsType::LITE_PAGE_REDIRECT)) {
+    ineligible_reasons.push_back(IneligibleReason::kNetworkNotSlow);
+  }
+
   // Record UMA.
   for (IneligibleReason reason : ineligible_reasons) {
     UMA_HISTOGRAM_ENUMERATION("Previews.ServerLitePage.IneligibleReasons",
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle.h b/chrome/browser/previews/previews_lite_page_navigation_throttle.h
index 7162747..df1c3c27 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle.h
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle.h
@@ -38,7 +38,8 @@
     kSubframeNavigation = 2,
     kServerUnavailable = 3,
     kInfoBarNotSeen = 4,
-    kMaxValue = kInfoBarNotSeen,
+    kNetworkNotSlow = 5,
+    kMaxValue = kNetworkNotSlow,
   };
 
   // The response type from the previews server. This enum must
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_get_more.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_get_more.js
index d41584db..ab39fe7 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_get_more.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_get_more.js
@@ -52,6 +52,10 @@
    * @private
    */
   onNextTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     var hotword = this.$$('#toggle0').hasAttribute('checked');
     var screenContext = this.$$('#toggle1').hasAttribute('checked');
     var toggle2 = this.$$('#toggle2');
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
index 5b539bc..c9e0479 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
@@ -60,6 +60,10 @@
    * @private
    */
   onSkipTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     chrome.send('login.AssistantOptInFlowScreen.flowFinished');
   },
 
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_ready.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_ready.js
index c8717359..c38ff2a 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_ready.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_ready.js
@@ -19,6 +19,10 @@
    * @private
    */
   onNextTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     chrome.send(
         'login.AssistantOptInFlowScreen.ReadyScreen.userActed',
         ['next-pressed']);
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_third_party.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_third_party.js
index 9717515..9a9aa00e 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_third_party.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_third_party.js
@@ -59,6 +59,10 @@
    * @private
    */
   onNextTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     chrome.send(
         'login.AssistantOptInFlowScreen.ThirdPartyScreen.userActed',
         ['next-pressed']);
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
index 4349623..dd9362d 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
@@ -118,10 +118,13 @@
    * @private
    */
   onSkipTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     chrome.send(
         'login.AssistantOptInFlowScreen.ValuePropScreen.userActed',
         ['skip-pressed']);
-    this.buttonsDisabled = true;
   },
 
   /**
@@ -130,10 +133,13 @@
    * @private
    */
   onNextTap_: function() {
+    if (this.buttonsDisabled) {
+      return;
+    }
+    this.buttonsDisabled = true;
     chrome.send(
         'login.AssistantOptInFlowScreen.ValuePropScreen.userActed',
         ['next-pressed']);
-    this.buttonsDisabled = true;
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index 4b8f1c5..635e21c 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -1764,12 +1764,7 @@
           .expectSpeech('tab2')
           .clearPendingOutput()
           .call(press(82  /* R */, {ctrl: true}))
-
-          // ChromeVox stays on the same node due to tree path recovery.
-          .call(() => {
-            assertEquals('tab2',
-                         ChromeVoxState.instance.currentRange.start.node.name);
-          })
+          .expectSpeech('tab2')
           .replay();
     });
   });
@@ -1789,31 +1784,3 @@
         .replay();
   });
 });
-
-TEST_F('BackgroundTest', 'ReinsertedNodeRecovery', function() {
-  var mockFeedback = this.createMockFeedback();
-  this.runWithLoadedTree(function(root) {/*
-    <div>
-      <button id="start">start</button>
-      <button id="hot">hot</button>
-    </div>
-    <button id="end">end</button>
-    <script>
-      var div =       document.body.firstElementChild;
-      var start =       document.getElementById('start');
-      document.getElementById('hot').addEventListener('focus', (evt) => {
-        var hot = evt.target;
-        hot.remove();
-        div.insertAfter(hot, start);
-      });
-    </script>
-  */}, function(root) {
-    mockFeedback.expectSpeech('start')
-        .clearPendingOutput()
-        .call(doCmd('nextObject'))
-        .call(doCmd('nextObject'))
-        .call(doCmd('nextObject'))
-        .expectSpeech('end', 'Button')
-        .replay();
-  });
-});
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
index 9c3572ce..8ddb5612 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
@@ -103,7 +103,7 @@
   /** @type {number} @private */
   this.index_ = index;
   /** @type {RecoveryStrategy} */
-  this.recovery_ = new TreePathRecoveryStrategy(node);
+  this.recovery_ = new AncestryRecoveryStrategy(node);
 };
 
 /**
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
index a676b7f..563892f 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
@@ -229,18 +229,18 @@
           .withoutHints()
           .withSpeechAndBraille(range, null, 'navigate');
 
-      checkSpeechOutput('play|Button|audio|Tool bar',
+      checkSpeechOutput('play|Disabled|Button|audio|Tool bar',
           [
             {value: new Output.EarconAction('BUTTON'), start: 0, end: 4},
-            {value: 'name', start: 12, end: 17},
-            {value: 'role', start: 18, end: 26}
+            {value: 'name', start: 21, end: 26},
+            {value: 'role', start: 27, end: 35}
           ],
           o);
 
       checkBrailleOutput(
-          'play btn audio tlbar',
-          [{value: new Output.NodeSpan(el), start: 0, end: 8},
-           {value: new Output.NodeSpan(el.parent), start: 9, end: 20}],
+          'play xx btn audio tlbar',
+          [{value: new Output.NodeSpan(el), start: 0, end: 11},
+           {value: new Output.NodeSpan(el.parent), start: 12, end: 23}],
           o);
 
       // TODO(dmazzoni/dtseng): Replace with a query.
diff --git a/chrome/browser/resources/local_discovery/chevron_left-1x.png b/chrome/browser/resources/local_discovery/chevron_left-1x.png
deleted file mode 100644
index 85d3117..0000000
--- a/chrome/browser/resources/local_discovery/chevron_left-1x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/local_discovery/chevron_left-2x.png b/chrome/browser/resources/local_discovery/chevron_left-2x.png
deleted file mode 100644
index 5dcdab95..0000000
--- a/chrome/browser/resources/local_discovery/chevron_left-2x.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/local_discovery/local_discovery.css b/chrome/browser/resources/local_discovery/local_discovery.css
index bae41d1f..bb08c2f 100644
--- a/chrome/browser/resources/local_discovery/local_discovery.css
+++ b/chrome/browser/resources/local_discovery/local_discovery.css
@@ -113,22 +113,3 @@
 .dialog-contents {
   padding-left: 17px;
 }
-
-#back-link {
-  background: -webkit-image-set(
-      url(chevron_left-1x.png) 1x,
-      url(chevron_left-2x.png) 2x)
-      no-repeat;
-  margin-bottom: 25px;
-  margin-top: 6px;
-  padding-left: 23px;
-}
-
-html[dir='rtl'] #back-link {
-  transform: scaleX(-1);
-}
-
-html[dir='rtl'] #back-link span {
-  display: inline-block;
-  transform: scaleX(-1);
-}
diff --git a/chrome/browser/resources/local_discovery/local_discovery.html b/chrome/browser/resources/local_discovery/local_discovery.html
index 6a699ba..58cdea2 100644
--- a/chrome/browser/resources/local_discovery/local_discovery.html
+++ b/chrome/browser/resources/local_discovery/local_discovery.html
@@ -75,18 +75,6 @@
           </div>
         </div>
 
-        <div id="register-device-page-adding2" class="register-page">
-          <h1>$i18n{addingDevice}</h1>
-          <div class="dialog-contents">
-            <div>$i18n{addingDeviceConfirmCodeMessage}</div>
-            <h1 id="register-device-page-code"></h1>
-            <div class="button-list">
-              <button class="register-cancel">$i18n{cancel}</button>
-              <button class="confirm-code">$i18n{confirmCode}</button>
-            </div>
-          </div>
-        </div>
-
         <div id="register-page-error" class="register-page">
           <h1>$i18n{addingError}</h1>
           <div class="dialog-contents">
@@ -100,9 +88,6 @@
     </div>
 
     <header>
-      <a is="action-link" id="back-link" hidden>
-        <span>$i18n{backButton}</span>
-      </a>
       <h1>$i18n{devicesTitle}</h1>
     </header>
 
@@ -126,9 +111,9 @@
         <section id="cloud-print-connector-section">
           <h2>$i18n{titleConnector}</h2>
           <div>
-            <p id="cloudPrintConnectorLabel" class="settings-row"></p>
+            <p id="cloudPrintConnectorLabel"></p>
 
-          <div class="settings-row">
+          <div>
             <button id="cloudPrintConnectorSetupButton"></button>
           </div>
         </div>
diff --git a/chrome/browser/resources/local_discovery/local_discovery.js b/chrome/browser/resources/local_discovery/local_discovery.js
index 0b9bc33..825f12c 100644
--- a/chrome/browser/resources/local_discovery/local_discovery.js
+++ b/chrome/browser/resources/local_discovery/local_discovery.js
@@ -292,15 +292,6 @@
   }
 
   /**
-   * Shows UI to confirm security code.
-   * @param {string} code The security code to confirm.
-   */
-  function onRegistrationConfirmDeviceCode(code) {
-    setRegisterPage('register-device-page-adding2');
-    $('register-device-page-code').textContent = code;
-  }
-
-  /**
    * Update device unregistered device list, and update related strings to
    * reflect the number of devices available to register.
    * @param {string} name Name of the device.
@@ -431,13 +422,6 @@
   }
 
   /**
-   * Update visibility status for page.
-   */
-  function updateVisibility() {
-    chrome.send('isVisible', [!document.hidden]);
-  }
-
-  /**
    * Set the page that the register wizard is on.
    * @param {string} page_id ID string for page.
    */
@@ -494,14 +478,6 @@
   }
 
   /**
-   * Confirms device code.
-   */
-  function confirmCode() {
-    chrome.send('confirmCode');
-    setRegisterPage('register-device-page-adding1');
-  }
-
-  /**
    * Retry loading the devices from Google Cloud Print.
    */
   function retryLoadCloudDevices() {
@@ -623,11 +599,6 @@
           button.addEventListener('click', cancelRegistration);
         });
 
-    [].forEach.call(
-        document.querySelectorAll('.confirm-code'), function(button) {
-          button.addEventListener('click', confirmCode);
-        });
-
     $('register-error-exit').addEventListener('click', cancelRegistration);
 
 
@@ -643,16 +614,6 @@
     $('register-overlay-login-button')
         .addEventListener('click', registerOverlayLoginButtonClicked);
 
-    if (loadTimeData.valueExists('backButtonURL')) {
-      $('back-link').hidden = false;
-      $('back-link').addEventListener('click', function() {
-        window.location.href = loadTimeData.getString('backButtonURL');
-      });
-    }
-
-    updateVisibility();
-    document.addEventListener('visibilitychange', updateVisibility, false);
-
     focusManager = new LocalDiscoveryFocusManager();
     focusManager.initialize();
 
@@ -665,7 +626,6 @@
     onRegistrationFailed: onRegistrationFailed,
     onUnregisteredDeviceUpdate: onUnregisteredDeviceUpdate,
     onRegistrationConfirmedOnPrinter: onRegistrationConfirmedOnPrinter,
-    onRegistrationConfirmDeviceCode: onRegistrationConfirmDeviceCode,
     onCloudDeviceListAvailable: onCloudDeviceListAvailable,
     onCloudDeviceListUnavailable: onCloudDeviceListUnavailable,
     onDeviceCacheFlushed: onDeviceCacheFlushed,
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.css b/chrome/browser/resources/local_ntp/custom_links_edit.css
index b2f29ae..a830fee 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.css
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.css
@@ -128,6 +128,14 @@
   margin-top: 24px;
 }
 
+html:not([dir=rtl]) .buttons-container span + span {
+  margin-left: 16px;
+}
+
+html[dir=rtl] .buttons-container span + span {
+  margin-right: 16px;
+}
+
 button {
   border: none;
   border-radius: 4px;
diff --git a/chrome/browser/resources/md_bookmarks/BUILD.gn b/chrome/browser/resources/md_bookmarks/BUILD.gn
index a479745b..29cb87a 100644
--- a/chrome/browser/resources/md_bookmarks/BUILD.gn
+++ b/chrome/browser/resources/md_bookmarks/BUILD.gn
@@ -26,7 +26,6 @@
     ":constants",
     ":debouncer",
     ":dialog_focus_manager",
-    ":dnd_chip",
     ":dnd_manager",
     ":edit_dialog",
     ":folder_node",
@@ -110,19 +109,10 @@
   ]
 }
 
-js_library("dnd_chip") {
-  deps = [
-    ":types",
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:icon",
-  ]
-}
-
 js_library("dnd_manager") {
   deps = [
     ":api_listener",
     ":debouncer",
-    ":dnd_chip",
     ":folder_node",
     ":store",
     ":types",
diff --git a/chrome/browser/resources/md_bookmarks/dnd_chip.html b/chrome/browser/resources/md_bookmarks/dnd_chip.html
deleted file mode 100644
index 07b9dbf..0000000
--- a/chrome/browser/resources/md_bookmarks/dnd_chip.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://bookmarks/shared_style.html">
-
-<dom-module id="bookmarks-dnd-chip">
-  <template>
-    <style include="shared-style">
-      :host {
-        --chip-height: 40px;
-        --chip-padding-x: 8px;
-        --chip-width: 172px;
-
-        left: 0;
-        pointer-events: none;
-        position: absolute;
-        top: 0;
-        transform: translate(calc(var(--mouse-x) - var(--chip-width) * 0.5),
-                             calc(var(--mouse-y) - var(--chip-height) * 0.8));
-        z-index: 2;
-      }
-
-      :host(:not([showing_])) {
-        display: none;
-      }
-
-      .chip-container {
-        background-color: var(--interactive-color);
-        border-radius: calc(var(--chip-height) / 2);
-        box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.12),
-                    0 4px 10px 0 rgba(0, 0, 0, 0.24);
-        height: var(--chip-height);
-        left: 0;
-        padding: 0 var(--chip-padding-x);
-        position: absolute;
-        top: 0;
-        width: calc(var(--chip-width) - var(--chip-padding-x) * 2);
-      }
-
-      #title {
-        color: white;
-        flex: 1;
-        font-weight: 500;
-        margin-inline-end: 8px;
-        margin-inline-start: 12px;
-        text-decoration: none;
-      }
-
-      #icon-wrapper {
-        background: white;
-        border-radius: 12px;
-        color: var(--cr-secondary-text-color);
-        height: 24px;
-        position: relative;
-        width: 24px;
-      }
-
-      .centered {
-        align-items: center;
-        display: flex;
-        justify-content: center;
-      }
-
-      #count {
-        background: var(--google-red-500);
-        border-radius: 12px;
-        color: white;
-        font-weight: 500;
-        height: 24px;
-        min-width: 14px;
-        padding: 0 5px;
-        position: absolute;
-        right: -170px;
-        top: -10px;
-      }
-
-      :host-context([dir='rtl']) #count {
-        left: 2px;
-        right: auto;
-      }
-    </style>
-    <div class="chip-container centered">
-      <div id="icon-wrapper" class="centered">
-        <div id="icon"></div>
-      </div>
-      <div id="title" class="elided-text"></div>
-    </div>
-    <div id="count" class="centered" hidden$="[[!isMultiItem_]]"></div>
-  </template>
-  <script src="chrome://bookmarks/dnd_chip.js"></script>
-</dom-module>
diff --git a/chrome/browser/resources/md_bookmarks/dnd_chip.js b/chrome/browser/resources/md_bookmarks/dnd_chip.js
deleted file mode 100644
index 22e3b63f..0000000
--- a/chrome/browser/resources/md_bookmarks/dnd_chip.js
+++ /dev/null
@@ -1,47 +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.
-
-Polymer({
-  is: 'bookmarks-dnd-chip',
-
-  properties: {
-    /** @private */
-    showing_: {
-      type: Boolean,
-      reflectToAttribute: true,
-    },
-
-    /** @private */
-    isMultiItem_: Boolean,
-  },
-
-  /**
-   * @param {number} x
-   * @param {number} y
-   * @param {!Array<BookmarkNode>} items
-   * @param {!BookmarkNode} dragItem
-   */
-  showForItems: function(x, y, items, dragItem) {
-    this.style.setProperty('--mouse-x', x + 'px');
-    this.style.setProperty('--mouse-y', y + 'px');
-
-    if (this.showing_)
-      return;
-
-    const isFolder = !dragItem.url;
-    this.isMultiItem_ = items.length > 1;
-
-    this.$.icon.className = isFolder ? 'folder-icon' : 'website-icon';
-    this.$.icon.style.backgroundImage =
-        isFolder ? null : cr.icon.getFavicon(assert(dragItem.url));
-
-    this.$.title.textContent = dragItem.title;
-    this.$.count.textContent = items.length;
-    this.showing_ = true;
-  },
-
-  hide: function() {
-    this.showing_ = false;
-  },
-});
diff --git a/chrome/browser/resources/md_bookmarks/dnd_manager.html b/chrome/browser/resources/md_bookmarks/dnd_manager.html
index 0a8b34b8..8469e5b 100644
--- a/chrome/browser/resources/md_bookmarks/dnd_manager.html
+++ b/chrome/browser/resources/md_bookmarks/dnd_manager.html
@@ -1,4 +1,3 @@
 <link rel="import" href="chrome://bookmarks/constants.html">
 <link rel="import" href="chrome://bookmarks/debouncer.html">
-<link rel="import" href="chrome://bookmarks/dnd_chip.html">
 <script src="chrome://bookmarks/dnd_manager.js"></script>
diff --git a/chrome/browser/resources/md_bookmarks/dnd_manager.js b/chrome/browser/resources/md_bookmarks/dnd_manager.js
index 248472cad..0864e0c 100644
--- a/chrome/browser/resources/md_bookmarks/dnd_manager.js
+++ b/chrome/browser/resources/md_bookmarks/dnd_manager.js
@@ -213,56 +213,6 @@
   };
 
   /**
-   * Manages auto scrolling of elements on hover during internal drags. Native
-   * drags do this by themselves.
-   * @constructor
-   */
-  function AutoScroller() {
-    /** @const {number} */
-    this.SCROLL_ZONE_LENGTH = 20;
-    /** @const {number} */
-    this.SCROLL_DISTANCE = 10;
-    /** @const {number} */
-    this.SCROLL_INTERVAL = 100;
-    /** @private {?number} */
-    this.intervalId_ = null;
-  }
-
-  AutoScroller.prototype = {
-    /** @param {!Event} e */
-    update: function(e) {
-      this.reset();
-
-      const scrollParent = e.path.find((el) => {
-        return el.nodeType == Node.ELEMENT_NODE &&
-            window.getComputedStyle(el).overflowY == 'auto';
-      });
-
-      if (!scrollParent)
-        return;
-
-      const rect = scrollParent.getBoundingClientRect();
-      let yDelta = 0;
-      if (e.clientY < rect.top + this.SCROLL_ZONE_LENGTH)
-        yDelta = -this.SCROLL_DISTANCE;
-      else if (e.clientY > rect.bottom - this.SCROLL_ZONE_LENGTH)
-        yDelta = this.SCROLL_DISTANCE;
-
-      this.intervalId_ = window.setInterval(() => {
-        scrollParent.scrollTop += yDelta;
-      }, this.SCROLL_INTERVAL);
-    },
-
-    reset: function() {
-      if (this.intervalId_ == null)
-        return;
-
-      window.clearInterval(this.intervalId_);
-      this.intervalId_ = null;
-    },
-  };
-
-  /**
    * Encapsulates the behavior of the drag and drop indicator which puts a line
    * between items or highlights folders which are valid drop targets.
    * @constructor
@@ -357,22 +307,6 @@
   /**
    * Manages drag and drop events for the bookmarks-app.
    *
-   * This class manages an internal drag and drop based on mouse events and then
-   * delegates to the native drag and drop in chrome.bookmarkManagerPrivate when
-   * the mouse leaves the web content area. This allows us to render a drag and
-   * drop chip UI for internal drags, while correctly handling and avoiding
-   * conflict with native drags.
-   *
-   * The event flows look like
-   *
-   * mousedown -> mousemove -> mouseup
-   *               |
-   *               v
-   *      dragstart/dragleave (if the drag leaves the browser window)
-   *               |
-   *               v
-   * external drag -> bookmarkManagerPrivate.onDragEnter -> dragover -> drop
-   *
    * @constructor
    */
   function DNDManager() {
@@ -388,9 +322,6 @@
     /** @private {Object<string, function(!Event)>} */
     this.documentListeners_ = null;
 
-    /** @private {?bookmarks.AutoScroller} */
-    this.autoScroller_ = null;
-
     /** @private {?bookmarks.AutoExpander} */
     this.autoExpander_ = null;
 
@@ -399,25 +330,6 @@
      * @private {!Object}
      */
     this.timerProxy_ = window;
-
-    /**
-     * The bookmark drag and drop indicator chip.
-     * @private {BookmarksDndChipElement}
-     */
-    this.chip_ = null;
-
-    /**
-     * The element that initiated an internal drag. Not used once native drag
-     * starts.
-     * @private {BookmarkElement}
-     */
-    this.internalDragElement_ = null;
-
-    /**
-     * Where the internal drag started.
-     * @private {?{x: number, y: number}}
-     */
-    this.mouseDownPos_ = null;
   }
 
   DNDManager.prototype = {
@@ -425,14 +337,8 @@
       this.dragInfo_ = new DragInfo();
       this.dropIndicator_ = new DropIndicator();
       this.autoExpander_ = new AutoExpander();
-      this.autoScroller_ = new AutoScroller();
 
       this.documentListeners_ = {
-        'mousedown': this.onMousedown_.bind(this),
-        'mousemove': this.onMouseMove_.bind(this),
-        'mouseup': this.onMouseUp_.bind(this),
-        'mouseleave': this.onMouseLeave_.bind(this),
-
         'dragstart': this.onDragStart_.bind(this),
         'dragenter': this.onDragEnter_.bind(this),
         'dragover': this.onDragOver_.bind(this),
@@ -448,8 +354,6 @@
           this.handleChromeDragEnter_.bind(this));
       chrome.bookmarkManagerPrivate.onDragLeave.addListener(
           this.clearDragData_.bind(this));
-      chrome.bookmarkManagerPrivate.onDrop.addListener(
-          this.clearDragData_.bind(this));
     },
 
     destroy: function() {
@@ -461,151 +365,33 @@
     },
 
     ////////////////////////////////////////////////////////////////////////////
-    // MouseEvent handlers:
-
-    /**
-     * @private
-     * @param {Event} e
-     */
-    onMousedown_: function(e) {
-      const dragElement = getDragElement(e.path);
-      if (e.button != 0 || !dragElement)
-        return;
-
-      this.internalDragElement_ = dragElement;
-      this.mouseDownPos_ = {
-        x: e.clientX,
-        y: e.clientY,
-      };
-    },
-
-    /**
-     * @private
-     * @param {Event} e
-     */
-    onMouseMove_: function(e) {
-      // mousemove events still fire when dragged onto the the bookmarks bar.
-      // Once we are outside of the web contents, allow the native drag to
-      // start.
-      if (!this.internalDragElement_ || e.clientX < 0 ||
-          e.clientX > window.innerWidth || e.clientY < 0 ||
-          e.clientY > window.innerHeight) {
-        return;
-      }
-
-      this.dropDestination_ = null;
-
-      // Prevents a native drag from starting.
-      e.preventDefault();
-
-      this.autoScroller_.update(e);
-
-      // On the first mousemove after a mousedown, calculate the items to drag.
-      // This can't be done in mousedown because the user may be shift-clicking
-      // an item.
-      if (!this.dragInfo_.isDragValid()) {
-        // If the mouse hasn't been moved far enough, defer to next mousemove.
-        if (Math.abs(this.mouseDownPos_.x - e.clientX) < DRAG_THRESHOLD &&
-            Math.abs(this.mouseDownPos_.y - e.clientY) < DRAG_THRESHOLD) {
-          return;
-        }
-
-        const dragData = this.calculateDragData_();
-        if (!dragData) {
-          this.clearDragData_();
-          return;
-        }
-
-        this.dragInfo_.dragData = dragData;
-      }
-
-      const state = bookmarks.Store.getInstance().data;
-      const items = this.dragInfo_.dragData.elements;
-      this.dndChip.showForItems(
-          e.clientX, e.clientY, items,
-          this.internalDragElement_ ?
-              state.nodes[this.internalDragElement_.itemId] :
-              items[0]);
-
-      this.onDragOverCommon_(e);
-    },
-
-    /**
-     * This event fires when the mouse leaves the browser window (not the web
-     * content area).
-     * @private
-     */
-    onMouseLeave_: function() {
-      if (!this.internalDragElement_)
-        return;
-
-      this.startNativeDrag_();
-    },
-
-    /**
-     * @private
-     */
-    onMouseUp_: function() {
-      if (!this.internalDragElement_)
-        return;
-
-      if (this.dropDestination_) {
-        // Complete the drag by moving all dragged items to the drop
-        // destination.
-        const dropInfo = this.calculateDropInfo_(this.dropDestination_);
-        const shouldHighlight = this.shouldHighlight_(this.dropDestination_);
-
-        const movePromises = this.dragInfo_.dragData.elements.map((item) => {
-          return new Promise((resolve) => {
-            chrome.bookmarks.move(
-                item.id, {
-                  parentId: dropInfo.parentId,
-                  index: dropInfo.index == -1 ? undefined : dropInfo.index
-                },
-                resolve);
-          });
-        });
-
-        if (shouldHighlight) {
-          bookmarks.ApiListener.trackUpdatedItems();
-          Promise.all(movePromises)
-              .then(() => bookmarks.ApiListener.highlightUpdatedItems());
-        }
-      }
-
-      this.clearDragData_();
-    },
-
-    ////////////////////////////////////////////////////////////////////////////
     // DragEvent handlers:
 
     /**
-     * This should only fire when a mousemove goes from the content area to the
-     * browser chrome.
      * @private
      * @param {Event} e
      */
     onDragStart_: function(e) {
-      // |e| will be for the originally dragged bookmark item which dragstart
-      // was disabled for due to mousemove's preventDefault.
       const dragElement = getDragElement(e.path);
       if (!dragElement)
         return;
 
-      // Prevent normal drags of all bookmark items.
       e.preventDefault();
 
-      if (!this.startNativeDrag_())
+      const dragData = this.calculateDragData_(dragElement);
+      if (!dragData) {
+        this.clearDragData_();
         return;
-
-      // If we are dragging a single link, we can do the *Link* effect.
-      // Otherwise, we only allow copy and move.
-      if (e.dataTransfer) {
-        const draggedNodes = this.dragInfo_.dragData.elements;
-        e.dataTransfer.effectAllowed =
-            draggedNodes.length == 1 && draggedNodes[0].url ? 'copyLink' :
-                                                              'copyMove';
       }
+
+      const state = bookmarks.Store.getInstance().data;
+      const draggedNodes = dragData.elements.map((item) => item.id);
+      const dragNodeIndex = draggedNodes.indexOf(dragElement.itemId);
+      assert(dragNodeIndex != -1);
+
+      // TODO(calamity): account for touch.
+      chrome.bookmarkManagerPrivate.startDrag(
+          draggedNodes, dragNodeIndex, false);
     },
 
     /** @private */
@@ -618,9 +404,9 @@
      * @param {!Event} e
      */
     onDrop_: function(e) {
-      if (this.dropDestination_) {
-        e.preventDefault();
+      e.preventDefault();
 
+      if (this.dropDestination_) {
         const dropInfo = this.calculateDropInfo_(this.dropDestination_);
         const index = dropInfo.index != -1 ? dropInfo.index : undefined;
         const shouldHighlight = this.shouldHighlight_(this.dropDestination_);
@@ -633,7 +419,6 @@
             shouldHighlight ? bookmarks.ApiListener.highlightUpdatedItems :
                               undefined);
       }
-
       this.clearDragData_();
     },
 
@@ -650,28 +435,39 @@
      * @param {Event} e
      */
     onDragOver_: function(e) {
-      this.dropDestination_ = null;
+      // The default operation is to allow dropping links etc to do
+      // navigation. We never want to do that for the bookmark manager.
+      e.preventDefault();
 
-      // This is necessary to actually trigger the 'none' effect, even though
-      // the event will have this set to 'none' already.
-      if (e.dataTransfer)
-        e.dataTransfer.dropEffect = 'none';
+      this.dropDestination_ = null;
 
       // Allow normal DND on text inputs.
       if (e.path[0].tagName == 'INPUT')
         return;
 
-      // The default operation is to allow dropping links etc to do
-      // navigation. We never want to do that for the bookmark manager.
-      e.preventDefault();
-
       if (!this.dragInfo_.isDragValid())
         return;
 
-      if (this.onDragOverCommon_(e) && e.dataTransfer) {
-        e.dataTransfer.dropEffect =
-            this.dragInfo_.isSameProfile() ? 'move' : 'copy';
+      const state = bookmarks.Store.getInstance().data;
+      const items = this.dragInfo_.dragData.elements;
+
+      const overElement = getBookmarkElement(e.path);
+      this.autoExpander_.update(e, overElement);
+      if (!overElement) {
+        this.dropIndicator_.finish();
+        return;
       }
+
+      // Now we know that we can drop. Determine if we will drop above, on or
+      // below based on mouse position etc.
+      this.dropDestination_ =
+          this.calculateDropDestination_(e.clientY, overElement);
+      if (!this.dropDestination_) {
+        this.dropIndicator_.finish();
+        return;
+      }
+
+      this.dropIndicator_.update(this.dropDestination_);
     },
 
     /**
@@ -683,14 +479,10 @@
     },
 
     ////////////////////////////////////////////////////////////////////////////
-    // Common drag methods:
+    // Helper methods:
 
     /** @private */
     clearDragData_: function() {
-      this.dndChip.hide();
-      this.autoScroller_.reset();
-      this.internalDragElement_ = null;
-      this.mouseDownPos_ = null;
       this.autoExpander_.reset();
 
       // Defer the clearing of the data so that the bookmark manager API's drop
@@ -704,63 +496,6 @@
     },
 
     /**
-     * Starts a native drag by sending a message to the browser.
-     * @private
-     * @return {boolean}
-     */
-    startNativeDrag_: function() {
-      const state = bookmarks.Store.getInstance().data;
-
-      if (!this.dragInfo_.isDragValid())
-        return false;
-
-      const draggedNodes =
-          this.dragInfo_.dragData.elements.map((item) => item.id);
-
-      // Clear the drag data here so that the chip is hidden. The native drag
-      // will return after the clearing and set up its data.
-      this.clearDragData_();
-
-      // TODO(calamity): account for touch.
-      chrome.bookmarkManagerPrivate.startDrag(draggedNodes, false);
-
-      return true;
-    },
-
-    /**
-     * @private
-     * @param {Event} e
-     * @return {boolean}
-     */
-    onDragOverCommon_: function(e) {
-      const state = bookmarks.Store.getInstance().data;
-      const items = this.dragInfo_.dragData.elements;
-
-      const overElement = getBookmarkElement(e.path);
-      this.autoExpander_.update(e, overElement);
-      if (!overElement) {
-        this.dropIndicator_.finish();
-        return false;
-      }
-
-      // Now we know that we can drop. Determine if we will drop above, on or
-      // below based on mouse position etc.
-      this.dropDestination_ =
-          this.calculateDropDestination_(e.clientY, overElement);
-      if (!this.dropDestination_) {
-        this.dropIndicator_.finish();
-        return false;
-      }
-
-      this.dropIndicator_.update(this.dropDestination_);
-
-      return true;
-    },
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Other methods:
-
-    /**
      * @param {DropDestination} dropDestination
      * @return {{parentId: string, index: number}}
      */
@@ -798,10 +533,11 @@
     /**
      * Calculates which items should be dragged based on the initial drag item
      * and the current selection. Dragged items will end up selected.
+     * @param {!BookmarkElement} dragElement
      * @private
      */
-    calculateDragData_: function() {
-      const dragId = this.internalDragElement_.itemId;
+    calculateDragData_: function(dragElement) {
+      const dragId = dragElement.itemId;
       const store = bookmarks.Store.getInstance();
       const state = store.data;
 
@@ -810,10 +546,10 @@
 
       // Change selection to the dragged node if the node is not part of the
       // existing selection.
-      if (isBookmarkFolderNode(this.internalDragElement_) ||
+      if (isBookmarkFolderNode(dragElement) ||
           draggedNodes.indexOf(dragId) == -1) {
         store.dispatch(bookmarks.actions.deselectItems());
-        if (!isBookmarkFolderNode(this.internalDragElement_)) {
+        if (!isBookmarkFolderNode(dragElement)) {
           store.dispatch(bookmarks.actions.selectItem(dragId, state, {
             clear: false,
             range: false,
@@ -989,23 +725,10 @@
       this.timerProxy_ = timerProxy;
       this.dropIndicator_.timerProxy = timerProxy;
     },
-
-    /** @return {BookmarksDndChipElement} */
-    get dndChip() {
-      if (!this.chip_) {
-        this.chip_ =
-            /** @type {BookmarksDndChipElement} */ (
-                document.createElement('bookmarks-dnd-chip'));
-        document.body.appendChild(this.chip_);
-      }
-
-      return this.chip_;
-    },
   };
 
   return {
     AutoExpander: AutoExpander,
-    AutoScroller: AutoScroller,
     DNDManager: DNDManager,
     DragInfo: DragInfo,
     DropIndicator: DropIndicator,
diff --git a/chrome/browser/resources/md_extensions/runtime_host_permissions.html b/chrome/browser/resources/md_extensions/runtime_host_permissions.html
index 3a09d0be..25ebeda 100644
--- a/chrome/browser/resources/md_extensions/runtime_host_permissions.html
+++ b/chrome/browser/resources/md_extensions/runtime_host_permissions.html
@@ -81,7 +81,7 @@
       </div>
       <ul id="hosts">
         <template is="dom-repeat"
-            items="[[permissions.runtimeHostPermissions]]">
+            items="[[getRuntimeHosts_(permissions.specificSiteControls)]]">
           <li>
             <div>[[item]]</div>
             <paper-icon-button-light class="icon-more-vert">
diff --git a/chrome/browser/resources/md_extensions/runtime_host_permissions.js b/chrome/browser/resources/md_extensions/runtime_host_permissions.js
index 0463c047..18d98e11 100644
--- a/chrome/browser/resources/md_extensions/runtime_host_permissions.js
+++ b/chrome/browser/resources/md_extensions/runtime_host_permissions.js
@@ -101,8 +101,7 @@
           /** @type {chrome.developerPrivate.HostAccess} */ (select.value);
 
       if (access == chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES &&
-          (!this.permissions.runtimeHostPermissions ||
-           this.permissions.runtimeHostPermissions.length == 0)) {
+          !this.permissions.specificSiteControls) {
         // If the user is transitioning to the "on specific sites" option, show
         // the "add host" dialog. This serves two purposes:
         // - The user is prompted to add a host immediately, since otherwise
@@ -129,6 +128,24 @@
     },
 
     /**
+     * Returns the granted host permissions as a sorted set of strings.
+     * @return {!Array<string>}
+     * @private
+     */
+    getRuntimeHosts_: function() {
+      if (!this.permissions.specificSiteControls)
+        return [];
+
+      // Only show granted hosts in the list.
+      // TODO(devlin): For extensions that request a finite set of hosts,
+      // display them in a toggle list. https://crbug.com/891803.
+      return this.permissions.specificSiteControls.hosts
+          .filter(control => control.granted)
+          .map(control => control.host)
+          .sort();
+    },
+
+    /**
      * @param {Event} e
      * @private
      */
diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chrome/browser/resources/settings/basic_page/basic_page.html
index 216ff5d5..2e8fb6c 100644
--- a/chrome/browser/resources/settings/basic_page/basic_page.html
+++ b/chrome/browser/resources/settings/basic_page/basic_page.html
@@ -123,7 +123,8 @@
             restamp>
           <settings-section page-title="$i18n{multidevicePageTitle}"
               section="multidevice">
-            <settings-multidevice-page></settings-multidevice-page>
+            <settings-multidevice-page prefs="{{prefs}}">
+            </settings-multidevice-page>
           </settings-section>
         </template>
 </if>
diff --git a/chrome/browser/resources/settings/controls/settings_slider.html b/chrome/browser/resources/settings/controls/settings_slider.html
index d6046aa..b30ee91 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.html
+++ b/chrome/browser/resources/settings/controls/settings_slider.html
@@ -53,7 +53,8 @@
     </template>
     <div class="outer">
       <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
-          on-value-changed="onSliderChanged_" max="[[max]]" min="[[min]]">
+          on-value-changed="onSliderChanged_" max="[[max]]" min="[[min]]"
+          update-value-instantly="[[updateValueInstantly]]">
       </cr-slider>
       <div id="labels" disabled$="[[disableSlider_]]">
         <div id="label-begin">[[labelMin]]</div>
diff --git a/chrome/browser/resources/settings/controls/settings_slider.js b/chrome/browser/resources/settings/controls/settings_slider.js
index d9f79075..0b62ed330 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.js
+++ b/chrome/browser/resources/settings/controls/settings_slider.js
@@ -54,6 +54,11 @@
       type: Boolean,
     },
 
+    updateValueInstantly: {
+      type: Boolean,
+      value: true,
+    },
+
     loaded_: Boolean,
   },
 
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index f10aec3..cbe128d 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -203,6 +203,7 @@
         this.displayChangedListener_);
 
     this.getDisplayInfo_();
+    this.$.displaySizeSlider.updateValueInstantly = false;
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chrome/browser/resources/settings/internet_page/network_summary_item.js
index b4a8f88..447b56be 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/internet_page/network_summary_item.js
@@ -102,7 +102,7 @@
    * @private
    */
   getConnectionStateText_: function(networkState) {
-    const state = networkState.ConnectionState;
+    const state = networkState ? networkState.ConnectionState : null;
     if (!state)
       return '';
     const name = CrOnc.getNetworkName(networkState);
@@ -130,8 +130,9 @@
    * @private
    */
   showPolicyIndicator_: function(activeNetworkState) {
-    return activeNetworkState.ConnectionState ==
-        CrOnc.ConnectionState.CONNECTED ||
+    return (activeNetworkState !== undefined &&
+            activeNetworkState.ConnectionState ==
+                CrOnc.ConnectionState.CONNECTED) ||
         this.isPolicySource(activeNetworkState.Source);
   },
 
diff --git a/chrome/browser/resources/settings/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/multidevice_page/BUILD.gn
index 30149cc..9f7fc350 100644
--- a/chrome/browser/resources/settings/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/multidevice_page/BUILD.gn
@@ -12,6 +12,7 @@
     ":multidevice_feature_item",
     ":multidevice_feature_toggle",
     ":multidevice_page",
+    ":multidevice_smartlock_subpage",
     ":multidevice_subpage",
     ":multidevice_tether_item",
   ]
@@ -65,6 +66,16 @@
   ]
 }
 
+js_library("multidevice_smartlock_subpage") {
+  deps = [
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    "../prefs:prefs_behavior",
+    "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button",
+    "//ui/webui/resources/js:cr",
+  ]
+}
+
 js_library("multidevice_subpage") {
   deps = [
     ":multidevice_constants",
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_page.html b/chrome/browser/resources/settings/multidevice_page/multidevice_page.html
index 5001f6bc..3f6c521 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_page.html
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_page.html
@@ -19,6 +19,7 @@
 <link rel="import" href="multidevice_constants.html">
 <link rel="import" href="multidevice_feature_behavior.html">
 <link rel="import" href="multidevice_feature_toggle.html">
+<link rel="import" href="multidevice_smartlock_subpage.html">
 <link rel="import" href="multidevice_subpage.html">
 
 <dom-module id="settings-multidevice-page">
@@ -91,9 +92,18 @@
       <template is="dom-if" route-path="/multidevice/features" restamp>
         <settings-subpage associated-control="[[$$('#multidevice-item')]]"
             page-title="[[pageContentData.hostDeviceName]]">
-            <settings-multidevice-subpage
-                page-content-data="[[pageContentData]]">
-            </settings-multidevice-subpage>
+          <settings-multidevice-subpage
+              page-content-data="[[pageContentData]]">
+          </settings-multidevice-subpage>
+        </settings-subpage>
+      </template>
+      <template is="dom-if" route-path="/multidevice/features/smartLock"
+          restamp>
+        <settings-subpage page-title="$i18n{easyUnlockSectionTitle}">
+          <settings-multidevice-smartlock-subpage
+              prefs="{{prefs}}"
+              page-content-data="[[pageContentData]]">
+          </settings-multidevice-smartlock-subpage>
         </settings-subpage>
       </template>
     </settings-animated-pages>
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
index 5a8aa974..447280a 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
@@ -14,6 +14,9 @@
   behaviors: [MultiDeviceFeatureBehavior, WebUIListenerBehavior],
 
   properties: {
+    /** Preferences state. */
+    prefs: {type: Object},
+
     /**
      * A Map specifying which element should be focused when exiting a subpage.
      * The key of the map holds a settings.Route path, and the value holds a
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html b/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html
new file mode 100644
index 0000000..954815f
--- /dev/null
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.html
@@ -0,0 +1,55 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
+<link rel="import" href="multidevice_constants.html">
+<link rel="import" href="multidevice_feature_behavior.html">
+<link rel="import" href="multidevice_feature_toggle.html">
+<link rel="import" href="../i18n_setup.html">
+<link rel="import" href="../prefs/prefs.html">
+<link rel="import" href="../settings_shared_css.html">
+
+<dom-module id="settings-multidevice-smartlock-subpage">
+  <template>
+    <style include="settings-shared"></style>
+    <div class="settings-box first">
+      <!-- TODO(jhawkins): Remove this status text and move the toggle into
+           the subpage header section. -->
+      <div class="start">
+        <template is="dom-if" if="[[smartLockEnabled_]]" restamp>
+          $i18n{multideviceEnabled}
+        </template>
+        <template is="dom-if" if="[[!smartLockEnabled_]]" restamp>
+          $i18n{multideviceDisabled}
+        </template>
+      </div>
+      <settings-multidevice-feature-toggle
+          feature="[[MultiDeviceFeature.SMART_LOCK]]"
+          page-content-data="[[pageContentData]]">
+      </settings-multidevice-feature-toggle>
+    </div>
+    <div class="settings-box first line-only">
+      <h2 class="start first">
+        $i18n{multideviceSmartLockOptions}
+      </h2>
+    </div>
+    <iron-collapse opened="[[smartLockEnabled_]]">
+      <div class="list-frame">
+        <paper-radio-group
+            selected="[[smartLockSignInEnabled_]]"
+            selectable="cr-radio-button">
+          <cr-radio-button
+              name="disabled"
+              class="list-item underbar"
+              label="$i18n{easyUnlockUnlockDeviceOnly}">
+          </cr-radio-button>
+          <cr-radio-button
+              name="enabled"
+              class="list-item"
+              label="$i18n{easyUnlockUnlockDeviceAndAllowSignin}">
+          </cr-radio-button>
+        </paper-radio-group>
+      </div>
+    </iron-collapse>
+  </template>
+  <script src="multidevice_smartlock_subpage.js"></script>
+</dom-module>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js b/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js
new file mode 100644
index 0000000..04ff48b
--- /dev/null
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_smartlock_subpage.js
@@ -0,0 +1,93 @@
+// 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.
+
+/**
+ * @fileoverview
+ * Subpage of settings-multidevice-feature for managing the Smart Lock feature.
+ */
+cr.exportPath('settings');
+
+cr.define('settings', function() {
+  /**
+   * The state of the preference controlling Smart Lock's ability to sign-in the
+   * user.
+   * @enum {string}
+   */
+  SignInEnabledState = {
+    ENABLED: 'enabled',
+    DISABLED: 'disabled',
+  };
+
+  /** @const {string} */
+  SmartLockSignInEnabledPrefName = 'proximity_auth.is_chromeos_login_enabled';
+
+  return {
+    SignInEnabledState: SignInEnabledState,
+        SmartLockSignInEnabledPrefName: SmartLockSignInEnabledPrefName,
+  };
+});
+
+Polymer({
+  is: 'settings-multidevice-smartlock-subpage',
+
+  behaviors: [
+    MultiDeviceFeatureBehavior,
+  ],
+
+  properties: {
+    /** Preferences state. */
+    prefs: {type: Object},
+
+    /** @type {?SettingsRoutes} */
+    routes: {
+      type: Object,
+      value: settings.routes,
+    },
+
+    /**
+     * True if Smart Lock is enabled.
+     * @private
+     */
+    smartLockEnabled_: {
+      type: Boolean,
+      computed: 'computeIsSmartLockEnabled_(pageContentData)',
+    },
+
+    /**
+     * Whether Smart Lock may be used to sign-in the user (as opposed to only
+     * being able to unlock the user's screen).
+     * @private {!settings.SignInEnabledState}
+     */
+    smartLockSignInEnabled_: {
+      type: Object,
+      value: settings.SignInEnabledState.DISABLED,
+    },
+  },
+
+  observers: [
+    'updateSmartLockSignInEnabled_(prefs.' +
+        settings.SmartLockSignInEnabledPrefName + '.value)',
+  ],
+
+  /**
+   * Returns true if Smart Lock is an enabled feature.
+   * @return {boolean}
+   * @private
+   */
+  computeIsSmartLockEnabled_: function() {
+    return !!this.pageContentData &&
+        this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK) ==
+        settings.MultiDeviceFeatureState.ENABLED_BY_USER;
+  },
+
+  /**
+   * Updates the state of Smart Lock 'sign-in enabled' toggle.
+   * @private
+   */
+  updateSmartLockSignInEnabled_: function(enabled) {
+    this.smartLockSignInEnabled_ = enabled ?
+        settings.SignInEnabledState.ENABLED :
+        settings.SignInEnabledState.DISABLED;
+  },
+});
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
index 5b45963..9aaef19 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
@@ -66,7 +66,7 @@
           <settings-multidevice-feature-item id="smartLockItem"
               feature="[[MultiDeviceFeature.SMART_LOCK]]"
               page-content-data="[[pageContentData]]"
-              subpage-route="[[routes.LOCK_SCREEN]]">
+              subpage-route="[[routes.SMART_LOCK]]">
           </settings-multidevice-feature-item>
         </template>
         <template is="dom-if"
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 866453b..fa4f50d9 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -94,6 +94,7 @@
  *   SITE_SETTINGS_UNSANDBOXED_PLUGINS: (undefined|!settings.Route),
  *   SITE_SETTINGS_USB_DEVICES: (undefined|!settings.Route),
  *   SITE_SETTINGS_ZOOM_LEVELS: (undefined|!settings.Route),
+ *   SMART_LOCK: (undefined|!settings.Route),
  *   SMB_SHARES: (undefined|!settings.Route),
  *   STORAGE: (undefined|!settings.Route),
  *   STYLUS: (undefined|!settings.Route),
@@ -238,6 +239,8 @@
 
     r.MULTIDEVICE = r.BASIC.createSection('/multidevice', 'multidevice');
     r.MULTIDEVICE_FEATURES = r.MULTIDEVICE.createChild('/multidevice/features');
+    r.SMART_LOCK =
+        r.MULTIDEVICE_FEATURES.createChild('/multidevice/features/smartLock');
     // </if>
 
     if (pageVisibility.appearance !== false) {
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 3b2676828..73e58e8 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -1357,6 +1357,12 @@
         <structure name="IDR_SETTINGS_MULTIDEVICE_PAGE_JS"
                    file="multidevice_page/multidevice_page.js"
                    type="chrome_html" />
+        <structure name="IDR_SETTINGS_MULTIDEVICE_SMARTLOCK_SUBPAGE_HTML"
+                   file="multidevice_page/multidevice_smartlock_subpage.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_MULTIDEVICE_SMARTLOCK_SUBPAGE_JS"
+                   file="multidevice_page/multidevice_smartlock_subpage.js"
+                   type="chrome_html" />
         <structure name="IDR_SETTINGS_MULTIDEVICE_SUBPAGE_HTML"
                    file="multidevice_page/multidevice_subpage.html"
                    type="chrome_html" />
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
index 06f746f..40789091 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
@@ -3,8 +3,6 @@
 <head>
   <meta charset="utf-8">
   <title>$i18n{headerText}</title>
-  <script src="chrome://resources/js/load_time_data.js"></script>
-  <script src="strings.js"></script>
   <link rel="import" href="chrome://resources/html/polymer.html">
   <link rel="import" href="chrome://welcome/email/email_chooser.html">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html
index eeee7f64..b9d49f9 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html
@@ -1,2 +1,3 @@
 <link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="../shared/i18n_setup.html">
 <script src="nux_email_proxy.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/shared/i18n_setup.html b/chrome/browser/resources/welcome/onboarding_welcome/shared/i18n_setup.html
new file mode 100644
index 0000000..fa9610d
--- /dev/null
+++ b/chrome/browser/resources/welcome/onboarding_welcome/shared/i18n_setup.html
@@ -0,0 +1,2 @@
+<script src="chrome://resources/js/load_time_data.js"></script>
+<script src="../strings.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/search/url_validity_checker_factory.cc b/chrome/browser/search/url_validity_checker_factory.cc
new file mode 100644
index 0000000..ae29f5e4
--- /dev/null
+++ b/chrome/browser/search/url_validity_checker_factory.cc
@@ -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.
+
+#include "chrome/browser/search/url_validity_checker_factory.h"
+
+#include <memory>
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+// static
+UrlValidityChecker* UrlValidityCheckerFactory::GetInstance() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  static base::LazyInstance<UrlValidityCheckerFactory>::DestructorAtExit
+      instance = LAZY_INSTANCE_INITIALIZER;
+  return &(instance.Get().url_validity_checker_);
+}
+
+UrlValidityCheckerFactory::UrlValidityCheckerFactory()
+    : url_validity_checker_(g_browser_process->system_network_context_manager()
+                                ->GetSharedURLLoaderFactory()) {}
+
+UrlValidityCheckerFactory::~UrlValidityCheckerFactory() {}
diff --git a/chrome/browser/search/url_validity_checker_factory.h b/chrome/browser/search/url_validity_checker_factory.h
new file mode 100644
index 0000000..1ebd79d0
--- /dev/null
+++ b/chrome/browser/search/url_validity_checker_factory.h
@@ -0,0 +1,30 @@
+// 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_SEARCH_URL_VALIDITY_CHECKER_FACTORY_H_
+#define CHROME_BROWSER_SEARCH_URL_VALIDITY_CHECKER_FACTORY_H_
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "components/search/url_validity_checker_impl.h"
+
+// Singleton that owns a single UrlValidityCheckerImpl instance. Should only be
+// called from the UI thread.
+class UrlValidityCheckerFactory {
+ public:
+  static UrlValidityChecker* GetInstance();
+
+ private:
+  friend struct base::LazyInstanceTraitsBase<UrlValidityCheckerFactory>;
+
+  UrlValidityCheckerFactory();
+  ~UrlValidityCheckerFactory();
+
+  // The only instance that exists.
+  UrlValidityCheckerImpl url_validity_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(UrlValidityCheckerFactory);
+};
+
+#endif  // CHROME_BROWSER_SEARCH_URL_VALIDITY_CHECKER_FACTORY_H_
diff --git a/chrome/browser/shell_integration_win.cc b/chrome/browser/shell_integration_win.cc
index 634ee698..c1998b8 100644
--- a/chrome/browser/shell_integration_win.cc
+++ b/chrome/browser/shell_integration_win.cc
@@ -159,13 +159,22 @@
   win::MigrateShortcutsInPathInternal(chrome_exe, pins_path);
 }
 
+// Windows treats a given scheme as an Internet scheme only if its registry
+// entry has a "URL Protocol" key. Check this, otherwise we allow ProgIDs to be
+// used as custom protocols which leads to security bugs.
+bool IsValidCustomProtocol(const base::string16& scheme) {
+  if (scheme.empty())
+    return false;
+  base::win::RegKey cmd_key(HKEY_CLASSES_ROOT, scheme.c_str(), KEY_QUERY_VALUE);
+  return cmd_key.Valid() && cmd_key.HasValue(L"URL Protocol");
+}
+
 // Windows 8 introduced a new protocol->executable binding system which cannot
 // be retrieved in the HKCR registry subkey method implemented below. We call
 // AssocQueryString with the new Win8-only flag ASSOCF_IS_PROTOCOL instead.
 base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
-  base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
-  // Don't attempt to query protocol association on an empty string.
-  if (url_scheme.empty())
+  const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
+  if (!IsValidCustomProtocol(url_scheme))
     return base::string16();
 
   // Query AssocQueryString for a human-readable description of the program
@@ -174,12 +183,9 @@
   // an unknown external protocol.
   wchar_t out_buffer[1024];
   DWORD buffer_size = arraysize(out_buffer);
-  HRESULT hr = AssocQueryString(ASSOCF_IS_PROTOCOL,
-                                ASSOCSTR_FRIENDLYAPPNAME,
-                                url_scheme.c_str(),
-                                NULL,
-                                out_buffer,
-                                &buffer_size);
+  HRESULT hr =
+      AssocQueryString(ASSOCF_IS_PROTOCOL, ASSOCSTR_FRIENDLYAPPNAME,
+                       url_scheme.c_str(), NULL, out_buffer, &buffer_size);
   if (FAILED(hr)) {
     DLOG(WARNING) << "AssocQueryString failed!";
     return base::string16();
@@ -188,11 +194,13 @@
 }
 
 base::string16 GetAppForProtocolUsingRegistry(const GURL& url) {
-  base::string16 command_to_launch;
+  const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
+  if (!IsValidCustomProtocol(url_scheme))
+    return base::string16();
 
   // First, try and extract the application's display name.
-  base::string16 cmd_key_path = base::ASCIIToUTF16(url.scheme());
-  base::win::RegKey cmd_key_name(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
+  base::string16 command_to_launch;
+  base::win::RegKey cmd_key_name(HKEY_CLASSES_ROOT, url_scheme.c_str(),
                                  KEY_READ);
   if (cmd_key_name.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS &&
       !command_to_launch.empty()) {
@@ -201,7 +209,7 @@
 
   // Otherwise, parse the command line in the registry, and return the basename
   // of the program path if it exists.
-  cmd_key_path = base::ASCIIToUTF16(url.scheme() + "\\shell\\open\\command");
+  const base::string16 cmd_key_path = url_scheme + L"\\shell\\open\\command";
   base::win::RegKey cmd_key_exe(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
                                 KEY_READ);
   if (cmd_key_exe.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS) {
@@ -581,10 +589,9 @@
 }
 
 base::string16 GetApplicationNameForProtocol(const GURL& url) {
-  base::string16 application_name;
   // Windows 8 or above has a new protocol association query.
   if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
-    application_name = GetAppForProtocolUsingAssocQuery(url);
+    base::string16 application_name = GetAppForProtocolUsingAssocQuery(url);
     if (!application_name.empty())
       return application_name;
   }
diff --git a/chrome/browser/speech/tts_controller.h b/chrome/browser/speech/tts_controller.h
index 6aba287..1989e2b 100644
--- a/chrome/browser/speech/tts_controller.h
+++ b/chrome/browser/speech/tts_controller.h
@@ -13,6 +13,7 @@
 
 #include "base/memory/singleton.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
 #include "url/gurl.h"
 
 class Utterance;
@@ -113,9 +114,8 @@
 
 // Class that wants to be notified when the set of
 // voices has changed.
-class VoicesChangedDelegate {
+class VoicesChangedDelegate : public base::CheckedObserver {
  public:
-  virtual ~VoicesChangedDelegate() {}
   virtual void OnVoicesChanged() = 0;
 };
 
diff --git a/chrome/browser/speech/tts_controller_impl.cc b/chrome/browser/speech/tts_controller_impl.cc
index 9ec2bdf..46ea468 100644
--- a/chrome/browser/speech/tts_controller_impl.cc
+++ b/chrome/browser/speech/tts_controller_impl.cc
@@ -622,20 +622,18 @@
   if (!platform_impl_)
     return;
 
-  for (auto iter = voices_changed_delegates_.begin();
-       iter != voices_changed_delegates_.end(); ++iter) {
-    (*iter)->OnVoicesChanged();
-  }
+  for (auto& delegate : voices_changed_delegates_)
+    delegate.OnVoicesChanged();
 }
 
 void TtsControllerImpl::AddVoicesChangedDelegate(
     VoicesChangedDelegate* delegate) {
-  voices_changed_delegates_.insert(delegate);
+  voices_changed_delegates_.AddObserver(delegate);
 }
 
 void TtsControllerImpl::RemoveVoicesChangedDelegate(
     VoicesChangedDelegate* delegate) {
-  voices_changed_delegates_.erase(delegate);
+  voices_changed_delegates_.RemoveObserver(delegate);
 }
 
 void TtsControllerImpl::RemoveUtteranceEventDelegate(
diff --git a/chrome/browser/speech/tts_controller_impl.h b/chrome/browser/speech/tts_controller_impl.h
index 49a22aa..e8b574e 100644
--- a/chrome/browser/speech/tts_controller_impl.h
+++ b/chrome/browser/speech/tts_controller_impl.h
@@ -102,7 +102,7 @@
   base::queue<Utterance*> utterance_queue_;
 
   // A set of delegates that want to be notified when the voices change.
-  std::set<VoicesChangedDelegate*> voices_changed_delegates_;
+  base::ObserverList<VoicesChangedDelegate> voices_changed_delegates_;
 
   // A pointer to the platform implementation of text-to-speech, for
   // dependency injection.
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 3a36371..982cd6e 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -7169,7 +7169,7 @@
   ui_test_utils::NavigateToURL(browser(), url);
   console_observer.Wait();
   EXPECT_TRUE(
-      base::MatchPattern(console_observer.message(), "*distrusted in M70*"));
+      base::MatchPattern(console_observer.message(), "*distrusted very soon*"));
 }
 
 // Tests that the Symantec console message is logged for subresources, but caps
diff --git a/chrome/browser/themes/theme_properties.cc b/chrome/browser/themes/theme_properties.cc
index 45a830f..cd5c3e9 100644
--- a/chrome/browser/themes/theme_properties.cc
+++ b/chrome/browser/themes/theme_properties.cc
@@ -168,7 +168,7 @@
       return incognito ? gfx::kGoogleGrey100 : gfx::kGoogleGrey800;
 
     case ThemeProperties::COLOR_BOOKMARK_TEXT:
-      return incognito ? gfx::kGoogleGrey100 : gfx::kGoogleGrey700;
+      return incognito ? gfx::kGoogleGrey100 : gfx::kGoogleGrey800;
 
     case ThemeProperties::COLOR_TAB_CLOSE_BUTTON_ACTIVE:
     case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON:
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9df30a8..266fd8b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -132,6 +132,8 @@
       "cocoa/themed_window.mm",
       "cocoa/touchbar/credit_card_autofill_touch_bar_controller.h",
       "cocoa/touchbar/credit_card_autofill_touch_bar_controller.mm",
+      "cocoa/touchbar/text_suggestions_touch_bar_controller.h",
+      "cocoa/touchbar/text_suggestions_touch_bar_controller.mm",
       "cocoa/touchbar/web_textfield_touch_bar_controller.h",
       "cocoa/touchbar/web_textfield_touch_bar_controller.mm",
       "cocoa/url_drop_target.h",
@@ -2854,6 +2856,8 @@
       "views/tabs/tab_strip_controller.h",
       "views/tabs/tab_strip_layout.cc",
       "views/tabs/tab_strip_layout.h",
+      "views/tabs/tab_style.cc",
+      "views/tabs/tab_style.h",
       "views/tabs/window_finder.h",
       "views/task_manager_view.cc",
       "views/task_manager_view.h",
diff --git a/chrome/browser/ui/app_list/arc/arc_app_icon.cc b/chrome/browser/ui/app_list/arc/arc_app_icon.cc
index 3cc1d8a..ddbccc65 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_icon.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_icon.cc
@@ -73,17 +73,19 @@
   ReadResult(bool error,
              bool request_to_install,
              ui::ScaleFactor scale_factor,
+             bool resize_allowed,
              std::string unsafe_icon_data)
       : error(error),
         request_to_install(request_to_install),
         scale_factor(scale_factor),
-        unsafe_icon_data(unsafe_icon_data) {
-  }
+        resize_allowed(resize_allowed),
+        unsafe_icon_data(unsafe_icon_data) {}
 
-  bool error;
-  bool request_to_install;
-  ui::ScaleFactor scale_factor;
-  std::string unsafe_icon_data;
+  const bool error;
+  const bool request_to_install;
+  const ui::ScaleFactor scale_factor;
+  const bool resize_allowed;
+  const std::string unsafe_icon_data;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -166,7 +168,8 @@
 class ArcAppIcon::DecodeRequest : public ImageDecoder::ImageRequest {
  public:
   DecodeRequest(const base::WeakPtr<ArcAppIcon>& host,
-                const ArcAppIconDescriptor& descriptor);
+                const ArcAppIconDescriptor& descriptor,
+                bool resize_allowed);
   ~DecodeRequest() override;
 
   // ImageDecoder::ImageRequest
@@ -176,6 +179,7 @@
  private:
   base::WeakPtr<ArcAppIcon> host_;
   const ArcAppIconDescriptor descriptor_;
+  const bool resize_allowed_;
 
   DISALLOW_COPY_AND_ASSIGN(DecodeRequest);
 };
@@ -184,11 +188,11 @@
 // ArcAppIcon::DecodeRequest
 
 ArcAppIcon::DecodeRequest::DecodeRequest(const base::WeakPtr<ArcAppIcon>& host,
-                                         const ArcAppIconDescriptor& descriptor)
-    : host_(host), descriptor_(descriptor) {}
+                                         const ArcAppIconDescriptor& descriptor,
+                                         bool resize_allowed)
+    : host_(host), descriptor_(descriptor), resize_allowed_(resize_allowed) {}
 
-ArcAppIcon::DecodeRequest::~DecodeRequest() {
-}
+ArcAppIcon::DecodeRequest::~DecodeRequest() = default;
 
 void ArcAppIcon::DecodeRequest::OnImageDecoded(const SkBitmap& bitmap) {
   DCHECK(!bitmap.isNull() && !bitmap.empty());
@@ -198,15 +202,20 @@
 
   const int expected_dim = descriptor_.GetSizeInPixels();
   if (bitmap.width() != expected_dim || bitmap.height() != expected_dim) {
-    VLOG(2) << "Decoded ARC icon has unexpected dimension " << bitmap.width()
-            << "x" << bitmap.height() << ". Expected " << expected_dim << ".";
-
-    host_->MaybeRequestIcon(descriptor_.scale_factor);
-    host_->DiscardDecodeRequest(this);
-    return;
+    if (!resize_allowed_) {
+      VLOG(2) << "Decoded ARC icon has unexpected dimension " << bitmap.width()
+              << "x" << bitmap.height() << ". Expected " << expected_dim << ".";
+      host_->MaybeRequestIcon(descriptor_.scale_factor);
+    } else {
+      host_->Update(descriptor_.scale_factor,
+                    skia::ImageOperations::Resize(
+                        bitmap, skia::ImageOperations::RESIZE_BEST,
+                        expected_dim, expected_dim));
+    }
+  } else {
+    host_->Update(descriptor_.scale_factor, bitmap);
   }
 
-  host_->Update(descriptor_.scale_factor, bitmap);
   host_->DiscardDecodeRequest(this);
 }
 
@@ -295,14 +304,19 @@
   DCHECK(!path.empty());
 
   base::FilePath path_to_read;
+  // Allow resizing only for default app icons.
+  bool resize_allowed;
   if (base::PathExists(path)) {
     path_to_read = path;
+    resize_allowed = false;
   } else {
     if (default_app_path.empty() || !base::PathExists(default_app_path)) {
-      return std::make_unique<ArcAppIcon::ReadResult>(false, true, scale_factor,
-                                                      std::string());
+      return std::make_unique<ArcAppIcon::ReadResult>(
+          false /* error */, true /* request_to_install */, scale_factor,
+          false /* resize_allowed */, std::string() /* unsafe_icon_data */);
     }
     path_to_read = default_app_path;
+    resize_allowed = true;
   }
 
   bool request_to_install = path_to_read != path;
@@ -317,11 +331,13 @@
     // on cached icon file. Send request to re install the icon.
     request_to_install |= unsafe_icon_data.empty();
     return std::make_unique<ArcAppIcon::ReadResult>(
-        true, request_to_install, scale_factor, std::string());
+        true /* error */, request_to_install, scale_factor,
+        false /* resize_allowed */, std::string() /* unsafe_icon_data */);
   }
 
   return std::make_unique<ArcAppIcon::ReadResult>(
-      false, request_to_install, scale_factor, unsafe_icon_data);
+      false /* error */, request_to_install, scale_factor, resize_allowed,
+      unsafe_icon_data);
 }
 
 void ArcAppIcon::OnIconRead(
@@ -334,8 +350,8 @@
   if (!read_result->unsafe_icon_data.empty()) {
     decode_requests_.emplace_back(std::make_unique<DecodeRequest>(
         weak_ptr_factory_.GetWeakPtr(),
-        ArcAppIconDescriptor(resource_size_in_dip_,
-                             read_result->scale_factor)));
+        ArcAppIconDescriptor(resource_size_in_dip_, read_result->scale_factor),
+        read_result->resize_allowed));
     if (disable_safe_decoding_for_testing) {
       SkBitmap bitmap;
       if (!read_result->unsafe_icon_data.empty() &&
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
index aaf98f6..b63d08d0 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -174,6 +174,22 @@
          info1.launchable != info2.launchable;
 }
 
+// We have only fixed icon dimensions for default apps, 32, 48 and 64. If
+// requested dimension does not exist, use bigger one that can be downsized.
+// In case requested dimension is bigger than 64, use largest possible size that
+// can be upsized.
+ArcAppIconDescriptor MapDefaultAppIconDescriptor(
+    const ArcAppIconDescriptor& descriptor) {
+  int default_app_dip_size;
+  if (descriptor.dip_size <= 32)
+    default_app_dip_size = 32;
+  else if (descriptor.dip_size <= 48)
+    default_app_dip_size = 48;
+  else
+    default_app_dip_size = 64;
+  return ArcAppIconDescriptor(default_app_dip_size, descriptor.scale_factor);
+}
+
 // Whether skip install_time for comparing two |AppInfo|.
 bool ignore_compare_app_info_install_time = false;
 
@@ -379,7 +395,8 @@
   if (!default_app || default_app->app_path.empty())
     return base::FilePath();
 
-  return default_app->app_path.AppendASCII(descriptor.GetName());
+  return default_app->app_path.AppendASCII(
+      MapDefaultAppIconDescriptor(descriptor).GetName());
 }
 
 base::FilePath ArcAppListPrefs::GetIconPath(
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index 5ddd291..9e445bd 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -2318,6 +2318,28 @@
   icon_loader.reset();
 }
 
+// Validates that default app icon can be loaded for non-default dips, that do
+// not exist in Chrome image.
+TEST_P(ArcAppLauncherForDefaulAppTest, AppIconNonDefaultDip) {
+  ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get());
+  ASSERT_NE(nullptr, prefs);
+
+  ASSERT_FALSE(fake_default_apps().empty());
+  const arc::mojom::AppInfo& app = fake_default_apps()[0];
+  const std::string app_id = ArcAppTest::GetAppId(app);
+
+  // Icon can be only fetched after app is registered in the system.
+  arc_test()->WaitForDefaultApps();
+
+  FakeAppIconLoaderDelegate icon_delegate;
+  // 17 should never be a default dip size.
+  std::unique_ptr<ArcAppIconLoader> icon_loader =
+      std::make_unique<ArcAppIconLoader>(profile(), 17, &icon_delegate);
+  icon_loader->FetchImage(app_id);
+  icon_delegate.WaitForIconUpdates(ui::GetSupportedScaleFactors().size());
+  icon_loader.reset();
+}
+
 TEST_P(ArcAppLauncherForDefaulAppTest, AppLauncherForDefaultApps) {
   ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get());
   ASSERT_NE(nullptr, prefs);
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc b/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
index 93d80a3..1293ff45 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
@@ -158,6 +158,15 @@
     return;
   }
 
+  // TODO(jkardatzke): Remove this code for M72. This is a workaround to deal
+  // with a bug where we were caching the wrong resolution of the icons and they
+  // looked really bad. This only existed on dev channel, so there's limited
+  // reach of it, and after everyone has upgraded we can remove this check.
+  // crbug.com/891588
+  if (bitmap.width() < expected_dim || bitmap.height() < expected_dim) {
+    host_->MaybeRequestIcon(scale_factor_);
+  }
+
   // We won't always get back from Crostini the icon size we asked for, so it
   // is expected that sometimes we need to rescale it.
   SkBitmap resized_image = skia::ImageOperations::Resize(
@@ -220,6 +229,15 @@
   if (!registry_service_)
     return;
 
+  // TODO(jkardatzke): Remove this for M-72, this is here temporarily to prevent
+  // continually requesting updated icons if there are not larger size ones
+  // available in Linux for that app.
+  // crbug.com/891588
+  if (already_requested_icons_.find(scale_factor) !=
+      already_requested_icons_.end())
+    return;
+  already_requested_icons_.insert(scale_factor);
+
   // CrostiniRegistryService notifies CrostiniAppModelBuilder via Observer when
   // icon is ready and CrostiniAppModelBuilder refreshes the icon of the
   // corresponding item by calling LoadScaleFactor.
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_icon.h b/chrome/browser/ui/app_list/crostini/crostini_app_icon.h
index a222687..7b611ed3 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_icon.h
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_icon.h
@@ -92,6 +92,10 @@
 
   gfx::ImageSkia image_skia_;
 
+  // TODO(jkardatzke): Remove this for M-72, it's to cleanup from
+  // crbug.com/891588
+  std::set<ui::ScaleFactor> already_requested_icons_;
+
   // Contains pending image decode requests.
   std::vector<std::unique_ptr<DecodeRequest>> decode_requests_;
 
diff --git a/chrome/browser/ui/app_list/search/omnibox_result.cc b/chrome/browser/ui/app_list/search/omnibox_result.cc
index e38e397..e348cc4 100644
--- a/chrome/browser/ui/app_list/search/omnibox_result.cc
+++ b/chrome/browser/ui/app_list/search/omnibox_result.cc
@@ -89,6 +89,7 @@
     case AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
     case AutocompleteMatchType::TAB_SEARCH_DEPRECATED:
     case AutocompleteMatchType::DOCUMENT_SUGGESTION:
+    case AutocompleteMatchType::PEDAL:
       return kIcDomainIcon;
 
     case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED:
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 657bb19..61c8619 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_prefs.h"
+#include "ash/public/cpp/window_animation_types.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
 #include "base/strings/pattern.h"
@@ -520,12 +521,19 @@
     return ash::SHELF_ACTION_WINDOW_ACTIVATED;
   }
 
+  AppListClientImpl* app_list_client = AppListClientImpl::GetInstance();
   if (window->IsActive() && allow_minimize &&
-      !AppListClientImpl::GetInstance()->app_list_target_visibility()) {
+      !(app_list_client && app_list_client->app_list_target_visibility())) {
     window->Minimize();
     return ash::SHELF_ACTION_WINDOW_MINIMIZED;
   }
 
+  if (app_list_client && app_list_client->IsHomeLauncherEnabledInTabletMode()) {
+    // Run slide down animation to show the window.
+    wm::SetWindowVisibilityAnimationType(
+        native_window, ash::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_DOWN);
+  }
+
   window->Show();
   window->Activate();
   return ash::SHELF_ACTION_WINDOW_ACTIVATED;
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index ae700a0..7e3e70db 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -4146,9 +4146,12 @@
 
   // Wait for real app icon image is decoded and set for shelf item.
   shelf_controller->GetLastItemImage();
-  // Should have only one update that guarantees default icon was not set in
+  // Should have only one update for newly created window with no-icon set plus
+  // update for each scale factor. That guarantees default icon was not set in
   // between.
-  EXPECT_EQ(update_count_before_launch + 1, shelf_controller->updated_count());
+  EXPECT_EQ(
+      update_count_before_launch + 1 + ui::GetSupportedScaleFactors().size(),
+      shelf_controller->updated_count());
 }
 
 TEST_P(ChromeLauncherControllerArcDefaultAppsTest, PlayStoreDeferredLaunch) {
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index bdc5c42b..048b1ad 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -146,9 +146,8 @@
   // to determine whether or not to offer save of Autofill data. However, we
   // don't allow saving of Autofill data while in incognito anyway, so an
   // incognito code path should never get far enough to query StrikeDatabase.
-  return profile->IsOffTheRecord()
-             ? nullptr
-             : StrikeDatabaseFactory::GetForProfile(profile);
+  DCHECK(!profile->IsOffTheRecord());
+  return StrikeDatabaseFactory::GetForProfile(profile);
 }
 
 ukm::UkmRecorder* ChromeAutofillClient::GetUkmRecorder() {
diff --git a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
index 69e4ff44..96e59c91 100644
--- a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
@@ -5,14 +5,17 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind_test_util.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -29,14 +32,17 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/gfx/image/image_skia.h"
 
 using bookmarks::BookmarkModel;
+using bookmarks::BookmarkNode;
 using bookmarks::UrlAndTitle;
 
 namespace {
 const char kPersistBookmarkURL[] = "http://www.cnn.com/";
 const char kPersistBookmarkTitle[] = "CNN";
-} // namespace
+}  // namespace
 
 class TestBookmarkTabHelperObserver : public BookmarkTabHelperObserver {
  public:
@@ -74,9 +80,8 @@
 
     base::RepeatingTimer timer;
     timer.Start(
-        FROM_HERE,
-        base::TimeDelta::FromMilliseconds(15),
-        base::Bind(&CheckAnimation, browser(), runner->QuitClosure()));
+        FROM_HERE, base::TimeDelta::FromMilliseconds(15),
+        base::BindRepeating(&CheckAnimation, browser(), runner->QuitClosure()));
     runner->Run();
     return base::Time::Now() - start;
   }
@@ -109,8 +114,7 @@
 IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest, PRE_Persist) {
   BookmarkModel* bookmark_model = WaitForBookmarkModel(browser()->profile());
 
-  bookmarks::AddIfNotBookmarked(bookmark_model,
-                                GURL(kPersistBookmarkURL),
+  bookmarks::AddIfNotBookmarked(bookmark_model, GURL(kPersistBookmarkURL),
                                 base::ASCIIToUTF16(kPersistBookmarkTitle));
 }
 
@@ -149,8 +153,7 @@
   Browser* browser2 = observer.WaitForSingleNewBrowser();
   BookmarkModel* bookmark_model2 = WaitForBookmarkModel(browser2->profile());
 
-  bookmarks::AddIfNotBookmarked(bookmark_model1,
-                                GURL(kPersistBookmarkURL),
+  bookmarks::AddIfNotBookmarked(bookmark_model1, GURL(kPersistBookmarkURL),
                                 base::ASCIIToUTF16(kPersistBookmarkTitle));
   std::vector<UrlAndTitle> urls1, urls2;
   bookmark_model1->GetBookmarks(&urls1);
@@ -164,10 +167,10 @@
 // Flaky on Linux: http://crbug.com/504869.
 #if defined(OS_LINUX)
 #define MAYBE_HideStarOnNonbookmarkedInterstitial \
-    DISABLED_HideStarOnNonbookmarkedInterstitial
+  DISABLED_HideStarOnNonbookmarkedInterstitial
 #else
 #define MAYBE_HideStarOnNonbookmarkedInterstitial \
-    HideStarOnNonbookmarkedInterstitial
+  HideStarOnNonbookmarkedInterstitial
 #endif
 IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest,
                        MAYBE_HideStarOnNonbookmarkedInterstitial) {
@@ -180,8 +183,7 @@
 
   BookmarkModel* bookmark_model = WaitForBookmarkModel(browser()->profile());
   GURL bookmark_url = embedded_test_server()->GetURL("example.test", "/");
-  bookmarks::AddIfNotBookmarked(bookmark_model,
-                                bookmark_url,
+  bookmarks::AddIfNotBookmarked(bookmark_model, bookmark_url,
                                 base::ASCIIToUTF16("Bookmark"));
 
   TestBookmarkTabHelperObserver bookmark_observer;
@@ -207,3 +209,96 @@
 
   tab_helper->RemoveObserver(&bookmark_observer);
 }
+
+// Provides coverage for the Bookmark Manager bookmark drag and drag image
+// generation for dragging a single bookmark.
+IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest, DragSingleBookmark) {
+  BookmarkModel* model = WaitForBookmarkModel(browser()->profile());
+  const base::string16 page_title(base::ASCIIToUTF16("foo"));
+  const GURL page_url("http://www.google.com");
+  const BookmarkNode* root = model->bookmark_bar_node();
+  const BookmarkNode* node = model->AddURL(root, 0, page_title, page_url);
+
+  auto run_loop = std::make_unique<base::RunLoop>();
+
+  chrome::DoBookmarkDragCallback cb = base::BindLambdaForTesting(
+      [&run_loop, page_title, page_url](
+          const ui::OSExchangeData& drag_data, gfx::NativeView native_view,
+          ui::DragDropTypes::DragEventSource source, int operation) {
+        GURL url;
+        base::string16 title;
+        EXPECT_TRUE(drag_data.provider().GetURLAndTitle(
+            ui::OSExchangeData::FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES,
+            &url, &title));
+        EXPECT_EQ(page_url, url);
+        EXPECT_EQ(page_title, title);
+#if !defined(OS_WIN)
+        // On Windows, GetDragImage() is a NOTREACHED() as the Windows
+        // implementation of OSExchangeData just sets the drag image on the OS
+        // API.
+        // See https://crbug.com/893388.
+        EXPECT_FALSE(drag_data.provider().GetDragImage().isNull());
+#endif
+        run_loop->Quit();
+      });
+
+  constexpr int kDragNodeIndex = 0;
+  chrome::DragBookmarksForTest(
+      browser()->profile(),
+      {{node},
+       kDragNodeIndex,
+       platform_util::GetViewForWindow(browser()->window()->GetNativeWindow()),
+       ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE},
+      std::move(cb));
+
+  run_loop->Run();
+}
+
+// Provides coverage for the Bookmark Manager bookmark drag and drag image
+// generation for dragging multiple bookmarks.
+IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest, DragMultipleBookmarks) {
+  BookmarkModel* model = WaitForBookmarkModel(browser()->profile());
+  const base::string16 page_title(base::ASCIIToUTF16("foo"));
+  const GURL page_url("http://www.google.com");
+  const BookmarkNode* root = model->bookmark_bar_node();
+  const BookmarkNode* node1 = model->AddURL(root, 0, page_title, page_url);
+  const BookmarkNode* node2 = model->AddFolder(root, 0, page_title);
+
+  auto run_loop = std::make_unique<base::RunLoop>();
+
+  chrome::DoBookmarkDragCallback cb = base::BindLambdaForTesting(
+      [&run_loop](const ui::OSExchangeData& drag_data,
+                  gfx::NativeView native_view,
+                  ui::DragDropTypes::DragEventSource source, int operation) {
+#if !defined(OS_MACOSX)
+        GURL url;
+        base::string16 title;
+        // On Mac 10.11 and 10.12, this returns true, even though we set no url.
+        // See https://crbug.com/893432.
+        EXPECT_FALSE(drag_data.provider().GetURLAndTitle(
+            ui::OSExchangeData::FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES,
+            &url, &title));
+#endif
+#if !defined(OS_WIN)
+        // On Windows, GetDragImage() is a NOTREACHED() as the Windows
+        // implementation of OSExchangeData just sets the drag image on the OS
+        // API.
+        // See https://crbug.com/893388.
+        EXPECT_FALSE(drag_data.provider().GetDragImage().isNull());
+#endif
+        run_loop->Quit();
+      });
+
+  constexpr int kDragNodeIndex = 1;
+  chrome::DragBookmarksForTest(browser()->profile(),
+                               {
+                                   {node1, node2},
+                                   kDragNodeIndex,
+                                   platform_util::GetViewForWindow(
+                                       browser()->window()->GetNativeWindow()),
+                                   ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE,
+                               },
+                               std::move(cb));
+
+  run_loop->Run();
+}
diff --git a/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc b/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
index e753432..1718324 100644
--- a/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
@@ -24,6 +24,17 @@
 
 namespace chrome {
 
+BookmarkDragParams::BookmarkDragParams(
+    std::vector<const bookmarks::BookmarkNode*> nodes,
+    int drag_node_index,
+    gfx::NativeView view,
+    ui::DragDropTypes::DragEventSource source)
+    : nodes(std::move(nodes)),
+      drag_node_index(drag_node_index),
+      view(view),
+      source(source) {}
+BookmarkDragParams::~BookmarkDragParams() = default;
+
 int DropBookmarks(Profile* profile,
                   const BookmarkNodeData& data,
                   const BookmarkNode* parent_node,
diff --git a/chrome/browser/ui/bookmarks/bookmark_drag_drop.h b/chrome/browser/ui/bookmarks/bookmark_drag_drop.h
index 890d705d2..47b11c2 100644
--- a/chrome/browser/ui/bookmarks/bookmark_drag_drop.h
+++ b/chrome/browser/ui/bookmarks/bookmark_drag_drop.h
@@ -18,13 +18,46 @@
 struct BookmarkNodeData;
 }
 
+namespace ui {
+class OSExchangeData;
+}
+
 namespace chrome {
 
+// Callback for implementing a system drag based on gathered bookmark drag data.
+// Used in testing.
+using DoBookmarkDragCallback =
+    base::OnceCallback<void(const ui::OSExchangeData& drag_data,
+                            gfx::NativeView native_view,
+                            ui::DragDropTypes::DragEventSource source,
+                            int operation)>;
+
+struct BookmarkDragParams {
+  BookmarkDragParams(std::vector<const bookmarks::BookmarkNode*> nodes,
+                     int drag_node_index,
+                     gfx::NativeView view,
+                     ui::DragDropTypes::DragEventSource source);
+  ~BookmarkDragParams();
+
+  // The bookmark nodes to be dragged.
+  std::vector<const bookmarks::BookmarkNode*> nodes;
+
+  // The index of the main dragged node.
+  int drag_node_index;
+
+  // The native view that initiated the drag.
+  gfx::NativeView view;
+
+  // The source of the drag.
+  ui::DragDropTypes::DragEventSource source;
+};
+
 // Starts the process of dragging a folder of bookmarks.
-void DragBookmarks(Profile* profile,
-                   const std::vector<const bookmarks::BookmarkNode*>& nodes,
-                   gfx::NativeView view,
-                   ui::DragDropTypes::DragEventSource source);
+void DragBookmarks(Profile* profile, const BookmarkDragParams& params);
+
+void DragBookmarksForTest(Profile* profile,
+                          const BookmarkDragParams& params,
+                          DoBookmarkDragCallback do_drag_callback);
 
 // Drops the bookmark nodes that are in |data| onto |parent_node| at |index|.
 // |copy| indicates the source operation: if true then the bookmarks in |data|
diff --git a/chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.mm b/chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.mm
index 711e219d..c81dbbf 100644
--- a/chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.mm
@@ -117,6 +117,7 @@
 
 - (void)updateWebContents:(content::WebContents*)contents {
   [defaultTouchBar_ updateWebContents:contents];
+  [webTextfieldTouchBar_ updateWebContents:contents];
   [self invalidateTouchBar];
 }
 
diff --git a/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h
new file mode 100644
index 0000000..288fb93
--- /dev/null
+++ b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h
@@ -0,0 +1,78 @@
+// 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_COCOA_TOUCHBAR_TEXT_SUGGESTIONS_TOUCH_BAR_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_TOUCHBAR_TEXT_SUGGESTIONS_TOUCH_BAR_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include <memory>
+
+#import "base/mac/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h"
+#import "ui/base/cocoa/touch_bar_forward_declarations.h"
+
+@class WebTextfieldTouchBarController;
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace gfx {
+class Range;
+}  // namespace gfx
+
+API_AVAILABLE(macos(10.12.2))
+@interface TextSuggestionsTouchBarController
+    : NSObject<NSTouchBarDelegate, NSCandidateListTouchBarItemDelegate>
+
+- (instancetype)initWithWebContents:(content::WebContents*)webContents
+                         controller:(WebTextfieldTouchBarController*)controller;
+
+- (NSTouchBar*)makeTouchBar;
+
+- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
+      makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier;
+
+// Creates a NSCandidateListTouchBarItem that contains text suggestions
+// based on the current text selection.
+- (NSCandidateListTouchBarItem*)makeCandidateListItem;
+
+- (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem*)anItem
+     endSelectingCandidateAtIndex:(NSInteger)index;
+
+- (void)updateTextSelection:(const base::string16&)text
+                      range:(const gfx::Range&)range
+                     offset:(size_t)offset;
+
+// Returns a range from start to the end of the word that the cursor is
+// currently in.
+- (NSRange)editingWordRangeFromText:(const base::string16&)text
+                     cursorPosition:(size_t)cursor;
+
+- (void)requestSuggestions;
+
+// Select the range of the editing word and replace it with a suggestion
+// from the touch bar.
+- (void)replaceEditingWordWithSuggestion:(NSString*)text;
+
+@end
+
+@interface TextSuggestionsTouchBarController (ExposedForTesting)
+
+- (void)setWebContents:(content::WebContents*)webContents;
+- (content::WebContents*)webContents;
+- (void)setText:(NSString*)text;
+- (NSString*)text;
+- (void)setSelectionRange:(const gfx::Range&)range;
+- (gfx::Range)selectionRange;
+- (void)setSuggestions:(NSArray*)suggestions;
+- (NSArray*)suggestions;
+- (WebTextfieldTouchBarController*)controller;
+- (void)setShouldIgnoreReplacementSelection:(BOOL)shouldIgnore;
+- (void)setEditingWordRange:(const gfx::Range&)range offset:(size_t)offset;
+
+@end
+
+#endif  // CHROME_BROWSER_UI_COCOA_TOUCHBAR_SUGGESTED_TEXT_TOUCH_BAR_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.mm b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.mm
new file mode 100644
index 0000000..7f3fb1b
--- /dev/null
+++ b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.mm
@@ -0,0 +1,322 @@
+// 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.
+
+#import "chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h"
+
+#include "base/i18n/break_iterator.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#import "ui/base/cocoa/touch_bar_util.h"
+#include "ui/gfx/range/range.h"
+
+namespace {
+// Touch bar identifier.
+NSString* const kTextSuggestionsTouchBarId = @"text-suggestions";
+
+// Touch bar item identifiers.
+NSString* const kTextSuggestionsItemsTouchId = @"TEXT-SUGGESTIONS-ITEMS";
+}  // namespace
+
+namespace text_observer {
+class API_AVAILABLE(macos(10.12.2)) WebContentsTextObserver
+    : public content::WebContentsObserver {
+ public:
+  WebContentsTextObserver(content::WebContents* web_contents,
+                          TextSuggestionsTouchBarController* owner)
+      : WebContentsObserver(web_contents), owner_(owner) {}
+
+  void UpdateWebContents(content::WebContents* web_contents) {
+    Observe(web_contents);
+  }
+
+  void DidChangeTextSelection(const base::string16& text,
+                              const gfx::Range& range,
+                              size_t offset) override {
+    [owner_ updateTextSelection:text range:range offset:offset];
+  }
+
+  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override {
+    [owner_ updateTextSelection:base::string16() range:gfx::Range() offset:0];
+  }
+
+ private:
+  TextSuggestionsTouchBarController* owner_;  // weak
+};
+}  // namespace text_observer
+
+@interface TextSuggestionsTouchBarController () {
+  // An observer for text selection changes.
+  std::unique_ptr<text_observer::WebContentsTextObserver> observer_;
+
+  // The WebContents in which text is being selected and replaced.
+  content::WebContents* webContents_;  // weak
+
+  // The WebTextfieldTouchBarController that invalidates the touch bar.
+  WebTextfieldTouchBarController* controller_;  // weak
+
+  // The text on which suggestions are based.
+  base::scoped_nsobject<NSString> text_;
+
+  // A list of NSTextCheckingResults containing text suggestions for |text_| at
+  // |selectionRange_|.
+  base::scoped_nsobject<NSArray> suggestions_;
+
+  // The currently selected range. If the range has length = 0, it is simply a
+  // cursor position.
+  NSRange selectionRange_;
+
+  // The range of the word that's currently being edited. Used when replacing
+  // the current editing word with a selected suggestion. If length = 0, there
+  // is no word currently being edited and the suggestion will be placed at the
+  // cursor.
+  NSRange editingWordRange_;
+
+  // The location of |editingWordRange_| within the total text, which may be
+  // longer than the text received on text selection update. Used for checking
+  // when to ignore replacement text selections.
+  NSRange offsetEditingWordRange_;
+
+  // When YES, -updateTextSelection:range: should ignore a text selection that
+  // is equal to the editing word range. Set to YES when
+  // -replaceEditingWordWithSuggestion: modifies the current text selection.
+  // Reset to NO when the text selection for replacing the editing word reaches
+  // -updateTextSelection:range:.
+  BOOL shouldIgnoreReplacementSelection_;
+}
+@end
+
+@implementation TextSuggestionsTouchBarController
+
+- (BOOL)isTextfieldFocused {
+  return webContents_ && webContents_->IsFocusedElementEditable();
+}
+
+- (instancetype)initWithWebContents:(content::WebContents*)webContents
+                         controller:
+                             (WebTextfieldTouchBarController*)controller {
+  if ((self = [super init])) {
+    webContents_ = webContents;
+    controller_ = controller;
+    observer_.reset(
+        new text_observer::WebContentsTextObserver(webContents_, self));
+    shouldIgnoreReplacementSelection_ = NO;
+  }
+
+  return self;
+}
+
+- (NSTouchBar*)makeTouchBar {
+  if (![self isTextfieldFocused] || ![suggestions_ count])
+    return nil;
+
+  base::scoped_nsobject<NSTouchBar> touchBar([[ui::NSTouchBar() alloc] init]);
+  [touchBar
+      setCustomizationIdentifier:ui::GetTouchBarId(kTextSuggestionsTouchBarId)];
+  [touchBar setDelegate:self];
+
+  [touchBar setDefaultItemIdentifiers:@[ kTextSuggestionsItemsTouchId ]];
+  return touchBar.autorelease();
+}
+
+- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
+      makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
+  if (![identifier hasSuffix:kTextSuggestionsItemsTouchId])
+    return nil;
+
+  return [self makeCandidateListItem];
+}
+
+- (NSCandidateListTouchBarItem*)makeCandidateListItem {
+  base::scoped_nsobject<NSCandidateListTouchBarItem> candidateListItem(
+      [[NSCandidateListTouchBarItem alloc]
+          initWithIdentifier:kTextSuggestionsItemsTouchId]);
+
+  [candidateListItem setDelegate:self];
+  if (selectionRange_.length)
+    [candidateListItem setCollapsed:YES];
+
+  [candidateListItem setCandidates:suggestions_
+                  forSelectedRange:selectionRange_
+                          inString:text_];
+
+  return candidateListItem.autorelease();
+}
+
+- (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem*)anItem
+     endSelectingCandidateAtIndex:(NSInteger)index {
+  if (index == NSNotFound)
+    return;
+
+  if (anItem) {
+    NSTextCheckingResult* selectedResult = anItem.candidates[index];
+    [self replaceEditingWordWithSuggestion:[selectedResult replacementString]];
+  }
+
+  ui::LogTouchBarUMA(ui::TouchBarAction::TEXT_SUGGESTION);
+}
+
+- (void)updateTextSelection:(const base::string16&)text
+                      range:(const gfx::Range&)range
+                     offset:(size_t)offset {
+  if (shouldIgnoreReplacementSelection_ &&
+      range == gfx::Range(offsetEditingWordRange_)) {
+    shouldIgnoreReplacementSelection_ = NO;
+    return;
+  }
+
+  if (![self isTextfieldFocused]) {
+    [controller_ invalidateTouchBar];
+    return;
+  }
+
+  // TODO(crbug.com/880642): It's possible for a range out of the text bounds
+  // to be passed in. Investigate this.
+  if (range.start() - offset > text.length() ||
+      range.end() - offset > text.length()) {
+    text_.reset([[NSString alloc] init]);
+    selectionRange_ = NSMakeRange(0, 0);
+    editingWordRange_ = NSMakeRange(0, 0);
+    offsetEditingWordRange_ = NSMakeRange(0, 0);
+    return;
+  }
+
+  text_.reset([base::SysUTF16ToNSString(text) retain]);
+  selectionRange_ = range.ToNSRange();
+  selectionRange_.location -= offset;
+
+  editingWordRange_ = [self editingWordRangeFromText:text
+                                      cursorPosition:selectionRange_.location];
+
+  offsetEditingWordRange_ = editingWordRange_;
+  offsetEditingWordRange_.location += offset;
+  [self requestSuggestions];
+}
+
+- (NSRange)editingWordRangeFromText:(const base::string16&)text
+                     cursorPosition:(size_t)cursor {
+  // The cursor should not be off the end of the text.
+  DCHECK(cursor <= text.length());
+
+  // Default range is just the cursor position. This is used if BreakIterator
+  // cannot be initialized, there is no text in the textfield, or the cursor is
+  // at the front of a word.
+  size_t location = cursor;
+  size_t length = 0;
+
+  base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
+
+  if (iter.Init()) {
+    // Repeat iter.Advance() until end of line is reached or
+    // current iterator position passes cursor position.
+    while (iter.pos() < cursor && iter.Advance()) {
+    }
+
+    // If BreakIterator stopped at the end of a word, the cursor is in/at the
+    // end of a word so the editing word range is [word start, word end].
+    if (iter.IsWord()) {
+      location = iter.prev();
+      length = cursor - iter.prev();
+    }
+  }
+
+  return NSMakeRange(location, length);
+}
+
+- (void)requestSuggestions {
+  NSSpellChecker* spell_checker = [NSSpellChecker sharedSpellChecker];
+  [spell_checker
+      requestCandidatesForSelectedRange:selectionRange_
+                               inString:text_
+                                  types:NSTextCheckingAllSystemTypes
+                                options:nil
+                 inSpellDocumentWithTag:0
+                      completionHandler:^(
+                          NSInteger sequenceNumber,
+                          NSArray<NSTextCheckingResult*>* candidates) {
+                        dispatch_async(dispatch_get_main_queue(), ^{
+                          suggestions_.reset([candidates copy]);
+                          [controller_ invalidateTouchBar];
+                        });
+                      }];
+}
+
+- (void)replaceEditingWordWithSuggestion:(NSString*)text {
+  // If the editing word is not selected in its entirety, modify the selection
+  // to cover the current editing word.
+  if (!NSEqualRanges(editingWordRange_, selectionRange_)) {
+    shouldIgnoreReplacementSelection_ = YES;
+    int start_adjust = editingWordRange_.location - selectionRange_.location;
+    int end_adjust = (editingWordRange_.location + editingWordRange_.length) -
+                     (selectionRange_.location + selectionRange_.length);
+    webContents_->AdjustSelectionByCharacterOffset(start_adjust, end_adjust,
+                                                   false);
+  }
+  webContents_->Replace(base::SysNSStringToUTF16(text));
+}
+
+- (void)setWebContents:(content::WebContents*)webContents {
+  webContents_ = webContents;
+  observer_->UpdateWebContents(webContents);
+
+  if (![self isTextfieldFocused]) {
+    [controller_ invalidateTouchBar];
+    return;
+  }
+
+  const base::string16 text =
+      webContents_->GetTopLevelRenderWidgetHostView()->GetSurroundingText();
+  const gfx::Range range =
+      webContents_->GetTopLevelRenderWidgetHostView()->GetSelectedRange();
+  const size_t offset = webContents_->GetTopLevelRenderWidgetHostView()
+                            ->GetOffsetForSurroundingText();
+
+  [self updateTextSelection:text range:range offset:offset];
+}
+
+- (content::WebContents*)webContents {
+  return webContents_;
+}
+
+- (void)setText:(NSString*)text {
+  text_.reset([text copy]);
+}
+
+- (NSString*)text {
+  return text_;
+}
+
+- (void)setSelectionRange:(const gfx::Range&)range {
+  selectionRange_ = range.ToNSRange();
+}
+
+- (gfx::Range)selectionRange {
+  return gfx::Range(selectionRange_);
+}
+
+- (void)setSuggestions:(NSArray*)suggestions {
+  suggestions_.reset([suggestions copy]);
+}
+
+- (NSArray*)suggestions {
+  return suggestions_;
+}
+
+- (WebTextfieldTouchBarController*)controller {
+  return controller_;
+}
+
+- (void)setShouldIgnoreReplacementSelection:(BOOL)shouldIgnore {
+  shouldIgnoreReplacementSelection_ = shouldIgnore;
+}
+
+- (void)setEditingWordRange:(const gfx::Range&)range offset:(size_t)offset {
+  editingWordRange_ = range.ToNSRange();
+  offsetEditingWordRange_ = NSMakeRange(editingWordRange_.location + offset,
+                                        editingWordRange_.length);
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_browsertest.mm b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_browsertest.mm
new file mode 100644
index 0000000..bc5949f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_browsertest.mm
@@ -0,0 +1,327 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
+#import "chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h"
+#include "chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "ui/base/cocoa/touch_bar_util.h"
+
+API_AVAILABLE(macos(10.12.2))
+@interface MockWebTextfieldTouchBarController : WebTextfieldTouchBarController {
+  // Counter for the number of times invalidateTouchBar is called.
+  int numInvalidations_;
+}
+- (int)numInvalidations;
+
+- (void)resetNumInvalidations;
+
+@end
+
+@implementation MockWebTextfieldTouchBarController
+
+- (void)invalidateTouchBar {
+  numInvalidations_++;
+}
+
+- (int)numInvalidations {
+  return numInvalidations_;
+}
+
+- (void)resetNumInvalidations {
+  numInvalidations_ = 0;
+}
+
+@end
+
+API_AVAILABLE(macos(10.12.2))
+@interface MockTextSuggestionsTouchBarController
+    : TextSuggestionsTouchBarController
+- (NSString*)firstSuggestion;
+@end
+
+@implementation MockTextSuggestionsTouchBarController
+
+- (void)requestSuggestions {
+  [self setSuggestions:@[ [self text] ]];
+  [[self controller] invalidateTouchBar];
+}
+
+- (NSString*)firstSuggestion {
+  return [self suggestions][0];
+}
+
+@end
+
+namespace {
+
+class TextSuggestionsTouchBarControllerTest : public InProcessBrowserTest {
+ public:
+  void SetUp() override {
+    InProcessBrowserTest::SetUp();
+    feature_list_.InitAndEnableFeature(features::kTextSuggestionsTouchBar);
+  }
+
+  void SetUpOnMainThread() override {
+    if (@available(macOS 10.12.2, *)) {
+      web_textfield_controller_.reset(
+          [[MockWebTextfieldTouchBarController alloc] init]);
+      [web_textfield_controller_ resetNumInvalidations];
+      touch_bar_controller_.reset([[MockTextSuggestionsTouchBarController alloc]
+          initWithWebContents:GetActiveWebContents()
+                   controller:web_textfield_controller_]);
+    }
+  }
+
+  void FocusTextfield() {
+    content::WindowedNotificationObserver focus_observer(
+        content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
+        content::NotificationService::AllSources());
+    ui_test_utils::NavigateToURL(
+        browser(),
+        GURL("data:text/html;charset=utf-8,<input type=\"text\" autofocus>"));
+    focus_observer.Wait();
+  }
+
+  void UnfocusTextfield() {
+    ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
+  }
+
+  content::WebContents* GetActiveWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  API_AVAILABLE(macos(10.12.2))
+  base::scoped_nsobject<MockWebTextfieldTouchBarController>
+      web_textfield_controller_;
+
+  API_AVAILABLE(macos(10.12.2))
+  base::scoped_nsobject<MockTextSuggestionsTouchBarController>
+      touch_bar_controller_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests to check if the touch bar shows up properly.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest, MakeTouchBar) {
+  if (@available(macOS 10.12.2, *)) {
+    NSString* const kTextSuggestionsTouchBarId = @"text-suggestions";
+    NSArray* const kSuggestions = @[ @"text" ];
+
+    // Touch bar shouldn't appear if the focused element is not a textfield.
+    UnfocusTextfield();
+    [touch_bar_controller_ setSuggestions:kSuggestions];
+    EXPECT_FALSE([touch_bar_controller_ makeTouchBar]);
+
+    // Touch bar shouldn't appear if there are no suggestions.
+    FocusTextfield();
+    [touch_bar_controller_ setSuggestions:[NSArray array]];
+    EXPECT_FALSE([touch_bar_controller_ makeTouchBar]);
+
+    // Touch bar should appear if textfield is focused and there are
+    // suggestions.
+    [touch_bar_controller_ setSuggestions:kSuggestions];
+    NSTouchBar* touch_bar = [touch_bar_controller_ makeTouchBar];
+    EXPECT_TRUE(touch_bar);
+    EXPECT_TRUE([[touch_bar customizationIdentifier]
+        isEqual:ui::GetTouchBarId(kTextSuggestionsTouchBarId)]);
+  }
+}
+
+// Tests that a change in text selection is handled properly.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest,
+                       UpdateTextSelection) {
+  if (@available(macOS 10.12.2, *)) {
+    NSString* const kText = @"text";
+    NSString* const kEmptyText = @"";
+    const gfx::Range kRange = gfx::Range(0, 4);
+    const gfx::Range kEmptyRange = gfx::Range();
+
+    // If not in a textfield,
+    //  1. Textfield text should not be saved.
+    //  2. Selected range should not be saved.
+    //  3. Touch bar should be invalidated (if on MacOS 10.12.2 or later).
+    UnfocusTextfield();
+    [touch_bar_controller_ setText:kEmptyText];
+    [touch_bar_controller_ setSelectionRange:kEmptyRange];
+    [web_textfield_controller_ resetNumInvalidations];
+
+    [touch_bar_controller_ updateTextSelection:base::SysNSStringToUTF16(kText)
+                                         range:kRange
+                                        offset:0];
+    EXPECT_STREQ(kEmptyText.UTF8String,
+                 [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kEmptyRange, [touch_bar_controller_ selectionRange]);
+    EXPECT_EQ(1, [web_textfield_controller_ numInvalidations]);
+
+    // If in a textfield and on MacOS 10.12.2 or later,
+    //   1. Textfield text should be saved.
+    //   2. Selected range should be saved.
+    //   3. Suggestions should be generated based on text selection.
+    //   4. Touch bar should be invalidated.
+    FocusTextfield();
+    [touch_bar_controller_ setText:kEmptyText];
+    [touch_bar_controller_ setSelectionRange:kEmptyRange];
+    [web_textfield_controller_ resetNumInvalidations];
+
+    [touch_bar_controller_ updateTextSelection:base::SysNSStringToUTF16(kText)
+                                         range:kRange
+                                        offset:0];
+    EXPECT_STREQ(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kRange, [touch_bar_controller_ selectionRange]);
+    EXPECT_STREQ(kText.UTF8String,
+                 [touch_bar_controller_ firstSuggestion].UTF8String);
+    EXPECT_EQ(1, [web_textfield_controller_ numInvalidations]);
+  }
+}
+
+// Tests that a range outside of the text bounds will set the selection range
+// to empty.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest,
+                       RangeOutOfTextBounds) {
+  if (@available(macOS 10.12.2, *)) {
+    FocusTextfield();
+    [touch_bar_controller_ setText:@""];
+    [touch_bar_controller_ setSelectionRange:gfx::Range()];
+
+    [touch_bar_controller_
+        updateTextSelection:base::string16(base::ASCIIToUTF16("text"))
+                      range:gfx::Range(4, 5)
+                     offset:0];
+    EXPECT_STREQ("", [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+
+    [touch_bar_controller_
+        updateTextSelection:base::string16(base::ASCIIToUTF16("text"))
+                      range:gfx::Range(2, 5)
+                     offset:0];
+    EXPECT_STREQ("", [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+  }
+}
+
+// Tests that a change in WebContents is handled properly.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest, SetWebContents) {
+  if (@available(macOS 10.12.2, *)) {
+    NSString* const kText = @"text";
+    const gfx::Range kRange = gfx::Range(1, 1);
+
+    FocusTextfield();
+
+    // A null-pointer should not break the controller.
+    [touch_bar_controller_ setWebContents:nullptr];
+    EXPECT_FALSE([touch_bar_controller_ webContents]);
+
+    [touch_bar_controller_ setText:kText];
+    [touch_bar_controller_ setSelectionRange:kRange];
+
+    // The text selection should change on MacOS 10.12.2 and later if the
+    // WebContents pointer is not null.
+    [touch_bar_controller_ setWebContents:GetActiveWebContents()];
+    EXPECT_EQ(GetActiveWebContents(), [touch_bar_controller_ webContents]);
+    EXPECT_STRNE(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_NE(kRange, [touch_bar_controller_ selectionRange]);
+  }
+}
+
+// Tests that the selection created when replacing the editing word with a
+// suggestion is ignored.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest,
+                       IgnoreReplacementSelection) {
+  if (@available(macOS 10.12.2, *)) {
+    NSString* const kText = @"text";
+    const base::string16 kEmptyText = base::string16();
+    const gfx::Range kRange = gfx::Range(0, 4);
+    const gfx::Range kEmptyRange = gfx::Range();
+
+    FocusTextfield();
+    [touch_bar_controller_ setText:kText];
+    [touch_bar_controller_ setSelectionRange:kRange];
+
+    // If ignoreReplacementSelection is YES and new selection range is equal to
+    // editing word range, ignore text selection update.
+    [touch_bar_controller_ setShouldIgnoreReplacementSelection:YES];
+    [touch_bar_controller_ setEditingWordRange:kEmptyRange offset:0];
+    [touch_bar_controller_ updateTextSelection:kEmptyText
+                                         range:kEmptyRange
+                                        offset:0];
+    EXPECT_STREQ(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kRange, [touch_bar_controller_ selectionRange]);
+
+    // If ignoreReplacementSelection is YES but new selection range is not equal
+    // to editing word range, do not ignore text selection update.
+    [touch_bar_controller_ setShouldIgnoreReplacementSelection:YES];
+    [touch_bar_controller_ setEditingWordRange:kRange offset:0];
+    [touch_bar_controller_ updateTextSelection:kEmptyText
+                                         range:kEmptyRange
+                                        offset:0];
+    EXPECT_STREQ("", [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+
+    [touch_bar_controller_ setText:kText];
+    [touch_bar_controller_ setSelectionRange:kRange];
+
+    // If ignoreReplacementSelection is NO and new selection range is equal to
+    // editing word range, do not ignore text selection update.
+    [touch_bar_controller_ setShouldIgnoreReplacementSelection:NO];
+    [touch_bar_controller_ setEditingWordRange:kEmptyRange offset:0];
+    [touch_bar_controller_ updateTextSelection:kEmptyText
+                                         range:kEmptyRange
+                                        offset:0];
+    EXPECT_STREQ("", [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+  }
+}
+
+// Tests that offsets are properly handled.
+IN_PROC_BROWSER_TEST_F(TextSuggestionsTouchBarControllerTest, Offset) {
+  if (@available(macOS 10.12.2, *)) {
+    const base::string16 kText =
+        base::string16(base::ASCIIToUTF16("hello world"));
+    const gfx::Range kRange = gfx::Range(3, 3);
+    const size_t kOffset = 1;
+    const gfx::Range kOffsetRange =
+        gfx::Range(kRange.start() - kOffset, kRange.end() - kOffset);
+
+    FocusTextfield();
+    [touch_bar_controller_ setSelectionRange:gfx::Range()];
+
+    // |selectionRange_| should include offset.
+    [touch_bar_controller_ updateTextSelection:kText
+                                         range:kRange
+                                        offset:kOffset];
+    if (@available(macOS 10.12.2, *))
+      EXPECT_EQ(kOffsetRange, [touch_bar_controller_ selectionRange]);
+    else
+      EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+
+    // The check for ignoring text selection updates should still work with
+    // offsets.
+    [touch_bar_controller_ setShouldIgnoreReplacementSelection:YES];
+    [touch_bar_controller_ updateTextSelection:kText
+                                         range:gfx::Range(1, 7)
+                                        offset:kOffset];
+    if (@available(macOS 10.12.2, *))
+      EXPECT_EQ(gfx::Range(0, 6), [touch_bar_controller_ selectionRange]);
+    else
+      EXPECT_EQ(gfx::Range(), [touch_bar_controller_ selectionRange]);
+  }
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_unittest.mm b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_unittest.mm
new file mode 100644
index 0000000..feb982f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_unittest.mm
@@ -0,0 +1,155 @@
+// 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
+#import "chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "ui/base/cocoa/touch_bar_util.h"
+#include "ui/gfx/range/range.h"
+
+const base::string16 kEmptyText(base::ASCIIToUTF16(""));
+const base::string16 kWord(base::ASCIIToUTF16("hello"));
+const base::string16 kWordWithTrailingWhitespace(base::ASCIIToUTF16("hello "));
+const base::string16 kWordWithLeadingWhitespace(base::ASCIIToUTF16(" hello"));
+const base::string16 kMultipleWords(base::ASCIIToUTF16("hello world"));
+const base::string16 kWhitespace(base::ASCIIToUTF16("     "));
+
+class TextSuggestionsTouchBarControllerTest : public CocoaTest {
+ public:
+  void SetUp() override {
+    CocoaTest::SetUp();
+    if (@available(macOS 10.12.2, *))
+      controller_.reset([[TextSuggestionsTouchBarController alloc] init]);
+  }
+
+  gfx::Range GetEditingWordRange(const base::string16& text, size_t cursor)
+      API_AVAILABLE(macos(10.12.2)) {
+    NSRange range =
+        [controller_.get() editingWordRangeFromText:text cursorPosition:cursor];
+    return gfx::Range(range);
+  }
+
+  API_AVAILABLE(macos(10.12.2))
+  base::scoped_nsobject<TextSuggestionsTouchBarController> controller_;
+};
+
+// Tests that the NSCandidateListTouchBarItem collapses properly.
+TEST_F(TextSuggestionsTouchBarControllerTest, CollapsedCandidateList) {
+  if (@available(macOS 10.12.2, *)) {
+    [controller_ setSelectionRange:gfx::Range()];
+    EXPECT_FALSE([[controller_ makeCandidateListItem] isCollapsed]);
+
+    [controller_ setSelectionRange:gfx::Range(0, 1)];
+    EXPECT_TRUE([[controller_ makeCandidateListItem] isCollapsed]);
+  }
+}
+
+// Tests that the editing word range is simply the cursor position if there
+// is no text.
+TEST_F(TextSuggestionsTouchBarControllerTest, EmptyTextEditingWordRange) {
+  if (@available(macOS 10.12.2, *))
+    EXPECT_EQ(gfx::Range(0, 0), GetEditingWordRange(kEmptyText, 0));
+}
+
+// Tests that the editing word range contains the full word as the cursor
+// moves through a word without breaks.
+TEST_F(TextSuggestionsTouchBarControllerTest, WordEditingWordRange) {
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_EQ(gfx::Range(0, 0), GetEditingWordRange(kWord, 0));
+    EXPECT_EQ(gfx::Range(0, 1), GetEditingWordRange(kWord, 1));
+    EXPECT_EQ(gfx::Range(0, 2), GetEditingWordRange(kWord, 2));
+    EXPECT_EQ(gfx::Range(0, 3), GetEditingWordRange(kWord, 3));
+    EXPECT_EQ(gfx::Range(0, 4), GetEditingWordRange(kWord, 4));
+  }
+}
+
+// Tests that the editing word range is properly calculated as the cursor moves
+// through non-word characters.
+TEST_F(TextSuggestionsTouchBarControllerTest, WhitespaceEditingWordRange) {
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_EQ(gfx::Range(0, 0), GetEditingWordRange(kWhitespace, 0));
+    EXPECT_EQ(gfx::Range(1, 1), GetEditingWordRange(kWhitespace, 1));
+    EXPECT_EQ(gfx::Range(2, 2), GetEditingWordRange(kWhitespace, 2));
+    EXPECT_EQ(gfx::Range(3, 3), GetEditingWordRange(kWhitespace, 3));
+    EXPECT_EQ(gfx::Range(4, 4), GetEditingWordRange(kWhitespace, 4));
+    EXPECT_EQ(gfx::Range(5, 5), GetEditingWordRange(kWhitespace, 5));
+  }
+}
+
+// Tests that the editing word range changes properly as the cursor moves
+// from word to non-word characters.
+TEST_F(TextSuggestionsTouchBarControllerTest,
+       TrailingWhitespaceEditingWordRange) {
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_EQ(gfx::Range(0, 0),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 0));
+    EXPECT_EQ(gfx::Range(0, 1),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 1));
+    EXPECT_EQ(gfx::Range(0, 2),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 2));
+    EXPECT_EQ(gfx::Range(0, 3),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 3));
+    EXPECT_EQ(gfx::Range(0, 4),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 4));
+    EXPECT_EQ(gfx::Range(0, 5),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 5));
+    EXPECT_EQ(gfx::Range(6, 6),
+              GetEditingWordRange(kWordWithTrailingWhitespace, 6));
+  }
+}
+
+// Tests that the editing word range changes properly as the cursor moves
+// from non-word to word characters.
+TEST_F(TextSuggestionsTouchBarControllerTest,
+       LeadingWhitespaceEditingWordRange) {
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_EQ(gfx::Range(0, 0),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 0));
+    EXPECT_EQ(gfx::Range(1, 1),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 1));
+    EXPECT_EQ(gfx::Range(1, 2),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 2));
+    EXPECT_EQ(gfx::Range(1, 3),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 3));
+    EXPECT_EQ(gfx::Range(1, 4),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 4));
+    EXPECT_EQ(gfx::Range(1, 5),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 5));
+    EXPECT_EQ(gfx::Range(1, 6),
+              GetEditingWordRange(kWordWithLeadingWhitespace, 6));
+  }
+}
+
+// Tests that the editing word range is properly calculated as the cursor moves
+// from word to non-word and back to word characters.
+TEST_F(TextSuggestionsTouchBarControllerTest, MultipleWordsEditingWordRange) {
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_EQ(gfx::Range(0, 0), GetEditingWordRange(kMultipleWords, 0));
+    EXPECT_EQ(gfx::Range(0, 1), GetEditingWordRange(kMultipleWords, 1));
+    EXPECT_EQ(gfx::Range(0, 2), GetEditingWordRange(kMultipleWords, 2));
+    EXPECT_EQ(gfx::Range(0, 3), GetEditingWordRange(kMultipleWords, 3));
+    EXPECT_EQ(gfx::Range(0, 4), GetEditingWordRange(kMultipleWords, 4));
+    EXPECT_EQ(gfx::Range(0, 5), GetEditingWordRange(kMultipleWords, 5));
+    EXPECT_EQ(gfx::Range(6, 6), GetEditingWordRange(kMultipleWords, 6));
+    EXPECT_EQ(gfx::Range(6, 7), GetEditingWordRange(kMultipleWords, 7));
+    EXPECT_EQ(gfx::Range(6, 8), GetEditingWordRange(kMultipleWords, 8));
+    EXPECT_EQ(gfx::Range(6, 9), GetEditingWordRange(kMultipleWords, 9));
+    EXPECT_EQ(gfx::Range(6, 10), GetEditingWordRange(kMultipleWords, 10));
+    EXPECT_EQ(gfx::Range(6, 11), GetEditingWordRange(kMultipleWords, 11));
+  }
+}
+
+// Tests that touch bar usage is properly logged.
+TEST_F(TextSuggestionsTouchBarControllerTest, TouchBarMetrics) {
+  if (@available(macOS 10.12.2, *)) {
+    base::HistogramTester histogram_tester;
+    [controller_ candidateListTouchBarItem:nil endSelectingCandidateAtIndex:1];
+    histogram_tester.ExpectBucketCount("TouchBar.Default.Metrics",
+                                       ui::TouchBarAction::TEXT_SUGGESTION, 1);
+  }
+}
diff --git a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h
index d5fb4be6..622cd75 100644
--- a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h
+++ b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.h
@@ -14,6 +14,7 @@
 
 @class BrowserWindowTouchBarController;
 @class CreditCardAutofillTouchBarController;
+@class TextSuggestionsTouchBarController;
 @class TabContentsController;
 
 namespace autofill {
@@ -31,6 +32,8 @@
   BrowserWindowTouchBarController* controller_;  // weak.
   base::scoped_nsobject<CreditCardAutofillTouchBarController>
       autofillTouchBarController_;
+  base::scoped_nsobject<TextSuggestionsTouchBarController>
+      textSuggestionsTouchBarController_;
 }
 
 + (WebTextfieldTouchBarController*)controllerForWindow:(NSWindow*)window;
@@ -43,6 +46,8 @@
 
 - (void)hideCreditCardAutofillTouchBar;
 
+- (void)updateWebContents:(content::WebContents*)contents;
+
 - (void)invalidateTouchBar;
 
 // Creates and returns a touch bar.
diff --git a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
index b8f98d68..76a9101 100644
--- a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
@@ -12,10 +12,13 @@
 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
 #import "chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.h"
 #import "chrome/browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller.h"
+#import "chrome/browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller.h"
 #include "chrome/browser/ui/views/frame/browser_frame_mac.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/common/chrome_features.h"
 #include "content/public/browser/web_contents.h"
 #import "ui/base/cocoa/touch_bar_util.h"
+#include "ui/base/ui_base_features.h"
 
 @implementation WebTextfieldTouchBarController
 
@@ -34,6 +37,14 @@
     (BrowserWindowTouchBarController*)controller {
   if ((self = [super init])) {
     controller_ = controller;
+
+    if (base::FeatureList::IsEnabled(features::kTextSuggestionsTouchBar) ||
+        base::FeatureList::IsEnabled(features::kExperimentalUi)) {
+      textSuggestionsTouchBarController_.reset(
+          [[TextSuggestionsTouchBarController alloc]
+              initWithWebContents:[controller_ webContents]
+                       controller:self]);
+    }
   }
 
   return self;
@@ -57,6 +68,10 @@
   [self invalidateTouchBar];
 }
 
+- (void)updateWebContents:(content::WebContents*)contents {
+  [textSuggestionsTouchBarController_ setWebContents:contents];
+}
+
 - (void)invalidateTouchBar {
   [controller_ invalidateTouchBar];
 }
@@ -64,6 +79,10 @@
 - (NSTouchBar*)makeTouchBar {
   if (autofillTouchBarController_)
     return [autofillTouchBarController_ makeTouchBar];
+
+  if (textSuggestionsTouchBarController_)
+    return [textSuggestionsTouchBarController_ makeTouchBar];
+
   return nil;
 }
 
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 89b0fda..2ef6d8f 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -238,8 +238,6 @@
   return model->IsEnabledAt(index) ? kEnabled : kDisabled;
 }
 
-}  // namespace
-
 class TestAppBannerManagerDesktop : public banners::AppBannerManagerDesktop {
  public:
   explicit TestAppBannerManagerDesktop(WebContents* web_contents)
@@ -296,6 +294,8 @@
   DISALLOW_COPY_AND_ASSIGN(TestAppBannerManagerDesktop);
 };
 
+}  // namespace
+
 // Parameters are {app_type, desktop_pwa_flag}. |app_type| controls whether it
 // is a Hosted or Bookmark app. |desktop_pwa_flag| enables the
 // kDesktopPWAWindowing flag.
@@ -401,6 +401,8 @@
     WebApplicationInfo web_app_info;
     web_app_info.app_url = app_url;
     web_app_info.scope = app_url.GetWithoutFilename();
+    web_app_info.open_as_window = true;
+
     app_ = InstallBookmarkApp(web_app_info);
 
     ui_test_utils::UrlLoadObserver url_observer(
@@ -873,6 +875,64 @@
   EXPECT_FALSE(model->IsEnabledAt(index));
 }
 
+// Tests that desktop PWAs open links in the browser.
+IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest,
+                       DesktopPWAsOpenLinksInAppWhenFeatureEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kDesktopPWAsStayInWindow);
+
+  ASSERT_TRUE(https_server()->Start());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  InstallSecurePWA();
+  ASSERT_TRUE(base::FeatureList::IsEnabled(features::kDesktopPWAsStayInWindow));
+  ASSERT_TRUE(
+      extensions::util::GetInstalledPwaForUrl(profile(), GetSecureAppURL()));
+
+  NavigateToURLAndWait(app_browser_, GetSecureAppURL());
+
+  ASSERT_TRUE(app_browser_->hosted_app_controller());
+
+  NavigateAndCheckForLocationBar(app_browser_, GURL(kExampleURL), true);
+}
+
+// Tests that desktop PWAs open links in the browser.
+IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest,
+                       DesktopPWAsOpenLinksInBrowserWhenFeatureDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(features::kDesktopPWAsStayInWindow);
+
+  ASSERT_TRUE(https_server()->Start());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  InstallSecurePWA();
+  ASSERT_FALSE(
+      base::FeatureList::IsEnabled(features::kDesktopPWAsStayInWindow));
+  ASSERT_TRUE(
+      extensions::util::GetInstalledPwaForUrl(profile(), GetSecureAppURL()));
+
+  NavigateToURLAndWait(app_browser_, GetSecureAppURL());
+
+  ASSERT_TRUE(app_browser_->hosted_app_controller());
+
+  TestAppActionOpensForegroundTab(
+      base::BindOnce(
+          [](Browser* browser, content::WebContents* app_contents,
+             const GURL& target_url) {
+            content::TestNavigationObserver observer(target_url);
+            observer.StartWatchingNewWebContents();
+
+            std::string script = base::StringPrintf("window.location = '%s';",
+                                                    target_url.spec().c_str());
+            ASSERT_TRUE(content::ExecuteScript(app_contents, script));
+
+            observer.WaitForNavigationFinished();
+          },
+          app_browser_, app_browser_->tab_strip_model()->GetActiveWebContents(),
+          GURL(kExampleURL)),
+      GURL(kExampleURL));
+}
+
 // Tests that PWA menus have an uninstall option.
 IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, UninstallMenuOption) {
   ASSERT_TRUE(https_server()->Start());
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
index e16693e..730e43c 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
@@ -37,7 +37,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 #include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gfx/range/range.h"
 #include "ui/gfx/text_utils.h"
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/ui/startup/invalid_user_data_dir_interactive_uitest.cc b/chrome/browser/ui/startup/invalid_user_data_dir_interactive_uitest.cc
new file mode 100644
index 0000000..cc39011
--- /dev/null
+++ b/chrome/browser/ui/startup/invalid_user_data_dir_interactive_uitest.cc
@@ -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.
+
+#include "base/macros.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/simple_message_box_internal.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+// https://crbug.com/833624
+class InvalidUserDataDirTest : public InProcessBrowserTest {
+ public:
+  InvalidUserDataDirTest() {}
+  ~InvalidUserDataDirTest() override {}
+
+ private:
+  void SetUp() override {
+    // Skip showing the error message box to avoid freezing the main thread.
+    chrome::internal::g_should_skip_message_box_for_test = true;
+    chrome::SetInvalidSpecifiedUserDataDir(
+        base::FilePath(FILE_PATH_LITERAL("foo/bar/baz")));
+    InProcessBrowserTest::SetUp();
+  }
+
+  // This override makes sure the screen instance is not set because in normal
+  // browser initialization, the screen is not set until after the call to
+  // chrome::GetInvalidSpecifiedUserDataDir.
+  void SetScreenInstance() override {}
+
+  DISALLOW_COPY_AND_ASSIGN(InvalidUserDataDirTest);
+};
+
+IN_PROC_BROWSER_TEST_F(InvalidUserDataDirTest, Basic) {
+  // A message dialog may be showing which would block shutdown.
+  chrome::Exit();
+}
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc b/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
index 033db6e3..f206f80 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
@@ -5,16 +5,36 @@
 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
 
 #include "base/message_loop/message_loop_current.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
 #include "chrome/browser/ui/views_mode_controller.h"
+#include "chrome/grit/platform_locale_settings.h"
+#include "components/bookmarks/browser/base_bookmark_model_observer.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node_data.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/render_text.h"
+#include "ui/resources/grit/ui_resources.h"
 #include "ui/views/drag_utils.h"
+#include "ui/views/style/platform_style.h"
+#include "ui/views/style/typography.h"
+#include "ui/views/style/typography_provider.h"
 #include "ui/views/widget/widget.h"
 
 using bookmarks::BookmarkModel;
@@ -22,34 +42,277 @@
 
 namespace chrome {
 
-void DragBookmarks(Profile* profile,
-                   const std::vector<const BookmarkNode*>& nodes,
-                   gfx::NativeView view,
-                   ui::DragDropTypes::DragEventSource source) {
-  DCHECK(!nodes.empty());
+namespace {
 
-  // Set up our OLE machinery.
-  ui::OSExchangeData data;
-  bookmarks::BookmarkNodeData drag_data(nodes);
-  drag_data.Write(profile->GetPath(), &data);
+class BookmarkDragHelper;
 
+// Generates a bookmark drag and drop chip image.
+class BookmarkDragImageSource : public gfx::CanvasImageSource {
+ public:
+  // These DIP measurements come from the MD Bookmarks Drag Drop spec.
+  static constexpr int kContainerWidth = 172;
+  static constexpr int kContainerHeight = 40;
+  static constexpr int kContainerRadius = kContainerHeight / 2;
+  static constexpr SkColor kContainerColor = gfx::kGoogleBlue500;
+
+  static constexpr int kIconContainerRadius = 12;
+  static constexpr int kIconSize = 16;
+  static constexpr SkColor kIconContainerColor = SK_ColorWHITE;
+
+  static constexpr int kTitlePadding = 12;
+
+  static constexpr int kCountPadding = 5;
+  static constexpr int kCountContainerRadius = 12;
+  static constexpr SkColor kCountContainerColor = gfx::kGoogleRed500;
+
+  static constexpr gfx::Size kBookmarkDragImageSize =
+      gfx::Size(kContainerWidth, kContainerHeight + kCountContainerRadius);
+
+  static constexpr int kDragImageOffsetX = kContainerWidth / 2;
+  static constexpr int kDragImageOffsetY = 0.9 * kContainerHeight;
+
+  BookmarkDragImageSource(const base::string16& title,
+                          const gfx::ImageSkia& icon,
+                          size_t count)
+      : gfx::CanvasImageSource(kBookmarkDragImageSize, false),
+        title_(title),
+        icon_(icon),
+        count_(count) {}
+
+ private:
+  // gfx::CanvasImageSource overrides:
+  void Draw(gfx::Canvas* canvas) override {
+    cc::PaintFlags paint_flags;
+    paint_flags.setAntiAlias(true);
+
+    // Draw background.
+    gfx::RectF container_rect(0, kCountContainerRadius, kContainerWidth,
+                              kContainerHeight);
+    paint_flags.setColor(kContainerColor);
+    canvas->DrawRoundRect(container_rect, kContainerRadius, paint_flags);
+
+    // Draw icon container.
+    paint_flags.setColor(kIconContainerColor);
+    canvas->DrawCircle(
+        gfx::PointF(kContainerRadius, kContainerRadius + kCountContainerRadius),
+        kIconContainerRadius, paint_flags);
+
+    // Draw icon image.
+    canvas->DrawImageInt(
+        icon_, kContainerRadius - kIconSize / 2,
+        kContainerRadius + kIconContainerRadius - kIconSize / 2);
+
+    // Draw bookmark title.
+    gfx::FontList font_list = views::style::GetFont(
+        views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
+    gfx::Rect text_rect(kBookmarkDragImageSize);
+    text_rect.Inset(kContainerRadius + kIconContainerRadius + kTitlePadding,
+                    kCountContainerRadius,
+                    kContainerRadius - kIconContainerRadius, 0);
+    canvas->DrawStringRectWithFlags(title_, font_list, SK_ColorWHITE, text_rect,
+                                    gfx::Canvas::TEXT_ALIGN_LEFT);
+
+    if (count_ <= 1)
+      return;
+
+    // Draw bookmark count if more than 1 bookmark is dragged.
+    base::string16 count = base::NumberToString16(count_);
+    auto render_text = gfx::RenderText::CreateFor(gfx::Typesetter::BROWSER);
+    render_text->SetFontList(font_list);
+    render_text->SetCursorEnabled(false);
+    render_text->SetColor(SK_ColorWHITE);
+    render_text->SetText(count);
+    render_text->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+
+    // We measure the count text size to determine container width, as the
+    // container is a rounded rect behind the text.
+    int count_width = render_text->GetStringSize().width();
+    int count_container_width =
+        std::max(kCountContainerRadius * 2, count_width + 2 * kCountPadding);
+
+    // Draw the count container.
+    gfx::Rect count_container_rect(
+        container_rect.right() - count_container_width, 0,
+        count_container_width, kCountContainerRadius * 2);
+    paint_flags.setColor(kCountContainerColor);
+    canvas->DrawRoundRect(gfx::RectF(count_container_rect),
+                          kCountContainerRadius, paint_flags);
+
+    // Draw the count text.
+    render_text->SetDisplayRect(count_container_rect);
+    render_text->Draw(canvas);
+  }
+
+  const base::string16 title_;
+  const gfx::ImageSkia icon_;
+  const int count_;
+};
+
+constexpr gfx::Size BookmarkDragImageSource::kBookmarkDragImageSize;
+
+// Helper class that takes a drag request, loads the icon from the bookmark
+// model and then launches a system drag with a generated drag image.
+// Owns itself.
+class BookmarkDragHelper : public bookmarks::BaseBookmarkModelObserver {
+ public:
+  static base::WeakPtr<BookmarkDragHelper> Create(
+      Profile* profile,
+      const BookmarkDragParams& params,
+      DoBookmarkDragCallback do_drag_callback) {
+    base::WeakPtr<BookmarkDragHelper> ptr =
+        (new BookmarkDragHelper(profile, params, std::move(do_drag_callback)))
+            ->GetWeakPtr();
+    ptr->Start(params.nodes.at(params.drag_node_index));
+    return ptr;
+  }
+
+ private:
+  BookmarkDragHelper(Profile* profile,
+                     const BookmarkDragParams& params,
+                     DoBookmarkDragCallback do_drag_callback)
+      : model_(BookmarkModelFactory::GetForBrowserContext(profile)),
+        count_(params.nodes.size()),
+        native_view_(params.view),
+        source_(params.source),
+        do_drag_callback_(std::move(do_drag_callback)),
+        observer_(this),
+        weak_factory_(this) {
+    observer_.Add(model_);
+
+    // Set up our OLE machinery.
+    bookmarks::BookmarkNodeData bookmark_drag_data(params.nodes);
+    bookmark_drag_data.Write(profile->GetPath(), &drag_data_);
+
+    operation_ = ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
+    if (bookmarks::CanAllBeEditedByUser(model_->client(), params.nodes))
+      operation_ |= ui::DragDropTypes::DRAG_MOVE;
+  }
+
+  void Start(const BookmarkNode* drag_node) {
+    drag_node_id_ = drag_node->id();
+
+    gfx::ImageSkia icon;
+    if (drag_node->is_url()) {
+      const gfx::Image& image = model_->GetFavicon(drag_node);
+      // If favicon is not loaded, the above call will initiate loading, and
+      // drag will proceed in BookmarkNodeFaviconChanged(). In rare cases,
+      // BookmarkNodeFaviconChanged() will never be called (e.g unfortunate
+      // bookmark deletion timing) and we intentionally leak at most one request
+      // in these cases which will clean up next drag.
+      if (!drag_node->is_favicon_loaded())
+        return;
+
+      icon = image.AsImageSkia();
+    } else {
+      icon = GetBookmarkFolderIcon(
+          ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
+              ui::NativeTheme::kColorId_LabelEnabledColor));
+    }
+
+    OnBookmarkIconLoaded(drag_node, icon);
+  }
+
+  void OnBookmarkIconLoaded(const BookmarkNode* drag_node,
+                            const gfx::ImageSkia& icon) {
+    gfx::ImageSkia drag_image(
+        std::make_unique<BookmarkDragImageSource>(
+            drag_node->GetTitle(),
+            icon.isNull()
+                ? *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+                      IDR_DEFAULT_FAVICON)
+                : icon,
+            count_),
+        BookmarkDragImageSource::kBookmarkDragImageSize);
+
+    drag_data_.provider().SetDragImage(
+        drag_image, gfx::Vector2d(BookmarkDragImageSource::kDragImageOffsetX,
+                                  BookmarkDragImageSource::kDragImageOffsetY));
+
+    std::move(do_drag_callback_)
+        .Run(drag_data_, native_view_, source_, operation_);
+
+    delete this;
+  }
+
+  base::WeakPtr<BookmarkDragHelper> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+  // bookmarks::BaseBookmarkModelObserver overrides:
+  void BookmarkModelChanged() override {}
+
+  void BookmarkModelBeingDeleted(BookmarkModel* model) override { delete this; }
+
+  void BookmarkNodeFaviconChanged(BookmarkModel* model,
+                                  const BookmarkNode* node) override {
+    if (node->id() != drag_node_id_)
+      return;
+
+    const gfx::Image& image = model_->GetFavicon(node);
+    DCHECK(node->is_favicon_loaded());
+
+    OnBookmarkIconLoaded(node, image.AsImageSkia());
+  }
+
+  BookmarkModel* model_;
+
+  int64_t drag_node_id_ = -1;
+  int count_;
+  gfx::NativeView native_view_;
+  ui::DragDropTypes::DragEventSource source_;
+  int operation_;
+
+  DoBookmarkDragCallback do_drag_callback_;
+
+  ui::OSExchangeData drag_data_;
+
+  ScopedObserver<bookmarks::BookmarkModel, bookmarks::BookmarkModelObserver>
+      observer_;
+
+  base::WeakPtrFactory<BookmarkDragHelper> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(BookmarkDragHelper);
+};
+
+void DoDragImpl(const ui::OSExchangeData& drag_data,
+                gfx::NativeView native_view,
+                ui::DragDropTypes::DragEventSource source,
+                int operation) {
   // Allow nested run loop so we get DnD events as we drag this around.
   base::MessageLoopCurrent::ScopedNestableTaskAllower nestable_task_allower;
 
-  int operation = ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
-  BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
-  if (bookmarks::CanAllBeEditedByUser(model->client(), nodes))
-    operation |= ui::DragDropTypes::DRAG_MOVE;
+  views::Widget* widget = views::Widget::GetWidgetForNativeView(native_view);
+  DCHECK(widget);
+  widget->RunShellDrag(nullptr, drag_data, gfx::Point(), operation, source);
+}
 
-  views::Widget* widget = views::Widget::GetWidgetForNativeView(view);
+void DragBookmarksImpl(Profile* profile,
+                       const BookmarkDragParams& params,
+                       DoBookmarkDragCallback do_drag_callback) {
+  DCHECK(!params.nodes.empty());
+  static base::NoDestructor<base::WeakPtr<BookmarkDragHelper>> g_drag_helper;
+  if (*g_drag_helper)
+    delete g_drag_helper->get();
 
-  if (widget) {
-    widget->RunShellDrag(NULL, data, gfx::Point(), operation, source);
-  } else {
-    // We hit this case when we're using WebContentsViewWin or
-    // WebContentsViewAura, instead of WebContentsViewViews.
-    views::RunShellDrag(view, data, gfx::Point(), operation, source);
-  }
+  DCHECK(!*g_drag_helper);
+
+  // Cleaned up in
+  // BookmarkDragHelper::BookmarkIconLoaded()/BookmarkModelBeingDeleted(), or
+  // above when a new drag is initiated before the favicon loads.
+  *g_drag_helper =
+      BookmarkDragHelper::Create(profile, params, std::move(do_drag_callback));
+}
+
+}  // namespace
+
+void DragBookmarks(Profile* profile, const BookmarkDragParams& params) {
+  DragBookmarksImpl(profile, params, base::BindOnce(&DoDragImpl));
+}
+
+void DragBookmarksForTest(Profile* profile,
+                          const BookmarkDragParams& params,
+                          DoBookmarkDragCallback do_drag_callback) {
+  DragBookmarksImpl(profile, params, std::move(do_drag_callback));
 }
 
 }  // namespace chrome
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
index 0ac480b..621e3fed 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
@@ -74,20 +74,17 @@
 #if defined(USE_AURA)
   wm_state_.reset(new wm::WMState);
 #endif
+
+  // TODO(pkasting): Try to move ViewsDelegate creation here as well;
+  // see https://crbug.com/691894#c1
+  if (!views::LayoutProvider::Get())
+    layout_provider_ = ChromeLayoutProvider::CreateLayoutProvider();
 }
 
 void ChromeBrowserMainExtraPartsViews::PreCreateThreads() {
 #if defined(USE_AURA)
   views::InstallDesktopScreenIfNecessary();
 #endif
-
-  // TODO(pkasting): Try to move ViewsDelegate creation here as well;
-  // see https://crbug.com/691894#c1
-  // The layout_provider_ must be intialized here instead of in
-  // ToolkitInitialized() because it relies on
-  // ui::MaterialDesignController::Intialize() having already been called.
-  if (!views::LayoutProvider::Get())
-    layout_provider_ = ChromeLayoutProvider::CreateLayoutProvider();
 }
 
 void ChromeBrowserMainExtraPartsViews::PreProfileInit() {
diff --git a/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
index f72498d..994f889 100644
--- a/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_uninstall_dialog_view.cc
@@ -43,9 +43,13 @@
       BrowserView::GetBrowserViewForNativeWindow(window);
   if (!browser_view)
     return nullptr;
-  ToolbarActionView* reference_view = browser_view->toolbar_button_provider()
-                                          ->GetBrowserActionsContainer()
-                                          ->GetViewForId(extension_id);
+  DCHECK(browser_view->toolbar_button_provider());
+  BrowserActionsContainer* const browser_actions_container =
+      browser_view->toolbar_button_provider()->GetBrowserActionsContainer();
+  if (!browser_actions_container)
+    return nullptr;
+  ToolbarActionView* const reference_view =
+      browser_actions_container->GetViewForId(extension_id);
   return reference_view && reference_view->visible() ? reference_view : nullptr;
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index 74b1b2c..9d75af83 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -681,7 +681,11 @@
     https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
     ASSERT_TRUE(https_server_.Start());
     ASSERT_TRUE(embedded_test_server()->Start());
+  }
 
+  // |SetUpHostedApp()| must be called after |SetUpOnMainThread()| to make sure
+  // the Network Service process has been setup properly.
+  void SetUpHostedApp() {
     WebApplicationInfo web_app_info;
     web_app_info.app_url = GetAppURL();
     web_app_info.scope = GetAppURL().GetWithoutFilename();
@@ -774,6 +778,7 @@
 // bubble anchor adjustment (see |BubbleDialogDelegateView::CreateBubble()|).
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        PageInfoBubblePosition) {
+  SetUpHostedApp();
   // Resize app window to only take up the left half of the screen.
   views::Widget* widget = browser_view_->GetWidget();
   gfx::Size screen_size =
@@ -796,6 +801,7 @@
 }
 
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, FocusableViews) {
+  SetUpHostedApp();
   EXPECT_TRUE(browser_view_->contents_web_view()->HasFocus());
   browser_view_->GetFocusManager()->AdvanceFocus(false);
   EXPECT_TRUE(app_menu_button_->HasFocus());
@@ -805,6 +811,7 @@
 
 // Tests that a web app's theme color is set.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, ThemeColor) {
+  SetUpHostedApp();
   aura::Window* window = browser_view_->GetWidget()->GetNativeWindow();
   EXPECT_EQ(GetThemeColor(), window->GetProperty(ash::kFrameActiveColorKey));
   EXPECT_EQ(GetThemeColor(), window->GetProperty(ash::kFrameInactiveColorKey));
@@ -814,6 +821,7 @@
 // Make sure that for hosted apps, the height of the frame doesn't exceed the
 // height of the caption buttons.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, FrameSize) {
+  SetUpHostedApp();
   const int inset = GetFrameViewAsh(browser_view_)->GetTopInset(false);
   EXPECT_EQ(inset,
             GetAshLayoutSize(ash::AshLayoutSize::kNonBrowserCaption).height());
@@ -825,12 +833,14 @@
 // provider in this window configuration.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        ToolbarButtonProvider) {
+  SetUpHostedApp();
   EXPECT_EQ(browser_view_->toolbar_button_provider(),
             hosted_app_button_container_);
 }
 
 // Test that the zoom icon appears in the title bar for hosted app windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, ZoomIcon) {
+  SetUpHostedApp();
   content::WebContents* web_contents =
       app_browser_->tab_strip_model()->GetActiveWebContents();
   zoom::ZoomController* zoom_controller =
@@ -850,6 +860,7 @@
 
 // Test that the find icon appears in the title bar for hosted app windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, FindIcon) {
+  SetUpHostedApp();
   PageActionIconView* find_icon = GetPageActionIcon(PageActionIconType::kFind);
 
   EXPECT_TRUE(find_icon);
@@ -864,6 +875,7 @@
 // windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandFocusToolbarAppMenu) {
+  SetUpHostedApp();
   EXPECT_FALSE(app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_TOOLBAR);
   EXPECT_TRUE(app_menu_button_->HasFocus());
@@ -873,6 +885,7 @@
 // the app menu button when present in web app windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandFocusToolbarGeolocation) {
+  SetUpHostedApp();
   ContentSettingImageView* geolocation_icon = GrantGeolocationPermission();
 
   EXPECT_FALSE(app_menu_button_->HasFocus());
@@ -887,6 +900,7 @@
 // Tests that the show app menu command opens the app menu for web app windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandShowAppMenu) {
+  SetUpHostedApp();
   EXPECT_EQ(nullptr, GetAppMenu());
   chrome::ExecuteCommand(app_browser_, IDC_SHOW_APP_MENU);
   EXPECT_NE(nullptr, GetAppMenu());
@@ -896,6 +910,7 @@
 // windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandFocusNextPane) {
+  SetUpHostedApp();
   EXPECT_FALSE(app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_NEXT_PANE);
   EXPECT_TRUE(app_menu_button_->HasFocus());
@@ -905,6 +920,7 @@
 // windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandFocusPreviousPane) {
+  SetUpHostedApp();
   EXPECT_FALSE(app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_PREVIOUS_PANE);
   EXPECT_TRUE(app_menu_button_->HasFocus());
@@ -913,6 +929,7 @@
 // Tests that a web app's content settings icons can be interacted with.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        ContentSettingIcons) {
+  SetUpHostedApp();
   for (auto* view : *content_setting_views_)
     EXPECT_FALSE(view->visible());
 
@@ -940,6 +957,7 @@
 
 // Tests that a web app's browser action icons can be interacted with.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, BrowserActions) {
+  SetUpHostedApp();
   // Even though 2 are visible in the browser, no extension actions should show.
   ToolbarActionsBar* toolbar_actions_bar =
       browser_actions_container_->toolbar_actions_bar();
@@ -965,6 +983,7 @@
 // Regression test for https://crbug.com/839955
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        ActiveStateOfButtonMatchesWidget) {
+  SetUpHostedApp();
   ash::FrameCaptionButtonContainerView::TestApi test(
       GetFrameViewAsh(browser_view_)->caption_button_container_);
   EXPECT_TRUE(test.size_button()->paint_as_active());
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.cc b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
index e8851ad..5da1c4c 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
@@ -80,11 +80,10 @@
 const base::TimeDelta HostedAppButtonContainer::kOriginTotalDuration =
     kOriginFadeInDuration + kOriginPauseDuration + kOriginFadeOutDuration;
 
-class HostedAppButtonContainer::ContentSettingsContainer
-    : public views::View,
-      public ContentSettingImageView::Delegate {
+class HostedAppButtonContainer::ContentSettingsContainer : public views::View {
  public:
-  explicit ContentSettingsContainer(BrowserView* browser_view);
+  explicit ContentSettingsContainer(
+      ContentSettingImageView::Delegate* delegate);
   ~ContentSettingsContainer() override = default;
 
   void UpdateContentSettingViewsVisibility() {
@@ -132,26 +131,9 @@
     PreferredSizeChanged();
   }
 
-  // ContentSettingsImageView::Delegate:
-  content::WebContents* GetContentSettingWebContents() override {
-    return browser_view_->GetActiveWebContents();
-  }
-  ContentSettingBubbleModelDelegate* GetContentSettingBubbleModelDelegate()
-      override {
-    return browser_view_->browser()->content_setting_bubble_model_delegate();
-  }
-  void OnContentSettingImageBubbleShown(
-      ContentSettingImageModel::ImageType type) const override {
-    UMA_HISTOGRAM_ENUMERATION(
-        "HostedAppFrame.ContentSettings.ImagePressed", type,
-        ContentSettingImageModel::ImageType::NUM_IMAGE_TYPES);
-  }
-
   // Owned by the views hierarchy.
   std::vector<ContentSettingImageView*> content_setting_views_;
 
-  BrowserView* browser_view_;
-
   DISALLOW_COPY_AND_ASSIGN(ContentSettingsContainer);
 };
 
@@ -165,12 +147,7 @@
 }
 
 HostedAppButtonContainer::ContentSettingsContainer::ContentSettingsContainer(
-    BrowserView* browser_view)
-    : browser_view_(browser_view) {
-  DCHECK(
-      extensions::HostedAppBrowserController::IsForExperimentalHostedAppBrowser(
-          browser_view->browser()));
-
+    ContentSettingImageView::Delegate* delegate) {
   views::BoxLayout& layout =
       *SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::kHorizontal, gfx::Insets(),
@@ -183,7 +160,7 @@
       ContentSettingImageModel::GenerateContentSettingImageModels();
   for (auto& model : models) {
     auto image_view = std::make_unique<ContentSettingImageView>(
-        std::move(model), this,
+        std::move(model), delegate,
         views::NativeWidgetAura::GetWindowTitleFontList());
     // Padding around content setting icons.
     constexpr int kContentSettingIconInteriorPadding = 4;
@@ -205,7 +182,7 @@
       active_color_(active_color),
       inactive_color_(inactive_color),
       hosted_app_origin_text_(new HostedAppOriginText(browser_view->browser())),
-      content_settings_container_(new ContentSettingsContainer(browser_view)),
+      content_settings_container_(new ContentSettingsContainer(this)),
       page_action_icon_container_view_(new PageActionIconContainerView(
           {PageActionIconType::kFind, PageActionIconType::kZoom},
           GetLayoutConstant(HOSTED_APP_PAGE_ACTION_ICON_SIZE),
@@ -220,6 +197,9 @@
                                       false /* interactive */)),
       app_menu_button_(new HostedAppMenuButton(browser_view)) {
   DCHECK(browser_view_);
+  DCHECK(
+      extensions::HostedAppBrowserController::IsForExperimentalHostedAppBrowser(
+          browser_view_->browser()));
   views::BoxLayout& layout =
       *SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::kHorizontal,
@@ -314,12 +294,20 @@
   g_animation_disabled_for_testing = true;
 }
 
+SkColor HostedAppButtonContainer::GetIconColor() const {
+  return paint_as_active_ ? active_color_ : inactive_color_;
+}
+
+SkColor HostedAppButtonContainer::GetIconInkDropColor() const {
+  return color_utils::IsDark(GetIconColor()) ? SK_ColorBLACK : SK_ColorWHITE;
+}
+
 void HostedAppButtonContainer::UpdateChildrenColor() {
-  SkColor color = paint_as_active_ ? active_color_ : inactive_color_;
-  hosted_app_origin_text_->SetTextColor(color);
-  content_settings_container_->SetIconColor(color);
-  page_action_icon_container_view_->SetIconColor(color);
-  app_menu_button_->SetIconColor(color);
+  SkColor icon_color = GetIconColor();
+  hosted_app_origin_text_->SetTextColor(icon_color);
+  content_settings_container_->SetIconColor(icon_color);
+  page_action_icon_container_view_->SetIconColor(icon_color);
+  app_menu_button_->SetColors(icon_color, GetIconInkDropColor());
 }
 
 gfx::Size HostedAppButtonContainer::CalculatePreferredSize() const {
@@ -371,11 +359,35 @@
                                                       main_bar);
 }
 
+SkColor HostedAppButtonContainer::GetPageActionInkDropColor() const {
+  return GetIconInkDropColor();
+}
+
 content::WebContents*
 HostedAppButtonContainer::GetWebContentsForPageActionIconView() {
   return browser_view_->GetActiveWebContents();
 }
 
+SkColor HostedAppButtonContainer::GetContentSettingInkDropColor() const {
+  return GetIconInkDropColor();
+}
+
+content::WebContents* HostedAppButtonContainer::GetContentSettingWebContents() {
+  return browser_view_->GetActiveWebContents();
+}
+
+ContentSettingBubbleModelDelegate*
+HostedAppButtonContainer::GetContentSettingBubbleModelDelegate() {
+  return browser_view_->browser()->content_setting_bubble_model_delegate();
+}
+
+void HostedAppButtonContainer::OnContentSettingImageBubbleShown(
+    ContentSettingImageModel::ImageType type) const {
+  UMA_HISTOGRAM_ENUMERATION(
+      "HostedAppFrame.ContentSettings.ImagePressed", type,
+      ContentSettingImageModel::ImageType::NUM_IMAGE_TYPES);
+}
+
 BrowserActionsContainer*
 HostedAppButtonContainer::GetBrowserActionsContainer() {
   return browser_actions_container_;
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.h b/chrome/browser/ui/views/frame/hosted_app_button_container.h
index d68f595b..912e7bc 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.h
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.h
@@ -40,6 +40,7 @@
 class HostedAppButtonContainer : public views::AccessiblePaneView,
                                  public BrowserActionsContainer::Delegate,
                                  public PageActionIconView::Delegate,
+                                 public ContentSettingImageView::Delegate,
                                  public ToolbarButtonProvider,
                                  public ImmersiveModeController::Observer,
                                  public views::WidgetObserver {
@@ -103,6 +104,8 @@
   const std::vector<ContentSettingImageView*>&
   GetContentSettingViewsForTesting() const;
 
+  SkColor GetIconColor() const;
+  SkColor GetIconInkDropColor() const;
   void UpdateChildrenColor();
 
   // views::View:
@@ -120,8 +123,17 @@
       ToolbarActionsBar* main_bar) const override;
 
   // PageActionIconView::Delegate:
+  SkColor GetPageActionInkDropColor() const override;
   content::WebContents* GetWebContentsForPageActionIconView() override;
 
+  // ContentSettingImageView::Delegate:
+  SkColor GetContentSettingInkDropColor() const override;
+  content::WebContents* GetContentSettingWebContents() override;
+  ContentSettingBubbleModelDelegate* GetContentSettingBubbleModelDelegate()
+      override;
+  void OnContentSettingImageBubbleShown(
+      ContentSettingImageModel::ImageType type) const override;
+
   // ToolbarButtonProvider:
   BrowserActionsContainer* GetBrowserActionsContainer() override;
   PageActionIconContainerView* GetPageActionIconContainerView() override;
diff --git a/chrome/browser/ui/views/frame/hosted_app_menu_button.cc b/chrome/browser/ui/views/frame/hosted_app_menu_button.cc
index 80d9db0a..4ef6f0f 100644
--- a/chrome/browser/ui/views/frame/hosted_app_menu_button.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_menu_button.cc
@@ -49,9 +49,11 @@
 
 HostedAppMenuButton::~HostedAppMenuButton() {}
 
-void HostedAppMenuButton::SetIconColor(SkColor color) {
+void HostedAppMenuButton::SetColors(SkColor icon_color,
+                                    SkColor ink_drop_color) {
   SetImage(views::Button::STATE_NORMAL,
-           gfx::CreateVectorIcon(kBrowserToolsIcon, color));
+           gfx::CreateVectorIcon(kBrowserToolsIcon, icon_color));
+  ink_drop_color_ = ink_drop_color;
 }
 
 void HostedAppMenuButton::StartHighlightAnimation() {
@@ -80,6 +82,10 @@
       base::UserMetricsAction("HostedAppMenuButtonButton_Clicked"));
 }
 
+SkColor HostedAppMenuButton::GetInkDropBaseColor() const {
+  return ink_drop_color_;
+}
+
 void HostedAppMenuButton::FadeHighlightOff() {
   if (!ShouldEnterHoveredState()) {
     GetInkDrop()->SetHoverHighlightFadeDurationMs(
diff --git a/chrome/browser/ui/views/frame/hosted_app_menu_button.h b/chrome/browser/ui/views/frame/hosted_app_menu_button.h
index e591d5c..79eac39 100644
--- a/chrome/browser/ui/views/frame/hosted_app_menu_button.h
+++ b/chrome/browser/ui/views/frame/hosted_app_menu_button.h
@@ -8,6 +8,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/views/controls/button/menu_button_listener.h"
 
 class BrowserView;
@@ -19,8 +20,8 @@
   explicit HostedAppMenuButton(BrowserView* browser_view);
   ~HostedAppMenuButton() override;
 
-  // Sets the color of the menu button icon.
-  void SetIconColor(SkColor color);
+  // Sets the color of the menu button icon and highlight.
+  void SetColors(SkColor icon_color, SkColor ink_drop_color);
 
   // Fades the menu button highlight on and off.
   void StartHighlightAnimation();
@@ -30,6 +31,9 @@
                            const gfx::Point& point,
                            const ui::Event* event) override;
 
+  // InkDropHostView:
+  SkColor GetInkDropBaseColor() const override;
+
  private:
   void FadeHighlightOff();
 
@@ -39,6 +43,8 @@
   // The containing browser view.
   BrowserView* browser_view_;
 
+  SkColor ink_drop_color_ = gfx::kPlaceholderColor;
+
   base::OneShotTimer highlight_off_timer_;
 
   DISALLOW_COPY_AND_ASSIGN(HostedAppMenuButton);
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
index 0abc5c66..45e6c5fa 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
@@ -153,16 +153,15 @@
   return bubble_view_ != nullptr;
 }
 
+SkColor ContentSettingImageView::GetInkDropBaseColor() const {
+  return delegate_->GetContentSettingInkDropColor();
+}
+
 ContentSettingImageModel::ImageType ContentSettingImageView::GetTypeForTesting()
     const {
   return content_setting_image_model_->image_type();
 }
 
-SkColor ContentSettingImageView::GetInkDropBaseColor() const {
-  return icon_color_ ? icon_color_.value()
-                     : IconLabelBubbleView::GetInkDropBaseColor();
-}
-
 void ContentSettingImageView::OnWidgetDestroying(views::Widget* widget) {
   DCHECK(bubble_view_);
   DCHECK_EQ(bubble_view_->GetWidget(), widget);
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.h b/chrome/browser/ui/views/location_bar/content_setting_image_view.h
index 17b6ce95..4a785e12 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.h
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.h
@@ -38,6 +38,9 @@
  public:
   class Delegate {
    public:
+    // Gets the color to use for the ink highlight.
+    virtual SkColor GetContentSettingInkDropColor() const = 0;
+
     // Gets the web contents the ContentSettingImageView is for.
     virtual content::WebContents* GetContentSettingWebContents() = 0;
 
@@ -49,9 +52,6 @@
     // Invoked when a bubble is shown.
     virtual void OnContentSettingImageBubbleShown(
         ContentSettingImageModel::ImageType type) const {}
-
-   protected:
-    virtual ~Delegate() {}
   };
 
   ContentSettingImageView(std::unique_ptr<ContentSettingImageModel> image_model,
@@ -75,11 +75,11 @@
   bool OnMousePressed(const ui::MouseEvent& event) override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void OnNativeThemeChanged(const ui::NativeTheme* native_theme) override;
-  SkColor GetInkDropBaseColor() const override;
   SkColor GetTextColor() const override;
   bool ShouldShowSeparator() const override;
   bool ShowBubble(const ui::Event& event) override;
   bool IsBubbleShowing() const override;
+  SkColor GetInkDropBaseColor() const override;
 
   ContentSettingImageModel::ImageType GetTypeForTesting() const;
 
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 28397eb2..963cda76 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -357,16 +357,6 @@
   return highlight;
 }
 
-SkColor IconLabelBubbleView::GetInkDropBaseColor() const {
-  const SkColor ink_color_opaque = GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
-  if (ui::MaterialDesignController::IsNewerMaterialUi()) {
-    // Opacity of the ink drop is set elsewhere, so just use full opacity here.
-    return ink_color_opaque;
-  }
-  return color_utils::DeriveDefaultIconColor(ink_color_opaque);
-}
-
 std::unique_ptr<views::InkDropMask> IconLabelBubbleView::CreateInkDropMask()
     const {
   if (!LocationBarView::IsRounded())
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index 182e563..7c0b752a 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -141,8 +141,8 @@
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
       const override;
-  SkColor GetInkDropBaseColor() const override;
   std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
+  SkColor GetInkDropBaseColor() const override = 0;
 
   // views::Button:
   bool IsTriggerableEvent(const ui::Event& event) override;
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
index 2f055f6..36c5662 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view_unittest.cc
@@ -84,6 +84,7 @@
  protected:
   // IconLabelBubbleView:
   SkColor GetTextColor() const override { return kTestColor; }
+  SkColor GetInkDropBaseColor() const override { return kTestColor; }
 
   bool ShouldShowLabel() const override {
     return !IsShrinking() ||
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index be9a915..61a3d96 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -333,6 +333,11 @@
                          state);
 }
 
+SkColor LocationBarView::GetIconInkDropColor() const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+}
+
 void LocationBarView::SetStarToggled(bool on) {
   if (star_view_)
     star_view_->SetToggled(on);
@@ -749,6 +754,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 // LocationBarView, public ContentSettingImageView::Delegate implementation:
 
+SkColor LocationBarView::GetContentSettingInkDropColor() const {
+  return GetIconInkDropColor();
+}
+
 content::WebContents* LocationBarView::GetContentSettingWebContents() {
   return GetToolbarModel()->input_in_progress() ? nullptr : GetWebContents();
 }
@@ -760,6 +769,11 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // LocationBarView, public PageActionIconView::Delegate implementation:
+
+SkColor LocationBarView::GetPageActionInkDropColor() const {
+  return GetIconInkDropColor();
+}
+
 WebContents* LocationBarView::GetWebContentsForPageActionIconView() {
   return GetWebContents();
 }
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h
index e441ae82..2075a15 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.h
@@ -137,6 +137,9 @@
   SkColor GetSecurityChipColor(
       security_state::SecurityLevel security_level) const;
 
+  // Returns the color to use for icon ink highlights.
+  SkColor GetIconInkDropColor() const;
+
   // Returns the cached theme color tint for the location bar and results.
   OmniboxTint tint() const { return tint_; }
 
@@ -239,6 +242,7 @@
   content::WebContents* GetWebContents() override;
 
   // ContentSettingImageView::Delegate:
+  SkColor GetContentSettingInkDropColor() const override;
   content::WebContents* GetContentSettingWebContents() override;
   ContentSettingBubbleModelDelegate* GetContentSettingBubbleModelDelegate()
       override;
@@ -373,6 +377,7 @@
                            const gfx::Point& p) override;
 
   // PageActionIconView::Delegate:
+  SkColor GetPageActionInkDropColor() const override;
   content::WebContents* GetWebContentsForPageActionIconView() override;
 
   // gfx::AnimationDelegate:
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.cc b/chrome/browser/ui/views/location_bar/location_icon_view.cc
index 86ffa0bd..2d6bd14 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.cc
@@ -96,6 +96,10 @@
   return location_bar_->ShowPageInfoDialog(contents);
 }
 
+SkColor LocationIconView::GetInkDropBaseColor() const {
+  return location_bar_->GetIconInkDropColor();
+}
+
 void LocationIconView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   if (location_bar_->GetOmniboxView()->IsEditingOrEmpty()) {
     node_data->role = ax::mojom::Role::kImage;
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.h b/chrome/browser/ui/views/location_bar/location_icon_view.h
index 63ebb5f..1d4187e 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.h
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.h
@@ -32,6 +32,7 @@
   bool ShowBubble(const ui::Event& event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   bool IsBubbleShowing() const override;
+  SkColor GetInkDropBaseColor() const override;
 
   // Whether we should show the tooltip for this icon or not.
   void set_show_tooltip(bool show_tooltip) { show_tooltip_ = show_tooltip; }
diff --git a/chrome/browser/ui/views/location_bar/selected_keyword_view.cc b/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
index dcbb75d..87edc033 100644
--- a/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
+++ b/chrome/browser/ui/views/location_bar/selected_keyword_view.cc
@@ -49,6 +49,10 @@
   return location_bar_->GetColor(OmniboxPart::LOCATION_BAR_SELECTED_KEYWORD);
 }
 
+SkColor SelectedKeywordView::GetInkDropBaseColor() const {
+  return location_bar_->GetIconInkDropColor();
+}
+
 gfx::Size SelectedKeywordView::CalculatePreferredSize() const {
   // Height will be ignored by the LocationBarView.
   return GetSizeForLabelWidth(full_label_.GetPreferredSize().width());
diff --git a/chrome/browser/ui/views/location_bar/selected_keyword_view.h b/chrome/browser/ui/views/location_bar/selected_keyword_view.h
index 861f9007..0f3ea308 100644
--- a/chrome/browser/ui/views/location_bar/selected_keyword_view.h
+++ b/chrome/browser/ui/views/location_bar/selected_keyword_view.h
@@ -33,6 +33,7 @@
 
   // IconLabelBubbleView:
   SkColor GetTextColor() const override;
+  SkColor GetInkDropBaseColor() const override;
 
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 07519d0a..bcd299cb 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/views/omnibox/rounded_omnibox_results_frame.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/omnibox_pedal.h"
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/omnibox/browser/vector_icons.h"
 #include "components/strings/grit/components_strings.h"
@@ -88,12 +89,18 @@
 
   // Set up 'switch to tab' button.
   if (match.ShouldShowTabMatch()) {
-    const base::string16 hint =
-        l10n_util::GetStringUTF16(IDS_OMNIBOX_TAB_SUGGEST_HINT);
-    const base::string16 hint_short =
-        l10n_util::GetStringUTF16(IDS_OMNIBOX_TAB_SUGGEST_SHORT_HINT);
-    suggestion_tab_switch_button_ = std::make_unique<OmniboxTabSwitchButton>(
-        model_, this, hint, hint_short, omnibox::kSwitchIcon);
+    if (match.pedal) {
+      const OmniboxPedal::LabelStrings& strings =
+          match.pedal->GetLabelStrings();
+      suggestion_tab_switch_button_ = std::make_unique<OmniboxTabSwitchButton>(
+          model_, this, strings.hint, strings.hint_short, omnibox::kPedalIcon);
+    } else {
+      suggestion_tab_switch_button_ = std::make_unique<OmniboxTabSwitchButton>(
+          model_, this, l10n_util::GetStringUTF16(IDS_OMNIBOX_TAB_SUGGEST_HINT),
+          l10n_util::GetStringUTF16(IDS_OMNIBOX_TAB_SUGGEST_SHORT_HINT),
+          omnibox::kSwitchIcon);
+    }
+
     suggestion_tab_switch_button_->set_owned_by_client();
     AddChildView(suggestion_tab_switch_button_.get());
   } else {
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
index a7d3b64..615721fb 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
@@ -68,12 +68,6 @@
   return GetBubble() != nullptr;
 }
 
-SkColor PageActionIconView::GetTextColor() const {
-  // Returns the color of the label shown during animation.
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_LabelDisabledColor);
-}
-
 bool PageActionIconView::SetCommandEnabled(bool enabled) const {
   DCHECK(command_updater_);
   command_updater_->UpdateCommandEnabled(command_id_, enabled);
@@ -84,6 +78,12 @@
   return false;
 }
 
+SkColor PageActionIconView::GetTextColor() const {
+  // Returns the color of the label shown during animation.
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_LabelDisabledColor);
+}
+
 void PageActionIconView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kButton;
   node_data->SetName(GetTextForTooltipAndAccessibleName());
@@ -205,16 +205,6 @@
   return highlight;
 }
 
-SkColor PageActionIconView::GetInkDropBaseColor() const {
-  const SkColor ink_color_opaque = GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
-  if (ui::MaterialDesignController::IsNewerMaterialUi()) {
-    // Opacity of the ink drop is set elsewhere, so just use full opacity here.
-    return ink_color_opaque;
-  }
-  return color_utils::DeriveDefaultIconColor(ink_color_opaque);
-}
-
 std::unique_ptr<views::InkDropMask> PageActionIconView::CreateInkDropMask()
     const {
   if (!LocationBarView::IsRounded())
@@ -223,6 +213,10 @@
                                                        height() / 2.f);
 }
 
+SkColor PageActionIconView::GetInkDropBaseColor() const {
+  return delegate_->GetPageActionInkDropColor();
+}
+
 void PageActionIconView::OnGestureEvent(ui::GestureEvent* event) {
   if (event->type() == ui::ET_GESTURE_TAP) {
     AnimateInkDrop(views::InkDropState::ACTIVATED, event);
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.h b/chrome/browser/ui/views/page_action/page_action_icon_view.h
index f0f73fb..897614d 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.h
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.h
@@ -37,6 +37,9 @@
  public:
   class Delegate {
    public:
+    // Gets the color to use for the ink highlight.
+    virtual SkColor GetPageActionInkDropColor() const = 0;
+
     virtual content::WebContents* GetWebContentsForPageActionIconView() = 0;
   };
 
@@ -73,8 +76,6 @@
   // Returns true if a related bubble is showing.
   bool IsBubbleShowing() const override;
 
-  SkColor GetTextColor() const override;
-
   // Enables or disables the associated command.
   // Returns true if the command is enabled.
   bool SetCommandEnabled(bool enabled) const;
@@ -89,6 +90,7 @@
   virtual void OnPressed(bool activated) {}
 
   // views::IconLabelBubbleView:
+  SkColor GetTextColor() const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   bool GetTooltipText(const gfx::Point& p,
                       base::string16* tooltip) const override;
@@ -106,8 +108,8 @@
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
       const override;
-  SkColor GetInkDropBaseColor() const override;
   std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
+  SkColor GetInkDropBaseColor() const override;
 
   // ui::EventHandler:
   void OnGestureEvent(ui::GestureEvent* event) override;
diff --git a/chrome/browser/ui/views/payments/payment_request_item_list.cc b/chrome/browser/ui/views/payments/payment_request_item_list.cc
index 30bf92d..a5d7425 100644
--- a/chrome/browser/ui/views/payments/payment_request_item_list.cc
+++ b/chrome/browser/ui/views/payments/payment_request_item_list.cc
@@ -188,7 +188,7 @@
     std::unique_ptr<PaymentRequestItemList::Item> item) {
   DCHECK_EQ(this, item->list());
   if (!items_.empty())
-    item->set_previous_row(items_.back().get());
+    item->set_previous_row(items_.back()->AsWeakPtr());
   items_.push_back(std::move(item));
   if (items_.back()->selected()) {
     if (selected_item_)
diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.h b/chrome/browser/ui/views/payments/payment_request_row_view.h
index b234029..8cdfa1d 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.h
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.h
@@ -6,13 +6,16 @@
 #define CHROME_BROWSER_UI_VIEWS_PAYMENTS_PAYMENT_REQUEST_ROW_VIEW_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/views/controls/button/button.h"
 
 namespace payments {
 
 // This class implements a clickable row of the Payment Request dialog that
 // darkens on hover and displays a horizontal ruler on its lower bound.
-class PaymentRequestRowView : public views::Button {
+class PaymentRequestRowView
+    : public views::Button,
+      public base::SupportsWeakPtr<PaymentRequestRowView> {
  public:
   // Creates a row view. If |clickable| is true, the row will be shaded on hover
   // and handle click events. |insets| are used as padding around the content.
@@ -21,7 +24,7 @@
                         const gfx::Insets& insets);
   ~PaymentRequestRowView() override;
 
-  void set_previous_row(PaymentRequestRowView* previous_row) {
+  void set_previous_row(base::WeakPtr<PaymentRequestRowView> previous_row) {
     previous_row_ = previous_row;
   }
 
@@ -55,7 +58,7 @@
 
   // A non-owned pointer to the previous row object in the UI. Used to hide the
   // bottom border of the previous row when highlighting this one. May be null.
-  PaymentRequestRowView* previous_row_;
+  base::WeakPtr<PaymentRequestRowView> previous_row_;
 
   DISALLOW_COPY_AND_ASSIGN(PaymentRequestRowView);
 };
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index 901ecb99..cc88250 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -432,7 +432,7 @@
 
   if (spec()->request_shipping()) {
     std::unique_ptr<PaymentRequestRowView> shipping_row = CreateShippingRow();
-    shipping_row->set_previous_row(previous_row);
+    shipping_row->set_previous_row(previous_row->AsWeakPtr());
     previous_row = shipping_row.get();
     layout->StartRow(views::GridLayout::kFixedSize, 0);
     layout->AddView(shipping_row.release());
@@ -442,7 +442,7 @@
     std::unique_ptr<PaymentRequestRowView> shipping_option_row =
         CreateShippingOptionRow();
     if (shipping_option_row) {
-      shipping_option_row->set_previous_row(previous_row);
+      shipping_option_row->set_previous_row(previous_row->AsWeakPtr());
       previous_row = shipping_option_row.get();
       layout->StartRow(views::GridLayout::kFixedSize, 0);
       layout->AddView(shipping_option_row.release());
@@ -450,7 +450,7 @@
   }
   std::unique_ptr<PaymentRequestRowView> payment_method_row =
       CreatePaymentMethodRow();
-  payment_method_row->set_previous_row(previous_row);
+  payment_method_row->set_previous_row(previous_row->AsWeakPtr());
   previous_row = payment_method_row.get();
   layout->StartRow(views::GridLayout::kFixedSize, 0);
   layout->AddView(payment_method_row.release());
@@ -458,7 +458,7 @@
       spec()->request_payer_phone()) {
     std::unique_ptr<PaymentRequestRowView> contact_info_row =
         CreateContactInfoRow();
-    contact_info_row->set_previous_row(previous_row);
+    contact_info_row->set_previous_row(previous_row->AsWeakPtr());
     previous_row = contact_info_row.get();
     layout->StartRow(views::GridLayout::kFixedSize, 0);
     layout->AddView(contact_info_row.release());
diff --git a/chrome/browser/ui/views/simple_message_box_views.cc b/chrome/browser/ui/views/simple_message_box_views.cc
index a5dcee2..355fd27 100644
--- a/chrome/browser/ui/views/simple_message_box_views.cc
+++ b/chrome/browser/ui/views/simple_message_box_views.cc
@@ -21,6 +21,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/display/screen.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/controls/message_box_view.h"
 #include "ui/views/widget/widget.h"
@@ -130,7 +131,8 @@
   }
 #else
   if (!base::MessageLoopForUI::IsCurrent() ||
-      !ui::ResourceBundle::HasSharedInstance()) {
+      !ui::ResourceBundle::HasSharedInstance() ||
+      !display::Screen::GetScreen()) {
     LOG(ERROR) << "Unable to show a dialog outside the UI thread message loop: "
                << title << " - " << message;
     std::move(callback).Run(chrome::MESSAGE_BOX_RESULT_NO);
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 5d06416..f83d1d9 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/views/tabs/tab.h"
 
 #include <stddef.h>
+
+#include <algorithm>
 #include <limits>
 #include <utility>
 
@@ -34,6 +36,7 @@
 #include "chrome/browser/ui/views/tabs/tab_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_icon.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
+#include "chrome/browser/ui/views/tabs/tab_style.h"
 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
@@ -73,6 +76,9 @@
 #include "ui/aura/env.h"
 #endif
 
+// Turn this off to revert to the old behavior.
+#define USE_TAB_STYLE
+
 using base::UserMetricsAction;
 using MD = ui::MaterialDesignController;
 
@@ -119,6 +125,8 @@
       flags);
 }
 
+#if !defined(USE_TAB_STYLE)
+
 // Scales |bounds| by scale and aligns so that adjacent tabs meet up exactly
 // during painting.
 const gfx::RectF ScaleAndAlignBounds(const gfx::Rect& bounds,
@@ -155,17 +163,17 @@
 }
 
 // Offsets each path inward by |insets|, then intersects them together.
-gfx::Path OffsetAndIntersectPaths(gfx::Path& left_path,
-                                  gfx::Path& right_path,
+gfx::Path OffsetAndIntersectPaths(gfx::Path* left_path,
+                                  gfx::Path* right_path,
                                   const gfx::InsetsF& insets) {
   // This code is not prepared to deal with vertical adjustments.
   DCHECK_EQ(0, insets.top());
   DCHECK_EQ(0, insets.bottom());
 
   gfx::Path complete_path;
-  left_path.offset(insets.left(), 0);
-  right_path.offset(-insets.right(), 0);
-  Op(left_path, right_path, SkPathOp::kIntersect_SkPathOp, &complete_path);
+  left_path->offset(insets.left(), 0);
+  right_path->offset(-insets.right(), 0);
+  Op(*left_path, *right_path, SkPathOp::kIntersect_SkPathOp, &complete_path);
   return complete_path;
 }
 
@@ -269,7 +277,7 @@
   right_path.offset(-origin.x(), -origin.y());
   left_path.offset(-origin.x(), -origin.y());
 
-  return OffsetAndIntersectPaths(left_path, right_path, insets.Scale(scale));
+  return OffsetAndIntersectPaths(&left_path, &right_path, insets.Scale(scale));
 }
 
 // Returns a path corresponding to the tab's outer border for a given tab
@@ -363,6 +371,8 @@
   return path;
 }
 
+#endif  // !defined(USE_TAB_STYLE)
+
 }  // namespace
 
 // Tab -------------------------------------------------------------------------
@@ -483,11 +493,18 @@
   // When the window is maximized we don't want to shave off the edges or top
   // shadow of the tab, such that the user can click anywhere along the top
   // edge of the screen to select a tab. Ditto for immersive fullscreen.
+#if defined(USE_TAB_STYLE)
+  *mask = GetTabStyle()->GetPath(
+      this, TabStyle::PathType::kHitTest,
+      GetWidget()->GetCompositor()->device_scale_factor(),
+      /* force_active */ false, TabStyle::RenderUnits::kDips);
+#else
   const views::Widget* widget = GetWidget();
   *mask = GetBorderPath(
       GetWidget()->GetCompositor()->device_scale_factor(), GetStrokeThickness(),
       GetBottomStrokeThickness(), true,
       widget && (widget->IsMaximized() || widget->IsFullscreen()), bounds());
+#endif
   return true;
 }
 
@@ -514,7 +531,7 @@
     favicon_bounds.set_size(
         gfx::Size(icon_->GetPreferredSize().width(),
                   contents_rect.height() - favicon_bounds.y()));
-    if (center_favicon_) {
+    if (center_icon_) {
       // When centering the favicon, the favicon is allowed to escape the normal
       // contents rect.
       favicon_bounds.set_x(Center(width(), gfx::kFaviconSize));
@@ -582,7 +599,13 @@
         std::max(contents_rect.x(), right - image_size.width()),
         contents_rect.y() + Center(contents_rect.height(), image_size.height()),
         image_size.width(), image_size.height());
-    MaybeAdjustLeftForPinnedTab(&bounds, bounds.width());
+    if (center_icon_) {
+      // When centering the alert icon, it is allowed to escape the normal
+      // contents rect.
+      bounds.set_x(Center(width(), bounds.width()));
+    } else {
+      MaybeAdjustLeftForPinnedTab(&bounds, bounds.width());
+    }
     alert_indicator_->SetBoundsRect(bounds);
   }
   alert_indicator_->SetVisible(showing_alert_indicator_);
@@ -817,6 +840,11 @@
   ui::ClipRecorder clip_recorder(info.context());
   // The paint recording scale for tabs is consistent along the x and y axis.
   const float paint_recording_scale = info.paint_recording_scale_x();
+
+#if defined(USE_TAB_STYLE)
+  const gfx::Path clip_path = GetTabStyle()->GetPath(
+      this, TabStyle::PathType::kClip, paint_recording_scale);
+#else
   // When there is a separator, animate the clip to account for it, in sync with
   // the separator's fading.
   // TODO(pkasting): Consider crossfading the favicon instead of animating the
@@ -825,14 +853,21 @@
   constexpr float kChildClipPadding = 2.5f;
   const gfx::InsetsF padding(0, kChildClipPadding + opacities.left, 0,
                              kChildClipPadding + opacities.right);
-  clip_recorder.ClipPathWithAntiAliasing(
+  const gfx::Path clip_path =
       GetInteriorPath(paint_recording_scale, GetStrokeThickness(),
-                      GetBottomStrokeThickness(), bounds(), padding));
+                      GetBottomStrokeThickness(), bounds(), padding);
+#endif
+
+  clip_recorder.ClipPathWithAntiAliasing(clip_path);
   View::PaintChildren(info);
 }
 
 void Tab::OnPaint(gfx::Canvas* canvas) {
   gfx::Path clip;
+#if defined(USE_TAB_STYLE)
+  if (!controller_->ShouldPaintTab(this, canvas->image_scale(), &clip))
+    return;
+#else
   if (!controller_->ShouldPaintTab(
           this,
           base::BindRepeating(&GetBorderPath, canvas->image_scale(),
@@ -840,6 +875,7 @@
                               true, false),
           &clip))
     return;
+#endif
 
   PaintTab(canvas, clip);
 }
@@ -1006,6 +1042,92 @@
              stroke_thickness + GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP), 0);
 }
 
+Tab::SeparatorOpacities Tab::GetSeparatorOpacities(bool for_layout) const {
+  // Something should visually separate tabs from each other and any adjacent
+  // new tab button.  Normally, active and hovered tabs draw distinct shapes
+  // (via different background colors) and thus need no separators, while
+  // background tabs need separators between them.  In single-tab mode, the
+  // active tab has no visible shape and thus needs separators on any side with
+  // an adjacent new tab button.  (The other sides will be faded out below.)
+  float leading_opacity, trailing_opacity;
+  if (controller_->SingleTabMode()) {
+    leading_opacity = trailing_opacity = 1.f;
+  } else if (IsActive()) {
+    leading_opacity = trailing_opacity = 0;
+  } else {
+    // Fade out the trailing separator while this tab or the subsequent tab is
+    // hovered.  If the subsequent tab is active, don't consider its hover
+    // animation value, lest the trailing separator on this tab disappear while
+    // the subsequent tab is being dragged.
+    const float hover_value = hover_controller_.GetAnimationValue();
+    const Tab* subsequent_tab = controller_->GetAdjacentTab(this, 1);
+    const float subsequent_hover =
+        !for_layout && subsequent_tab && !subsequent_tab->IsActive()
+            ? float{subsequent_tab->hover_controller_.GetAnimationValue()}
+            : 0;
+    trailing_opacity = 1.f - std::max(hover_value, subsequent_hover);
+
+    // The leading separator need not consider the previous tab's hover value,
+    // since if there is a previous tab that's hovered and not being dragged, it
+    // will draw atop this tab.
+    leading_opacity = 1.f - hover_value;
+
+    const Tab* previous_tab = controller_->GetAdjacentTab(this, -1);
+    if (IsSelected()) {
+      // Since this tab is selected, its shape will be visible against adjacent
+      // unselected tabs, so remove the separator in those cases.
+      if (previous_tab && !previous_tab->IsSelected())
+        leading_opacity = 0;
+      if (subsequent_tab && !subsequent_tab->IsSelected())
+        trailing_opacity = 0;
+    } else if (controller_->HasVisibleBackgroundTabShapes()) {
+      // Since this tab is unselected, adjacent selected tabs will normally
+      // paint atop it, covering the separator.  But if the user drags those
+      // selected tabs away, the exposed region looks like the window frame; and
+      // since background tab shapes are visible, there should be no separator.
+      // TODO(pkasting): https://crbug.com/876599  When a tab is animating into
+      // this gap, we should adjust its separator opacities as well.
+      if (previous_tab && previous_tab->IsSelected())
+        leading_opacity = 0;
+      if (subsequent_tab && subsequent_tab->IsSelected())
+        trailing_opacity = 0;
+    }
+  }
+
+  // For the first or last tab in the strip, fade the leading or trailing
+  // separator based on the NTB position and how close to the target bounds this
+  // tab is.  In the steady state, this hides separators on the opposite end of
+  // the strip from the NTB; it fades out the separators as tabs animate into
+  // these positions, after they pass by the other tabs; and it snaps the
+  // separators to full visibility immediately when animating away from these
+  // positions, which seems desirable.
+  const NewTabButtonPosition ntb_position =
+      controller_->GetNewTabButtonPosition();
+  const gfx::Rect target_bounds =
+      controller_->GetTabAnimationTargetBounds(this);
+  const int tab_width = std::max(width(), target_bounds.width());
+  const float target_opacity =
+      float{std::min(std::abs(x() - target_bounds.x()), tab_width)} / tab_width;
+  // If the tab shapes are visible, never draw end separators.
+  const bool always_hide_separators_on_ends =
+      controller_->HasVisibleBackgroundTabShapes();
+  if (controller_->IsFirstVisibleTab(this) &&
+      (ntb_position != LEADING || always_hide_separators_on_ends))
+    leading_opacity = target_opacity;
+  if (controller_->IsLastVisibleTab(this) &&
+      (ntb_position != AFTER_TABS || always_hide_separators_on_ends))
+    trailing_opacity = target_opacity;
+
+  // Return the opacities in physical order, rather than logical.
+  if (base::i18n::IsRTL())
+    std::swap(leading_opacity, trailing_opacity);
+  return {leading_opacity, trailing_opacity};
+}
+
+const TabStyle* Tab::GetTabStyle() const {
+  return TabStyle::GetInstance();
+}
+
 // static
 gfx::Insets Tab::GetContentsHorizontalInsets() {
   return gfx::Insets(0, GetCornerRadius() * 2);
@@ -1185,23 +1307,38 @@
   const bool paint_hover_effect = !active && hover_controller_.ShouldDraw();
   const float scale = canvas->image_scale();
   const float stroke_thickness = GetStrokeThickness(active);
+#if !defined(USE_TAB_STYLE)
   const float bottom_offset = GetBottomStrokeThickness(active);
+#endif
   const auto paint_fill = [&](gfx::Canvas* canvas) {
+#if defined(USE_TAB_STYLE)
+    const gfx::Path fill_path =
+        GetTabStyle()->GetPath(this, TabStyle::PathType::kFill, scale, active);
+#else
     // When there's a border, we want the stroke to cover up the edge of the
     // fill path (https://crbug.com/873003), so set the fill path halfway
     // between the inner path and the border paths.  When there's no stroke,
     // |stroke_thickness| is 0 and the fill, inner, and stroke paths are all
     // identical.
-    gfx::Path fill_path =
+    const gfx::Path fill_path =
         GetInteriorPath(scale, stroke_thickness / 2, bottom_offset, bounds());
+#endif
     PaintTabBackgroundFill(canvas, fill_path, active, paint_hover_effect,
                            active_color, inactive_color, fill_id, y_inset);
   };
   const auto paint_stroke = [&](gfx::Canvas* canvas) {
+#if defined(USE_TAB_STYLE)
+    const TabStyle* tab_style = GetTabStyle();
+    gfx::Path interior_path = tab_style->GetPath(
+        this, TabStyle::PathType::kInsideBorder, scale, active);
+    gfx::Path outer_path = tab_style->GetPath(
+        this, TabStyle::PathType::kOutsideBorder, scale, active);
+#else
     gfx::Path interior_path =
         GetInteriorPath(scale, stroke_thickness, bottom_offset, bounds());
     gfx::Path outer_path = GetBorderPath(scale, stroke_thickness, bottom_offset,
                                          false, false, bounds());
+#endif
     PaintTabBackgroundStroke(canvas, interior_path, outer_path, active,
                              stroke_color);
   };
@@ -1332,22 +1469,28 @@
   gfx::ScopedCanvas scoped_canvas(canvas);
   const float scale = canvas->UndoDeviceScaleFactor();
 
+#if defined(USE_TAB_STYLE)
+  TabStyle::SeparatorBounds separator_bounds =
+      GetTabStyle()->GetSeparatorBounds(this, scale);
+#else
+  TabStyle::SeparatorBounds separator_bounds;
   const gfx::RectF aligned_bounds =
       ScaleAndAlignBounds(bounds(), scale, GetStrokeThickness());
   const int corner_radius = GetCornerRadius();
   const float separator_height = GetTabSeparatorHeight() * scale;
-  gfx::RectF leading_separator_bounds(
+  separator_bounds.leading = gfx::RectF(
       aligned_bounds.x() + corner_radius * scale,
       aligned_bounds.y() + (aligned_bounds.height() - separator_height) / 2,
       kSeparatorThickness * scale, separator_height);
-  gfx::RectF trailing_separator_bounds = leading_separator_bounds;
-  trailing_separator_bounds.set_x(
+  separator_bounds.trailing = separator_bounds.leading;
+  separator_bounds.trailing.set_x(
       aligned_bounds.right() - (corner_radius + kSeparatorThickness) * scale);
 
   gfx::PointF origin(bounds().origin());
   origin.Scale(scale);
-  leading_separator_bounds.Offset(-origin.x(), -origin.y());
-  trailing_separator_bounds.Offset(-origin.x(), -origin.y());
+  separator_bounds.leading.Offset(-origin.x(), -origin.y());
+  separator_bounds.trailing.Offset(-origin.x(), -origin.y());
+#endif
 
   const SkColor separator_base_color = controller_->GetTabSeparatorColor();
   const auto separator_color = [separator_base_color](float opacity) {
@@ -1359,9 +1502,9 @@
   cc::PaintFlags flags;
   flags.setAntiAlias(true);
   flags.setColor(separator_color(separator_opacities.left));
-  canvas->DrawRect(leading_separator_bounds, flags);
+  canvas->DrawRect(separator_bounds.leading, flags);
   flags.setColor(separator_color(separator_opacities.right));
-  canvas->DrawRect(trailing_separator_bounds, flags);
+  canvas->DrawRect(separator_bounds.trailing, flags);
 }
 
 void Tab::UpdateIconVisibility() {
@@ -1373,7 +1516,7 @@
   // This prevents the icon and text from sliding left at the end of closing
   // a non-narrow tab.
   if (!closing_) {
-    center_favicon_ = false;
+    center_icon_ = false;
     extra_padding_before_content_ = false;
   }
 
@@ -1445,15 +1588,16 @@
     if (showing_close_button_ || show_on_hover)
       available_width -= close_button_width;
 
-    // If no other controls are visible, show favicon even though we
-    // don't have enough space. We'll clip the favicon in PaintChildren().
-    if (!showing_close_button_ && !showing_alert_indicator_ && !showing_icon_ &&
-        has_favicon) {
-      showing_icon_ = true;
+    // If no other controls are visible, show the alert icon or the favicon
+    // even though we don't have enough space. We'll clip the icon in
+    // PaintChildren().
+    if (!showing_close_button_ && !showing_alert_indicator_ && !showing_icon_) {
+      showing_alert_indicator_ = has_alert_icon;
+      showing_icon_ = !showing_alert_indicator_ && has_favicon;
 
       // See comments near top of function on why this conditional is here.
       if (!closing_)
-        center_favicon_ = true;
+        center_icon_ = true;
     }
   }
 
@@ -1477,88 +1621,6 @@
       (width() >= (GetPinnedWidth() + kPinnedTabExtraWidthToRenderAsNormal));
 }
 
-Tab::SeparatorOpacities Tab::GetSeparatorOpacities(bool for_layout) const {
-  // Something should visually separate tabs from each other and any adjacent
-  // new tab button.  Normally, active and hovered tabs draw distinct shapes
-  // (via different background colors) and thus need no separators, while
-  // background tabs need separators between them.  In single-tab mode, the
-  // active tab has no visible shape and thus needs separators on any side with
-  // an adjacent new tab button.  (The other sides will be faded out below.)
-  float leading_opacity, trailing_opacity;
-  if (controller_->SingleTabMode()) {
-    leading_opacity = trailing_opacity = 1.f;
-  } else if (IsActive()) {
-    leading_opacity = trailing_opacity = 0;
-  } else {
-    // Fade out the trailing separator while this tab or the subsequent tab is
-    // hovered.  If the subsequent tab is active, don't consider its hover
-    // animation value, lest the trailing separator on this tab disappear while
-    // the subsequent tab is being dragged.
-    const float hover_value = hover_controller_.GetAnimationValue();
-    const Tab* subsequent_tab = controller_->GetAdjacentTab(this, 1);
-    const float subsequent_hover =
-        !for_layout && subsequent_tab && !subsequent_tab->IsActive()
-            ? float{subsequent_tab->hover_controller_.GetAnimationValue()}
-            : 0;
-    trailing_opacity = 1.f - std::max(hover_value, subsequent_hover);
-
-    // The leading separator need not consider the previous tab's hover value,
-    // since if there is a previous tab that's hovered and not being dragged, it
-    // will draw atop this tab.
-    leading_opacity = 1.f - hover_value;
-
-    const Tab* previous_tab = controller_->GetAdjacentTab(this, -1);
-    if (IsSelected()) {
-      // Since this tab is selected, its shape will be visible against adjacent
-      // unselected tabs, so remove the separator in those cases.
-      if (previous_tab && !previous_tab->IsSelected())
-        leading_opacity = 0;
-      if (subsequent_tab && !subsequent_tab->IsSelected())
-        trailing_opacity = 0;
-    } else if (controller_->HasVisibleBackgroundTabShapes()) {
-      // Since this tab is unselected, adjacent selected tabs will normally
-      // paint atop it, covering the separator.  But if the user drags those
-      // selected tabs away, the exposed region looks like the window frame; and
-      // since background tab shapes are visible, there should be no separator.
-      // TODO(pkasting): https://crbug.com/876599  When a tab is animating into
-      // this gap, we should adjust its separator opacities as well.
-      if (previous_tab && previous_tab->IsSelected())
-        leading_opacity = 0;
-      if (subsequent_tab && subsequent_tab->IsSelected())
-        trailing_opacity = 0;
-    }
-  }
-
-  // For the first or last tab in the strip, fade the leading or trailing
-  // separator based on the NTB position and how close to the target bounds this
-  // tab is.  In the steady state, this hides separators on the opposite end of
-  // the strip from the NTB; it fades out the separators as tabs animate into
-  // these positions, after they pass by the other tabs; and it snaps the
-  // separators to full visibility immediately when animating away from these
-  // positions, which seems desirable.
-  const NewTabButtonPosition ntb_position =
-      controller_->GetNewTabButtonPosition();
-  const gfx::Rect target_bounds =
-      controller_->GetTabAnimationTargetBounds(this);
-  const int tab_width = std::max(width(), target_bounds.width());
-  const float target_opacity =
-      float{std::min(std::abs(x() - target_bounds.x()), tab_width)} / tab_width;
-  // If the tab shapes are visible, never draw end separators.
-  const bool always_hide_separators_on_ends =
-      controller_->HasVisibleBackgroundTabShapes();
-  if (controller_->IsFirstVisibleTab(this) &&
-      (ntb_position != LEADING || always_hide_separators_on_ends))
-    leading_opacity = target_opacity;
-  if (controller_->IsLastVisibleTab(this) &&
-      (ntb_position != AFTER_TABS || always_hide_separators_on_ends))
-    trailing_opacity = target_opacity;
-
-  // Return the opacities in physical order, rather than logical.
-  if (base::i18n::IsRTL())
-    std::swap(leading_opacity, trailing_opacity);
-  return {leading_opacity, trailing_opacity};
-}
-
 float Tab::GetHoverOpacity() const {
   // Opacity boost varies on tab width.  The interpolation is nonlinear so
   // that most tabs will fall on the low end of the opacity range, but very
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 45db570..ea4ddb8 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -186,6 +186,21 @@
   // Returns the insets to use for laying out tab contents.
   gfx::Insets GetContentsInsets() const;
 
+  // Contains values 0..1 representing the opacity of the corresponding
+  // separators.  These are physical and not logical, so "left" is the left
+  // separator in both LTR and RTL.
+  struct SeparatorOpacities {
+    float left = 0, right = 0;
+  };
+
+  // Returns the opacities of the separators.  If |for_layout| is true, returns
+  // the "layout" opacities, which ignore the effects of surrounding tabs' hover
+  // effects and consider only the current tab's state.
+  SeparatorOpacities GetSeparatorOpacities(bool for_layout) const;
+
+  // Returns the TabStyle associated with this tab.
+  const class TabStyle* GetTabStyle() const;
+
   // Returns the horizontal insets to use for laying out tab contents.
   static gfx::Insets GetContentsHorizontalInsets();
 
@@ -233,13 +248,6 @@
                            TabCloseButtonVisibilityWhenNotStacked);
   FRIEND_TEST_ALL_PREFIXES(TabTest, TitleTextHasSufficientContrast);
 
-  // Contains values 0..1 representing the opacity of the corresponding
-  // separators.  These are physical and not logical, so "left" is the left
-  // separator in both LTR and RTL.
-  struct SeparatorOpacities {
-    float left = 0, right = 0;
-  };
-
   // Invoked from Layout to adjust the position of the favicon or alert
   // indicator for pinned tabs. The visual_width parameter is how wide the
   // icon looks (rather than how wide the bounds are).
@@ -287,11 +295,6 @@
   // pinned tab.
   bool ShouldRenderAsNormalTab() const;
 
-  // Returns the opacities of the separators.  If |for_layout| is true, returns
-  // the "layout" opacities, which ignore the effects of surrounding tabs' hover
-  // effects and consider only the current tab's state.
-  SeparatorOpacities GetSeparatorOpacities(bool for_layout) const;
-
   // Returns the final hover opacity for this tab (considers tab width).
   float GetHoverOpacity() const;
 
@@ -346,10 +349,10 @@
   // The offset used to paint the inactive background image.
   int background_offset_;
 
-  // For narrow tabs, we show the favicon even if it won't completely fit.
-  // In this case, we need to center the favicon within the tab; it will be
-  // clipped to fit.
-  bool center_favicon_ = false;
+  // For narrow tabs, we show the alert icon or, if there is no alert icon, the
+  // favicon even if it won't completely fit. In this case, we need to center
+  // the icon within the tab; it will be clipped to fit.
+  bool center_icon_ = false;
 
   // Whether we're showing the icon. It is cached so that we can detect when it
   // changes and layout appropriately.
diff --git a/chrome/browser/ui/views/tabs/tab_controller.h b/chrome/browser/ui/views/tabs/tab_controller.h
index 593834995..b2f29332 100644
--- a/chrome/browser/ui/views/tabs/tab_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_controller.h
@@ -30,7 +30,6 @@
 // Controller for tabs.
 class TabController {
  public:
-
   virtual const ui::ListSelectionModel& GetSelectionModel() const = 0;
 
   // Returns true if multiple selection is supported.
@@ -126,6 +125,7 @@
       const base::RepeatingCallback<gfx::Path(const gfx::Rect&)>&
           border_callback,
       gfx::Path* clip) = 0;
+  virtual bool ShouldPaintTab(const Tab* tab, float scale, gfx::Path* clip) = 0;
 
   // Returns the thickness of the stroke around the active tab in DIP.  Returns
   // 0 if there is no stroke.
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index c4b8845..f1710f5 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -1214,6 +1214,7 @@
     }
   }
 
+  ClearIsDraggingTabs();
   ClearTabDraggingInfo();
   attached_tabstrip_->DraggedTabsDetached();
   attached_tabstrip_ = NULL;
@@ -1549,7 +1550,9 @@
     GetAttachedBrowserWidget()->EndMoveLoop();
   }
 
-  ClearTabDraggingInfo();
+  // "IsDraggingTabs" flag needs to be cleared since some part of CompleteDrag()
+  // assumes it's cleared beforehand. See https://crbug.com/892221.
+  ClearIsDraggingTabs();
 
   if (type != TAB_DESTROYED) {
     // We only finish up the drag if we were actually dragging. If start_drag_
@@ -1573,6 +1576,10 @@
       RevertDrag();
   }  // else case the only tab we were dragging was deleted. Nothing to do.
 
+  // Clear tab dragging info after the complete/revert as CompleteDrag() may
+  // need to use some of the properties.
+  ClearTabDraggingInfo();
+
   // Clear out drag data so we don't attempt to do anything with it.
   drag_data_.clear();
 
@@ -2113,22 +2120,32 @@
 #endif
 }
 
-void TabDragController::ClearTabDraggingInfo() {
+void TabDragController::ClearIsDraggingTabs() {
 #if defined(OS_CHROMEOS)
   TabStrip* dragged_tabstrip =
       attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_;
   DCHECK(!dragged_tabstrip->IsDragSessionActive() || !active_);
   // Do not clear the dragging info properties for a to-be-destroyed window.
-  // They will be cleared later in Window's destrutor. It's intentional as
+  // They will be cleared later in Window's destructor. It's intentional as
   // ash::SplitViewController::TabDraggedWindowObserver listens to both
   // OnWindowDestroying() event and the window properties change event, and uses
   // the two events to decide what to do next.
   if (GetModel(dragged_tabstrip)->empty())
     return;
 
+  GetWindowForTabDraggingProperties(dragged_tabstrip)
+      ->ClearProperty(ash::kIsDraggingTabsKey);
+#endif
+}
+
+void TabDragController::ClearTabDraggingInfo() {
+#if defined(OS_CHROMEOS)
+  TabStrip* dragged_tabstrip =
+      attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_;
+  DCHECK(!dragged_tabstrip->IsDragSessionActive() || !active_);
+
   aura::Window* dragged_window =
       GetWindowForTabDraggingProperties(dragged_tabstrip);
-  dragged_window->ClearProperty(ash::kIsDraggingTabsKey);
   dragged_window->ClearProperty(ash::kTabDraggingSourceWindowKey);
   dragged_window->ClearProperty(ash::kTabDroppedWindowStateTypeKey);
 #endif
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h
index a028f08..b82ccd0 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h
@@ -492,6 +492,11 @@
   // whenever the dragged tabs are attached to a new tabstrip.
   void SetTabDraggingInfo();
 
+  // Clears the flag to indicate that the tab dragging is happening in the
+  // tabstrip. This is separated from ClearTabDraggingInfo() since this needs
+  // to happen slightly before ClearTabDraggingInfo().
+  void ClearIsDraggingTabs();
+
   // Clears the tab dragging info for the current dragged tabstrip. This
   // function is supposed to be called whenever the dragged tabs are detached
   // from the old tabstrip or the tab dragging is ended.
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index c083a87a..0181b3b2e 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_layout.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
+#include "chrome/browser/ui/views/tabs/tab_style.h"
 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
@@ -815,7 +816,7 @@
 }
 
 bool TabStrip::SupportsMultipleSelection() {
-  // TODO: currently only allow single selection in touch layout mode.
+  // Currently we only allow single selection in touch layout mode.
   return touch_layout_ == nullptr;
 }
 
@@ -1083,6 +1084,51 @@
   return true;
 }
 
+bool TabStrip::ShouldPaintTab(const Tab* tab, float scale, gfx::Path* clip) {
+  if (!MaySetClip())
+    return true;
+
+  int index = GetModelIndexOfTab(tab);
+  if (index == -1)
+    return true;  // Tab is closing, paint it all.
+
+  int active_index = IsStackingDraggedTabs() ? controller_->GetActiveIndex()
+                                             : touch_layout_->active_index();
+  if (active_index == tab_count())
+    active_index--;
+
+  const gfx::Rect& current_bounds = tab_at(index)->bounds();
+  if (index < active_index) {
+    const Tab* next_tab = tab_at(index + 1);
+    const gfx::Rect& next_bounds = next_tab->bounds();
+    if (current_bounds.x() == next_bounds.x())
+      return false;
+
+    if (current_bounds.x() > next_bounds.x())
+      return true;  // Can happen during dragging.
+
+    *clip = tab->GetTabStyle()->GetPath(
+        next_tab, TabStyle::PathType::kOutsideBorder, scale, false,
+        TabStyle::RenderUnits::kDips);
+
+    clip->offset(SkIntToScalar(next_bounds.x() - current_bounds.x()), 0);
+  } else if (index > active_index && index > 0) {
+    const Tab* prev_tab = tab_at(index - 1);
+    const gfx::Rect& previous_bounds = prev_tab->bounds();
+    if (current_bounds.x() == previous_bounds.x())
+      return false;
+
+    if (current_bounds.x() < previous_bounds.x())
+      return true;  // Can happen during dragging.
+
+    *clip = tab->GetTabStyle()->GetPath(
+        prev_tab, TabStyle::PathType::kOutsideBorder, scale, false,
+        TabStyle::RenderUnits::kDips);
+    clip->offset(SkIntToScalar(previous_bounds.x() - current_bounds.x()), 0);
+  }
+  return true;
+}
+
 int TabStrip::GetStrokeThickness() const {
   return controller_->ShouldDrawStrokes() ? 1 : 0;
 }
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index b237f4d..10bd8cd5 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -5,7 +5,9 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_
 #define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_H_
 
+#include <map>
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/compiler_specific.h"
@@ -248,6 +250,7 @@
       const base::RepeatingCallback<gfx::Path(const gfx::Rect&)>&
           border_callback,
       gfx::Path* clip) override;
+  bool ShouldPaintTab(const Tab* tab, float scale, gfx::Path* clip) override;
   int GetStrokeThickness() const override;
   bool CanPaintThrobberToLayer() const override;
   bool HasVisibleBackgroundTabShapes() const override;
diff --git a/chrome/browser/ui/views/tabs/tab_style.cc b/chrome/browser/ui/views/tabs/tab_style.cc
new file mode 100644
index 0000000..3a0a4d2
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_style.cc
@@ -0,0 +1,276 @@
+// 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/ui/views/tabs/tab_style.h"
+
+#include "base/no_destructor.h"
+#include "base/numerics/ranges.h"
+#include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/tabs/tab.h"
+#include "chrome/browser/ui/views/tabs/tab_controller.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// Returns whether we should extend the hit test region for Fitts' Law.
+bool ShouldExtendHitTest(const Tab* tab) {
+  const views::Widget* widget = tab->GetWidget();
+  return widget->IsMaximized() || widget->IsFullscreen();
+}
+
+// Given a tab of width |width|, returns the radius to use for the corners.
+float GetTopCornerRadiusForWidth(int width) {
+  // Get the width of the top of the tab by subtracting the width of the outer
+  // corners.
+  const int ideal_radius = Tab::GetCornerRadius();
+  const int top_width = width - ideal_radius * 2;
+
+  // To maintain a round-rect appearance, ensure at least one third of the top
+  // of the tab is flat.
+  const float radius = top_width / 3.f;
+  return base::ClampToRange<float>(radius, 0, ideal_radius);
+}
+
+// Scales |bounds| by scale and aligns so that adjacent tabs meet up exactly
+// during painting.
+const gfx::RectF ScaleAndAlignBounds(const gfx::Rect& bounds,
+                                     float scale,
+                                     float stroke_thickness) {
+  // Convert to layout bounds.  We must inset the width such that the right edge
+  // of one tab's layout bounds is the same as the left edge of the next tab's;
+  // this way the two tabs' separators will be drawn at the same coordinate.
+  gfx::RectF aligned_bounds(bounds);
+  const int corner_radius = Tab::GetCornerRadius();
+  // Note: This intentionally doesn't subtract TABSTRIP_TOOLBAR_OVERLAP from the
+  // bottom inset, because we want to pixel-align the bottom of the stroke, not
+  // the bottom of the overlap.
+  gfx::InsetsF layout_insets(stroke_thickness, corner_radius, stroke_thickness,
+                             corner_radius + Tab::kSeparatorThickness);
+  aligned_bounds.Inset(layout_insets);
+
+  // Scale layout bounds from DIP to px.
+  aligned_bounds.Scale(scale);
+
+  // Snap layout bounds to nearest pixels so we get clean lines.
+  const float x = std::round(aligned_bounds.x());
+  const float y = std::round(aligned_bounds.y());
+  // It's important to round the right edge and not the width, since rounding
+  // both x and width would mean the right edge would accumulate error.
+  const float right = std::round(aligned_bounds.right());
+  const float bottom = std::round(aligned_bounds.bottom());
+  aligned_bounds = gfx::RectF(x, y, right - x, bottom - y);
+
+  // Convert back to full bounds.  It's OK that the outer corners of the curves
+  // around the separator may not be snapped to the pixel grid as a result.
+  aligned_bounds.Inset(-layout_insets.Scale(scale));
+  return aligned_bounds;
+}
+
+class GM2TabStyle : public TabStyle {
+ public:
+  gfx::Path GetPath(
+      const Tab* tab,
+      PathType path_type,
+      float scale,
+      bool force_active = false,
+      RenderUnits render_units = RenderUnits::kPixels) const override {
+    const float stroke_thickness = tab->GetStrokeThickness(force_active);
+
+    // We'll do the entire path calculation in aligned pixels.
+    // TODO(dfried): determine if we actually want to use |stroke_thickness| as
+    // the inset in this case.
+    gfx::RectF aligned_bounds =
+        ScaleAndAlignBounds(tab->bounds(), scale, stroke_thickness);
+
+    if (path_type == PathType::kClip) {
+      // When there is a separator, animate the clip to account for it, in sync
+      // with the separator's fading.
+      // TODO(pkasting): Consider crossfading the favicon instead of animating
+      // the clip, especially if other children get crossfaded.
+      const auto opacities = tab->GetSeparatorOpacities(true);
+      constexpr float kChildClipPadding = 2.5f;
+      aligned_bounds.Inset(
+          gfx::InsetsF(0.0f, kChildClipPadding + opacities.left, 0.0f,
+                       kChildClipPadding + opacities.right));
+    }
+
+    // Calculate the corner radii. Note that corner radius is based on original
+    // tab width (in DIP), not our new, scaled-and-aligned bounds.
+    const float radius = GetTopCornerRadiusForWidth(tab->width()) * scale;
+    float top_radius = radius;
+    float bottom_radius = radius;
+
+    // Compute |extension| as the width outside the separators.  This is a
+    // fixed value equal to the normal corner radius.
+    const float extension = Tab::GetCornerRadius() * scale;
+
+    // Calculate the bounds of the actual path.
+    const float left = aligned_bounds.x();
+    const float right = aligned_bounds.right();
+    float tab_top = aligned_bounds.y();
+    float tab_left = left + extension;
+    float tab_right = right - extension;
+
+    // Overlap the toolbar below us so that gaps don't occur when rendering at
+    // non-integral display scale factors.
+    const float extended_bottom = aligned_bounds.bottom();
+    const float bottom_extension =
+        GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP) * scale;
+    float tab_bottom = extended_bottom - bottom_extension;
+
+    // Path-specific adjustments:
+    const float stroke_adjustment = stroke_thickness * scale;
+    if (path_type == PathType::kInsideBorder || path_type == PathType::kClip) {
+      // Inside border runs |stroke_thickness| inside the outer stroke.
+      tab_left += stroke_adjustment;
+      tab_right -= stroke_adjustment;
+      tab_top += stroke_adjustment;
+      top_radius -= stroke_adjustment;
+    } else if (path_type == PathType::kFill) {
+      tab_left += 0.5f * stroke_adjustment;
+      tab_right -= 0.5f * stroke_adjustment;
+      tab_top += 0.5f * stroke_adjustment;
+      top_radius -= 0.5f * stroke_adjustment;
+      tab_bottom -= 0.5f * stroke_adjustment;
+      bottom_radius -= 0.5f * stroke_adjustment;
+    } else if (path_type == PathType::kOutsideBorder ||
+               path_type == PathType::kHitTest) {
+      // Outside border needs to draw its bottom line a stroke width above the
+      // bottom of the tab, to line up with the stroke that runs across the rest
+      // of the bottom of the tab bar (when strokes are enabled).
+      tab_bottom -= stroke_adjustment;
+      bottom_radius -= stroke_adjustment;
+    }
+    const bool extend_to_top =
+        (path_type == PathType::kHitTest) && ShouldExtendHitTest(tab);
+
+    // When the radius shrinks, it leaves a gap between the bottom corners and
+    // the edge of the tab. Make sure we account for this - and for any
+    // adjustment we may have made to the location of the tab!
+    const float corner_gap = (right - tab_right) - bottom_radius;
+
+    gfx::Path path;
+
+    // We will go clockwise from the lower left. We start in the overlap
+    // region, preventing a gap between toolbar and tabstrip.
+    // TODO(dfried): verify that the we actually want to start the stroke for
+    // the exterior path outside the region; we might end up rendering an
+    // extraneous descending pixel on displays with odd scaling and nonzero
+    // stroke width.
+
+    // Start with the left side of the shape.
+
+    // Draw everything left of the bottom-left corner of the tab.
+    //   ╭─────────╮
+    //   │ Content │
+    // ┏━╯         ╰─┐
+    path.moveTo(left, extended_bottom);
+    path.lineTo(left, tab_bottom);
+    path.lineTo(left + corner_gap, tab_bottom);
+
+    // Draw the bottom-left arc.
+    //   ╭─────────╮
+    //   │ Content │
+    // ┌─╝         ╰─┐
+    path.arcTo(bottom_radius, bottom_radius, 0, SkPath::kSmall_ArcSize,
+               SkPath::kCCW_Direction, tab_left, tab_bottom - bottom_radius);
+
+    // Draw the ascender and top arc, if present.
+    if (extend_to_top) {
+      //   ┎─────────╮
+      //   ┃ Content │
+      // ┌─╯         ╰─┐
+      path.lineTo(tab_left, tab_top);
+    } else {
+      //   ╔─────────╮
+      //   ┃ Content │
+      // ┌─╯         ╰─┐
+      path.lineTo(tab_left, tab_top + top_radius);
+      path.arcTo(top_radius, top_radius, 0, SkPath::kSmall_ArcSize,
+                 SkPath::kCW_Direction, tab_left + top_radius, tab_top);
+    }
+
+    // Draw the top crossbar and top-right curve, if present.
+    if (extend_to_top) {
+      //   ┌━━━━━━━━━┑
+      //   │ Content │
+      // ┌─╯         ╰─┐
+      path.lineTo(tab_right, tab_top);
+
+    } else {
+      //   ╭━━━━━━━━━╗
+      //   │ Content │
+      // ┌─╯         ╰─┐
+      path.lineTo(tab_right - top_radius, tab_top);
+      path.arcTo(top_radius, top_radius, 0, SkPath::kSmall_ArcSize,
+                 SkPath::kCW_Direction, tab_right, tab_top + top_radius);
+    }
+
+    // Draw the descender and bottom-right arc.
+    //   ╭─────────╮
+    //   │ Content ┃
+    // ┌─╯         ╚─┐
+    path.lineTo(tab_right, tab_bottom - bottom_radius);
+    path.arcTo(bottom_radius, bottom_radius, 0, SkPath::kSmall_ArcSize,
+               SkPath::kCCW_Direction, right - corner_gap, tab_bottom);
+
+    // Draw everything right of the bottom-right corner of the tab.
+    //   ╭─────────╮
+    //   │ Content │
+    // ┌─╯         ╰━┓
+    path.lineTo(right, tab_bottom);
+    path.lineTo(right, extended_bottom);
+
+    // Finish the path.
+    path.close();
+
+    // Convert path to be relative to the tab origin.
+    gfx::PointF origin(tab->origin());
+    origin.Scale(scale);
+    path.offset(-origin.x(), -origin.y());
+
+    // Possibly convert back to DIPs.
+    if (render_units == RenderUnits::kDips && scale != 1.0f)
+      path.transform(SkMatrix::MakeScale(1.f / scale));
+
+    return path;
+  }
+
+  SeparatorBounds GetSeparatorBounds(const Tab* tab,
+                                     float scale) const override {
+    const gfx::RectF aligned_bounds =
+        ScaleAndAlignBounds(tab->bounds(), scale, tab->GetStrokeThickness());
+    const int corner_radius = Tab::GetCornerRadius() * scale;
+    const float separator_height = Tab::GetTabSeparatorHeight() * scale;
+    const float separator_thickness = Tab::kSeparatorThickness * scale;
+
+    SeparatorBounds separator_bounds;
+
+    separator_bounds.leading = gfx::RectF(
+        aligned_bounds.x() + corner_radius,
+        aligned_bounds.y() + (aligned_bounds.height() - separator_height) / 2,
+        separator_thickness, separator_height);
+
+    separator_bounds.trailing = separator_bounds.leading;
+    separator_bounds.trailing.set_x(aligned_bounds.right() -
+                                    (corner_radius + separator_thickness));
+
+    gfx::PointF origin(tab->bounds().origin());
+    origin.Scale(scale);
+    separator_bounds.leading.Offset(-origin.x(), -origin.y());
+    separator_bounds.trailing.Offset(-origin.x(), -origin.y());
+
+    return separator_bounds;
+  }
+};
+
+}  // namespace
+
+// static
+const TabStyle* TabStyle::GetInstance() {
+  static base::NoDestructor<GM2TabStyle> instance;
+
+  return instance.get();
+}
diff --git a/chrome/browser/ui/views/tabs/tab_style.h b/chrome/browser/ui/views/tabs/tab_style.h
new file mode 100644
index 0000000..8601dec
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_style.h
@@ -0,0 +1,85 @@
+// 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_TABS_TAB_STYLE_H_
+#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STYLE_H_
+
+#include "base/macros.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/path.h"
+
+class Tab;
+
+// Used to generate a number of outlines for clipping, border painting,
+// interior fill, etc. for tabs.
+class TabStyle {
+ public:
+  // The different types of path GetPath() can return. Different paths are used
+  // in different situations, but most (excluding |kClip|) are roughly the same
+  // shape.
+  enum class PathType {
+    // Interior fill outline. Extends halfway into the border so there are no
+    // gaps between border and fill.
+    kFill,
+    // Inside path of the border. Border is currently the difference between
+    // outside and inside, allowing for a varying thickness of stroke.
+    kInsideBorder,
+    // Outside path of the border. Border is currently the difference between
+    // outside and inside, allowing for a varying thickness of stroke.
+    kOutsideBorder,
+    // The hit test region. May be extended into a rectangle that touches the
+    // top of the bounding box when the window is maximized, for Fitts' Law.
+    kHitTest,
+    // The area inside the tab where children can be rendered, used to clip
+    // child views. Does not have to be the same shape as the border.
+    kClip
+  };
+
+  // How we want the resulting path scaled.
+  enum class RenderUnits {
+    // The path is in pixels, and should have its internal area nicely aligned
+    // to pixel boundaries.
+    kPixels,
+    // The path is in DIPs. It will likely be calculated in pixels and then
+    // scaled back down.
+    kDips
+  };
+
+  // If we want to draw vertical separators between tabs, these are the leading
+  // and trailing separator stroke rectangles.
+  struct SeparatorBounds {
+    gfx::RectF leading;
+    gfx::RectF trailing;
+  };
+
+  // Gets the specific |path_type| associated with the specific |tab|.
+  // If |force_active| is true, applies an active appearance on the tab (usually
+  // involving painting an optional stroke) even if the tab is not the active
+  // tab.
+  virtual gfx::Path GetPath(
+      const Tab* tab,
+      PathType path_type,
+      float scale,
+      bool force_active = false,
+      RenderUnits render_units = RenderUnits::kPixels) const = 0;
+
+  // Gets the bounds for the leading and trailing separators for a tab.
+  virtual SeparatorBounds GetSeparatorBounds(const Tab* tab,
+                                             float scale) const = 0;
+
+  // Gets the currently active tab style.
+  // TODO(dfried): As we move more of the rendering code into TabStyle from
+  // Tab, we may need to have one instance of TabStyle per tab instead of a
+  // singleton (e.g. for caching purposes).
+  static const TabStyle* GetInstance();
+
+ protected:
+  // Avoid implicitly-deleted constructor.
+  TabStyle() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TabStyle);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STYLE_H_
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index 0e6048b..4aef0d84 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <utility>
+
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/layout_constants.h"
@@ -83,6 +85,9 @@
       gfx::Path* clip) override {
     return true;
   }
+  bool ShouldPaintTab(const Tab* tab, float scale, gfx::Path* clip) override {
+    return true;
+  }
   int GetStrokeThickness() const override { return 0; }
   bool CanPaintThrobberToLayer() const override {
     return paint_throbber_to_layer_;
@@ -157,7 +162,7 @@
     return tab.title_->bounds().width();
   }
 
-  static void EndTitleAnimation(Tab& tab) { tab.title_animation_.End(); }
+  static void EndTitleAnimation(Tab* tab) { tab->title_animation_.End(); }
 
   static void LayoutTab(Tab* tab) { tab->Layout(); }
 
@@ -205,11 +210,13 @@
       switch (VisibleIconCount(tab)) {
         case 1:
           EXPECT_FALSE(tab.showing_close_button_);
-          if (tab.data_.alert_state == TabAlertState::NONE ||
-              tab.center_favicon_)
+          if (tab.data_.alert_state == TabAlertState::NONE) {
             EXPECT_FALSE(tab.showing_alert_indicator_);
-          if (tab.center_favicon_)
             EXPECT_TRUE(tab.showing_icon_);
+          } else {
+            EXPECT_FALSE(tab.showing_icon_);
+            EXPECT_TRUE(tab.showing_alert_indicator_);
+          }
           break;
         case 2:
           EXPECT_TRUE(tab.showing_icon_);
@@ -228,7 +235,7 @@
     // are fully within the contents bounds.
     const gfx::Rect contents_bounds = tab.GetContentsBounds();
     if (tab.showing_icon_) {
-      if (tab.center_favicon_) {
+      if (tab.center_icon_) {
         EXPECT_LE(tab.icon_->x(), contents_bounds.x());
       } else {
         EXPECT_LE(contents_bounds.x(), tab.icon_->x());
@@ -252,7 +259,13 @@
         EXPECT_LE(tab.title_->bounds().right(),
                   GetAlertIndicatorBounds(tab).x());
       }
-      EXPECT_LE(GetAlertIndicatorBounds(tab).right(), contents_bounds.right());
+      if (tab.center_icon_) {
+        EXPECT_LE(contents_bounds.right(),
+                  GetAlertIndicatorBounds(tab).right());
+      } else {
+        EXPECT_LE(GetAlertIndicatorBounds(tab).right(),
+                  contents_bounds.right());
+      }
       EXPECT_LE(contents_bounds.y(), GetAlertIndicatorBounds(tab).y());
       EXPECT_LE(GetAlertIndicatorBounds(tab).bottom(),
                 contents_bounds.bottom());
@@ -736,7 +749,7 @@
   TabRendererData data;
   data.show_icon = false;
   tab.SetData(data);
-  EndTitleAnimation(tab);
+  EndTitleAnimation(&tab);
   EXPECT_FALSE(icon->visible());
   // Title should be placed where the favicon was.
   EXPECT_EQ(icon_x, GetTabTitle(tab)->x());
diff --git a/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc b/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc
index 3edcc57d..cb1da97a 100644
--- a/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc
+++ b/chrome/browser/ui/webui/local_discovery/local_discovery_ui.cc
@@ -8,8 +8,6 @@
 
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
 #include "chrome/browser/ui/webui/metrics_handler.h"
 #include "chrome/common/pref_names.h"
@@ -59,9 +57,6 @@
                              IDS_LOCAL_DISCOVERY_ADDING_PRINTER_MESSAGE2);
   source->AddLocalizedString("addingDeviceMessage1",
                              IDS_LOCAL_DISCOVERY_ADDING_DEVICE_MESSAGE1);
-  source->AddLocalizedString("addingDeviceConfirmCodeMessage",
-                             IDS_LOCAL_DISCOVERY_CONFIRM_CODE_MESSAGE);
-  source->AddLocalizedString("confirmCode", IDS_LOCAL_DISCOVERY_CONFIRM_CODE);
   source->AddLocalizedString("devicesTitle",
                              IDS_LOCAL_DISCOVERY_DEVICES_PAGE_TITLE);
   source->AddLocalizedString("noDescriptionDevice",
@@ -96,7 +91,6 @@
                              IDS_LOCAL_DISCOVERY_AVAILABLE_DEVICES);
   source->AddLocalizedString("myDevicesTitle",
                              IDS_LOCAL_DISCOVERY_MY_DEVICES);
-  source->AddLocalizedString("backButton", IDS_SETTINGS_TITLE);
 
   // Cloud print connector-related strings.
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW) && !defined(OS_CHROMEOS)
@@ -125,11 +119,6 @@
     : WebUIController(web_ui) {
   // Set up the chrome://devices/ source.
   content::WebUIDataSource* source = CreateLocalDiscoveryHTMLSource();
-  Browser* browser =
-      chrome::FindBrowserWithWebContents(web_ui->GetWebContents());
-  // Show a back button pointing to Settings if the browser has no location bar.
-  if (browser && browser->is_trusted_source())
-    source->AddString("backButtonURL", chrome::kChromeUISettingsURL);
   content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source);
 
   // TODO(gene): Use LocalDiscoveryUIHandler to send updated to the devices
diff --git a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
index 1fa7b8a..c408294f 100644
--- a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
+++ b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
@@ -116,9 +116,8 @@
 }
 
 LocalDiscoveryUIHandler::LocalDiscoveryUIHandler()
-    : is_visible_(false),
-      failed_list_count_(0),
-      succeded_list_count_(0) {
+    : failed_list_count_(0), succeded_list_count_(0) {
+  g_num_visible++;
 }
 
 LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
@@ -128,7 +127,7 @@
   if (identity_manager)
     identity_manager->RemoveObserver(this);
   ResetCurrentRegistration();
-  SetIsVisible(false);
+  g_num_visible--;
 }
 
 // static
@@ -141,10 +140,6 @@
       "start", base::BindRepeating(&LocalDiscoveryUIHandler::HandleStart,
                                    base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
-      "isVisible",
-      base::BindRepeating(&LocalDiscoveryUIHandler::HandleIsVisible,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
       "registerDevice",
       base::BindRepeating(&LocalDiscoveryUIHandler::HandleRegisterDevice,
                           base::Unretained(this)));
@@ -215,13 +210,6 @@
   CheckUserLoggedIn();
 }
 
-void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue* args) {
-  bool is_visible = false;
-  bool rv = args->GetBoolean(0, &is_visible);
-  DCHECK(rv);
-  SetIsVisible(is_visible);
-}
-
 void LocalDiscoveryUIHandler::HandleRegisterDevice(
     const base::ListValue* args) {
   std::string device;
@@ -471,14 +459,6 @@
       "local_discovery.onRegistrationSuccess", device_value);
 }
 
-void LocalDiscoveryUIHandler::SetIsVisible(bool visible) {
-  if (visible == is_visible_)
-    return;
-
-  g_num_visible += visible ? 1 : -1;
-  is_visible_ = visible;
-}
-
 std::string LocalDiscoveryUIHandler::GetSyncAccount() const {
   Profile* profile = Profile::FromWebUI(web_ui());
   identity::IdentityManager* identity_manager =
diff --git a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h
index 440c646..072beb1 100644
--- a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h
+++ b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h
@@ -104,9 +104,6 @@
   // For when the page is ready to receive device notifications.
   void HandleStart(const base::ListValue* args);
 
-  // For when a visibility change occurs.
-  void HandleIsVisible(const base::ListValue* args);
-
   // For when a user choice is made.
   void HandleRegisterDevice(const base::ListValue* args);
 
@@ -137,9 +134,6 @@
   // Singal to the web interface that registration has finished.
   void SendRegisterDone(const std::string& service_name);
 
-  // Set the visibility of the page.
-  void SetIsVisible(bool visible);
-
   // Get the sync account email.
   std::string GetSyncAccount() const;
 
@@ -196,9 +190,6 @@
   // The device lister used to list devices on the local network.
   std::unique_ptr<cloud_print::PrivetDeviceLister> privet_lister_;
 
-  // Whether or not the page is marked as visible.
-  bool is_visible_;
-
   // List of printers from cloud print.
   std::unique_ptr<cloud_print::GCDApiFlow> cloud_print_printer_list_;
   std::vector<cloud_print::CloudPrintPrinterList::Device> cloud_devices_;
diff --git a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
index b80afb6a..1dbcf03 100644
--- a/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
+++ b/chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc
@@ -162,8 +162,6 @@
                           IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML);
   source->AddResourcePath("dialog_focus_manager.js",
                           IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS);
-  source->AddResourcePath("dnd_chip.html", IDR_MD_BOOKMARKS_DND_CHIP_HTML);
-  source->AddResourcePath("dnd_chip.js", IDR_MD_BOOKMARKS_DND_CHIP_JS);
   source->AddResourcePath("dnd_manager.html",
                           IDR_MD_BOOKMARKS_DND_MANAGER_HTML);
   source->AddResourcePath("dnd_manager.js", IDR_MD_BOOKMARKS_DND_MANAGER_JS);
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc b/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
index 956a6b38..c8fbaecf 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.cc
@@ -231,6 +231,9 @@
     const gfx::Size& page_size,
     const scoped_refptr<base::RefCountedMemory>& print_data,
     PrintCallback callback) {
+  size_t size_in_kb = print_data->size() / 1024;
+  UMA_HISTOGRAM_MEMORY_KB("Printing.CUPS.PrintDocumentSize", size_in_kb);
+
   printing::StartLocalPrint(ticket_json, print_data, preview_web_contents_,
                             std::move(callback));
 }
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 5f0cdc2..6014e45 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
@@ -1117,6 +1117,10 @@
        IDS_SETTINGS_EASY_UNLOCK_PROXIMITY_THRESHOLD_FAR},
       {"easyUnlockProximityThresholdVeryFar",
        IDS_SETTINGS_EASY_UNLOCK_PROXIMITY_THRESHOLD_VERY_FAR},
+      {"easyUnlockUnlockDeviceOnly",
+       IDS_SETTINGS_EASY_UNLOCK_UNLOCK_DEVICE_ONLY},
+      {"easyUnlockUnlockDeviceAndAllowSignin",
+       IDS_SETTINGS_EASY_UNLOCK_UNLOCK_DEVICE_AND_ALLOW_SIGNIN},
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
                           arraysize(localized_strings));
@@ -2663,6 +2667,8 @@
        IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE_EXPLANATION},
       {"multideviceForgetDeviceDialogMessage",
        IDS_SETTINGS_MULTIDEVICE_FORGET_DEVICE_DIALOG_MESSAGE},
+      {"multideviceSmartLockOptions",
+       IDS_SETTINGS_PEOPLE_LOCK_SCREEN_OPTIONS_LOCK},
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
                           arraysize(localized_strings));
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index 53ae802..a07f37c 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -148,6 +148,9 @@
   // apps experiments end.
   html_source->AddResourcePath("shared/chooser_shared_css.html",
                                IDR_NUX_CHOOSER_SHARED_CSS);
+  html_source->AddResourcePath(
+      "shared/i18n_setup.html",
+      IDR_WELCOME_ONBOARDING_WELCOME_SHARED_I18N_SETUP_HTML);
 
   if (base::FeatureList::IsEnabled(nux::kNuxOnboardingFeature)) {
     web_ui->AddMessageHandler(std::make_unique<nux::SetAsDefaultHandler>());
diff --git a/chrome/browser/vr/service/browser_xr_runtime.cc b/chrome/browser/vr/service/browser_xr_runtime.cc
index 4813c1d..fccecce 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.cc
+++ b/chrome/browser/vr/service/browser_xr_runtime.cc
@@ -17,20 +17,21 @@
       display_info_(std::move(display_info)),
       binding_(this),
       weak_ptr_factory_(this) {
-  device::mojom::XRRuntimeEventListenerPtr listener;
+  device::mojom::XRRuntimeEventListenerAssociatedPtr listener;
   binding_.Bind(mojo::MakeRequest(&listener));
 
   // Unretained is safe because we are calling through an InterfacePtr we own,
   // so we won't be called after runtime_ is destroyed.
   runtime_->ListenToDeviceChanges(
-      std::move(listener),
+      listener.PassInterface(),
       base::BindOnce(&BrowserXRRuntime::OnInitialDevicePropertiesReceived,
                      base::Unretained(this)));
 }
 
 void BrowserXRRuntime::OnInitialDevicePropertiesReceived(
     device::mojom::VRDisplayInfoPtr display_info) {
-  OnDisplayInfoChanged(std::move(display_info));
+  if (!display_info_)
+    OnDisplayInfoChanged(std::move(display_info));
 }
 
 BrowserXRRuntime::~BrowserXRRuntime() = default;
@@ -79,6 +80,13 @@
   }
 }
 
+void BrowserXRRuntime::OnInitialized() {
+  for (auto& callback : pending_initialization_callbacks_) {
+    std::move(callback).Run(display_info_.Clone());
+  }
+  pending_initialization_callbacks_.clear();
+}
+
 void BrowserXRRuntime::OnRendererDeviceAdded(XRDeviceImpl* device) {
   renderer_device_connections_.insert(device);
 }
@@ -164,6 +172,19 @@
   }
 }
 
+void BrowserXRRuntime::InitializeAndGetDisplayInfo(
+    device::mojom::XRDevice::GetImmersiveVRDisplayInfoCallback callback) {
+  device::mojom::VRDisplayInfoPtr device_info = GetVRDisplayInfo();
+  if (device_info) {
+    std::move(callback).Run(std::move(device_info));
+    return;
+  }
+
+  pending_initialization_callbacks_.push_back(std::move(callback));
+  runtime_->EnsureInitialized(
+      base::BindOnce(&BrowserXRRuntime::OnInitialized, base::Unretained(this)));
+}
+
 void BrowserXRRuntime::OnListeningForActivate(bool is_listening) {
   runtime_->SetListeningForActivate(is_listening);
 }
diff --git a/chrome/browser/vr/service/browser_xr_runtime.h b/chrome/browser/vr/service/browser_xr_runtime.h
index 741f5bf..3c402f2 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.h
+++ b/chrome/browser/vr/service/browser_xr_runtime.h
@@ -10,7 +10,7 @@
 #include "device/vr/public/mojom/isolated_xr_service.mojom.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
 
 namespace content {
 class WebContents;
@@ -56,6 +56,8 @@
   device::mojom::VRDisplayInfoPtr GetVRDisplayInfo() {
     return display_info_.Clone();
   }
+  void InitializeAndGetDisplayInfo(
+      device::mojom::XRDevice::GetImmersiveVRDisplayInfoCallback callback);
 
   // Methods called to support metrics/overlays on Windows.
   void AddObserver(BrowserXRRuntimeObserver* observer) {
@@ -85,6 +87,7 @@
       device::mojom::XRSessionPtr session,
       device::mojom::XRSessionControllerPtr immersive_session_controller);
   void OnImmersiveSessionError();
+  void OnInitialized();
 
   device::mojom::XRRuntimePtr runtime_;
   device::mojom::XRSessionControllerPtr immersive_session_controller_;
@@ -95,7 +98,9 @@
   XRDeviceImpl* listening_for_activation_renderer_device_ = nullptr;
   XRDeviceImpl* presenting_renderer_device_ = nullptr;
 
-  mojo::Binding<device::mojom::XRRuntimeEventListener> binding_;
+  mojo::AssociatedBinding<device::mojom::XRRuntimeEventListener> binding_;
+  std::vector<device::mojom::XRDevice::GetImmersiveVRDisplayInfoCallback>
+      pending_initialization_callbacks_;
 
   base::ObserverList<BrowserXRRuntimeObserver> observers_;
 
diff --git a/chrome/browser/vr/service/xr_device_impl.cc b/chrome/browser/vr/service/xr_device_impl.cc
index fea8f982..183b25af 100644
--- a/chrome/browser/vr/service/xr_device_impl.cc
+++ b/chrome/browser/vr/service/xr_device_impl.cc
@@ -203,9 +203,12 @@
     device::mojom::XRDevice::GetImmersiveVRDisplayInfoCallback callback) {
   BrowserXRRuntime* immersive_runtime =
       XRRuntimeManager::GetInstance()->GetImmersiveRuntime();
-  device::mojom::VRDisplayInfoPtr device_info =
-      immersive_runtime ? immersive_runtime->GetVRDisplayInfo() : nullptr;
-  std::move(callback).Run(std::move(device_info));
+  if (!immersive_runtime) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  immersive_runtime->InitializeAndGetDisplayInfo(std::move(callback));
 }
 
 void XRDeviceImpl::SetInFocusedFrame(bool in_focused_frame) {
diff --git a/chrome/browser/vr/service/xr_runtime_manager.cc b/chrome/browser/vr/service/xr_runtime_manager.cc
index 07a9d36..7a25455 100644
--- a/chrome/browser/vr/service/xr_runtime_manager.cc
+++ b/chrome/browser/vr/service/xr_runtime_manager.cc
@@ -62,7 +62,7 @@
 #if defined(OS_ANDROID)
 #if BUILDFLAG(ENABLE_ARCORE)
     if (base::FeatureList::IsEnabled(features::kWebXrHitTest)) {
-      providers.emplace_back(device::ARCoreDeviceProviderFactory::Create());
+      providers.emplace_back(device::ArCoreDeviceProviderFactory::Create());
     }
 #endif
 
@@ -198,6 +198,12 @@
   if (immersive_runtime) {
     // Listen to changes for this device.
     immersive_runtime->OnRendererDeviceAdded(device);
+
+    // If we don't have display info for the immersive device, get display info
+    // from a different device.
+    if (!immersive_runtime->GetVRDisplayInfo()) {
+      immersive_runtime = nullptr;
+    }
   }
 
   // Get an AR device if there is one.
diff --git a/chrome/browser/web_applications/components/web_app_data_retriever.cc b/chrome/browser/web_applications/components/web_app_data_retriever.cc
index 71fae7c..4b440d6 100644
--- a/chrome/browser/web_applications/components/web_app_data_retriever.cc
+++ b/chrome/browser/web_applications/components/web_app_data_retriever.cc
@@ -70,9 +70,8 @@
 
   // Generate missing icons.
   static constexpr int kIconSizesToGenerate[] = {
-      web_app::icon_size::k32,  web_app::icon_size::k32 * 2,
-      web_app::icon_size::k48,  web_app::icon_size::k48 * 2,
-      web_app::icon_size::k128, web_app::icon_size::k128 * 2,
+      icon_size::k32,     icon_size::k32 * 2, icon_size::k48,
+      icon_size::k48 * 2, icon_size::k128,    icon_size::k128 * 2,
   };
 
   // Get the letter to use in the generated icon.
@@ -97,7 +96,7 @@
     WebApplicationInfo::IconInfo icon_info;
     icon_info.width = size;
     icon_info.height = size;
-    icon_info.data = web_app::GenerateBitmap(size, SK_ColorDKGRAY, icon_letter);
+    icon_info.data = GenerateBitmap(size, SK_ColorDKGRAY, icon_letter);
     icons.push_back(icon_info);
   }
 
diff --git a/chrome/browser/web_applications/components/web_app_helpers.cc b/chrome/browser/web_applications/components/web_app_helpers.cc
index 08205f4..fdfc4fd 100644
--- a/chrome/browser/web_applications/components/web_app_helpers.cc
+++ b/chrome/browser/web_applications/components/web_app_helpers.cc
@@ -24,25 +24,25 @@
 // are no naming conflicts.
 static const char kCrxAppPrefix[] = "_crx_";
 
-std::string GenerateApplicationNameFromAppId(const std::string& app_id) {
+std::string GenerateApplicationNameFromAppId(const AppId& app_id) {
   std::string t(kCrxAppPrefix);
   t.append(app_id);
   return t;
 }
 
-std::string GetAppIdFromApplicationName(const std::string& app_name) {
+AppId GetAppIdFromApplicationName(const std::string& app_name) {
   std::string prefix(kCrxAppPrefix);
   if (app_name.substr(0, prefix.length()) != prefix)
     return std::string();
   return app_name.substr(prefix.length());
 }
 
-static std::string GenerateExtensionHashFromURL(const GURL& url) {
+static std::string GenerateAppHashFromURL(const GURL& url) {
   return crypto::SHA256HashString(url.spec());
 }
 
-std::string GenerateExtensionIdFromURL(const GURL& url) {
-  return crx_file::id_util::GenerateId(GenerateExtensionHashFromURL(url));
+AppId GenerateAppIdFromURL(const GURL& url) {
+  return crx_file::id_util::GenerateId(GenerateAppHashFromURL(url));
 }
 
 // Generate the public key for the fake extension that we synthesize to contain
@@ -56,9 +56,9 @@
 //
 // (*) The comment above says that we hash the manifest URL, but in practice,
 // it seems that we hash the start URL.
-std::string GenerateExtensionKeyFromURL(const GURL& url) {
+std::string GenerateAppKeyFromURL(const GURL& url) {
   std::string key;
-  base::Base64Encode(GenerateExtensionHashFromURL(url), &key);
+  base::Base64Encode(GenerateAppHashFromURL(url), &key);
   return key;
 }
 
diff --git a/chrome/browser/web_applications/components/web_app_helpers.h b/chrome/browser/web_applications/components/web_app_helpers.h
index 2abf80f8..29ebf73 100644
--- a/chrome/browser/web_applications/components/web_app_helpers.h
+++ b/chrome/browser/web_applications/components/web_app_helpers.h
@@ -11,19 +11,22 @@
 
 namespace web_app {
 
+// App ID matches Extension ID.
+using AppId = std::string;
+
 // Compute a deterministic name based on the URL. We use this pseudo name
 // as a key to store window location per application URLs in Browser and
 // as app id for BrowserWindow, shortcut and jump list.
 std::string GenerateApplicationNameFromURL(const GURL& url);
 
 // Compute a deterministic name based on an apps's id.
-std::string GenerateApplicationNameFromAppId(const std::string& app_id);
+std::string GenerateApplicationNameFromAppId(const AppId& app_id);
 
 // Extracts the application id from the app name.
-std::string GetAppIdFromApplicationName(const std::string& app_name);
+AppId GetAppIdFromApplicationName(const std::string& app_name);
 
-// Compute the Extension ID (such as "fedbieoalmbobgfjapopkghdmhgncnaa") or
-// Extension Key, from a web app's URL. Both are derived from a hash of the
+// Compute the App ID (such as "fedbieoalmbobgfjapopkghdmhgncnaa") or
+// App Key, from a web app's URL. Both are derived from a hash of the
 // URL, but are subsequently encoded differently, for historical reasons. The
 // ID is a Base-16 encoded (a=0, b=1, ..., p=15) subset of the hash, and is
 // used as a directory name, sometimes on case-insensitive file systems
@@ -32,10 +35,12 @@
 // For PWAs (progressive web apps), the URL should be the Start URL, explicitly
 // listed in the manifest.
 //
-// For non-PWA web apps, also known as "bookmark apps", the URL is just the
+// For non-PWA web apps, also known as "shortcuts", the URL is just the
 // bookmark URL.
-std::string GenerateExtensionIdFromURL(const GURL& url);
-std::string GenerateExtensionKeyFromURL(const GURL& url);
+//
+// App ID and App Key match Extension ID and Extension Key for migration.
+AppId GenerateAppIdFromURL(const GURL& url);
+std::string GenerateAppKeyFromURL(const GURL& url);
 
 // Returns whether the given |app_url| is a valid bookmark app url.
 bool IsValidWebAppUrl(const GURL& app_url);
diff --git a/chrome/browser/web_applications/components/web_app_helpers_unittest.cc b/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
index 5211250b..d8cf8ed7 100644
--- a/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
@@ -19,16 +19,16 @@
             GenerateApplicationNameFromURL(GURL("https://example.com/path")));
 }
 
-TEST(WebAppHelpers, GenerateExtensionIdFromURL) {
-  EXPECT_EQ("fedbieoalmbobgfjapopkghdmhgncnaa",
-            GenerateExtensionIdFromURL(
-                GURL("https://www.chromestatus.com/features")));
+TEST(WebAppHelpers, GenerateAppIdFromURL) {
+  EXPECT_EQ(
+      "fedbieoalmbobgfjapopkghdmhgncnaa",
+      GenerateAppIdFromURL(GURL("https://www.chromestatus.com/features")));
 
   // The io2016 example is also walked through at
   // https://play.golang.org/p/VrIq_QKFjiV
   EXPECT_EQ(
       "mjgafbdfajpigcjmkgmeokfbodbcfijl",
-      GenerateExtensionIdFromURL(GURL(
+      GenerateAppIdFromURL(GURL(
           "https://events.google.com/io2016/?utm_source=web_app_manifest")));
 }
 
diff --git a/chrome/browser/web_applications/extensions/web_app_extension_shortcut.cc b/chrome/browser/web_applications/extensions/web_app_extension_shortcut.cc
index 3f61534..6765f82 100644
--- a/chrome/browser/web_applications/extensions/web_app_extension_shortcut.cc
+++ b/chrome/browser/web_applications/extensions/web_app_extension_shortcut.cc
@@ -4,10 +4,15 @@
 
 #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
 
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/extension_ui_util.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
@@ -16,6 +21,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/image_loader.h"
+#include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/grit/extensions_browser_resources.h"
 #include "skia/ext/image_operations.h"
@@ -33,6 +39,8 @@
 
 using content::BrowserThread;
 
+namespace web_app {
+
 namespace {
 
 #if defined(OS_MACOSX)
@@ -51,8 +59,8 @@
 const size_t kNumDesiredSizes = base::size(kDesiredSizes);
 #endif
 
-void OnImageLoaded(std::unique_ptr<web_app::ShortcutInfo> shortcut_info,
-                   web_app::ShortcutInfoCallback callback,
+void OnImageLoaded(std::unique_ptr<ShortcutInfo> shortcut_info,
+                   ShortcutInfoCallback callback,
                    gfx::ImageFamily image_family) {
   // If the image failed to load (e.g. if the resource being loaded was empty)
   // use the standard application icon.
@@ -79,32 +87,29 @@
 void UpdateAllShortcutsForShortcutInfo(
     const base::string16& old_app_title,
     base::OnceClosure callback,
-    std::unique_ptr<web_app::ShortcutInfo> shortcut_info) {
+    std::unique_ptr<ShortcutInfo> shortcut_info) {
   base::FilePath shortcut_data_dir =
-      web_app::internals::GetShortcutDataDir(*shortcut_info);
-  web_app::internals::PostShortcutIOTaskAndReply(
-      base::BindOnce(&web_app::internals::UpdatePlatformShortcuts,
-                     shortcut_data_dir, old_app_title),
+      internals::GetShortcutDataDir(*shortcut_info);
+  internals::PostShortcutIOTaskAndReply(
+      base::BindOnce(&internals::UpdatePlatformShortcuts, shortcut_data_dir,
+                     old_app_title),
       std::move(shortcut_info), std::move(callback));
 }
 
 void ScheduleCreatePlatformShortcut(
-    web_app::ShortcutCreationReason reason,
-    const web_app::ShortcutLocations& locations,
-    std::unique_ptr<web_app::ShortcutInfo> shortcut_info) {
+    ShortcutCreationReason reason,
+    const ShortcutLocations& locations,
+    std::unique_ptr<ShortcutInfo> shortcut_info) {
   base::FilePath shortcut_data_dir =
-      web_app::internals::GetShortcutDataDir(*shortcut_info);
-  web_app::internals::PostShortcutIOTask(
-      base::BindOnce(
-          base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts),
-          shortcut_data_dir, locations, reason),
+      internals::GetShortcutDataDir(*shortcut_info);
+  internals::PostShortcutIOTask(
+      base::BindOnce(base::IgnoreResult(&internals::CreatePlatformShortcuts),
+                     shortcut_data_dir, locations, reason),
       std::move(shortcut_info));
 }
 
 }  // namespace
 
-namespace web_app {
-
 void CreateShortcutsWithInfo(ShortcutCreationReason reason,
                              const ShortcutLocations& locations,
                              std::unique_ptr<ShortcutInfo> shortcut_info) {
@@ -139,8 +144,8 @@
 void GetShortcutInfoForApp(const extensions::Extension* extension,
                            Profile* profile,
                            ShortcutInfoCallback callback) {
-  std::unique_ptr<web_app::ShortcutInfo> shortcut_info(
-      web_app::ShortcutInfoForExtensionAndProfile(extension, profile));
+  std::unique_ptr<ShortcutInfo> shortcut_info(
+      ShortcutInfoForExtensionAndProfile(extension, profile));
 
   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
   for (size_t i = 0; i < kNumDesiredSizes; ++i) {
@@ -207,7 +212,7 @@
   return shortcut_info;
 }
 
-bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason,
+bool ShouldCreateShortcutFor(ShortcutCreationReason reason,
                              Profile* profile,
                              const extensions::Extension* extension) {
   // Shortcuts should never be created for component apps, or for apps that
@@ -278,7 +283,7 @@
   std::unique_ptr<ShortcutInfo> shortcut_info(
       ShortcutInfoForExtensionAndProfile(app, profile));
   base::FilePath shortcut_data_dir =
-      web_app::internals::GetShortcutDataDir(*shortcut_info);
+      internals::GetShortcutDataDir(*shortcut_info);
   internals::PostShortcutIOTask(
       base::BindOnce(&internals::DeletePlatformShortcuts, shortcut_data_dir),
       std::move(shortcut_info));
@@ -305,7 +310,7 @@
 void UpdateRelaunchDetailsForApp(Profile* profile,
                                  const extensions::Extension* extension,
                                  HWND hwnd) {
-  web_app::GetShortcutInfoForApp(
+  GetShortcutInfoForApp(
       extension, profile,
       base::BindOnce(&internals::OnShortcutInfoLoadedForSetRelaunchDetails,
                      hwnd));
diff --git a/chrome/browser/web_applications/extensions/web_app_extension_shortcut.h b/chrome/browser/web_applications/extensions/web_app_extension_shortcut.h
index 5dcf41b..21b636b 100644
--- a/chrome/browser/web_applications/extensions/web_app_extension_shortcut.h
+++ b/chrome/browser/web_applications/extensions/web_app_extension_shortcut.h
@@ -5,9 +5,13 @@
 #ifndef CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_H_
 #define CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_WEB_APP_EXTENSION_SHORTCUT_H_
 
-#include "chrome/browser/web_applications/components/web_app_shortcut.h"
+#include <memory>
 
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
 #include "build/build_config.h"
+#include "chrome/browser/web_applications/components/web_app_shortcut.h"
 
 class Profile;
 
@@ -39,7 +43,7 @@
     Profile* profile);
 
 // Whether to create a shortcut for this type of extension.
-bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason,
+bool ShouldCreateShortcutFor(ShortcutCreationReason reason,
                              Profile* profile,
                              const extensions::Extension* extension);
 
@@ -75,22 +79,11 @@
 void UpdateShortcutsForAllApps(Profile* profile, base::OnceClosure callback);
 
 #if defined(OS_WIN)
-
 // Update the relaunch details for the given app's window, making the taskbar
 // group's "Pin to the taskbar" button function correctly.
 void UpdateRelaunchDetailsForApp(Profile* profile,
                                  const extensions::Extension* extension,
                                  HWND hwnd);
-
-namespace internals {
-
-// Returns the Windows user-level shortcut paths that are specified in
-// |creation_locations|.
-std::vector<base::FilePath> GetShortcutPaths(
-    const ShortcutLocations& creation_locations);
-
-}  // namespace internals
-
 #endif  // defined(OS_WIN)
 
 }  // namespace web_app
diff --git a/chrome/common/apps/platform_apps/BUILD.gn b/chrome/common/apps/platform_apps/BUILD.gn
index b8ddfa0..f94a5b2 100644
--- a/chrome/common/apps/platform_apps/BUILD.gn
+++ b/chrome/common/apps/platform_apps/BUILD.gn
@@ -13,6 +13,13 @@
     "chrome_apps_api_permissions.h",
     "chrome_apps_api_provider.cc",
     "chrome_apps_api_provider.h",
+    "chrome_apps_message_generator.cc",
+    "chrome_apps_message_generator.h",
+    "chrome_apps_messages.h",
+    "media_galleries_permission.cc",
+    "media_galleries_permission.h",
+    "media_galleries_permission_data.cc",
+    "media_galleries_permission_data.h",
   ]
 
   deps = [
@@ -20,5 +27,6 @@
     "//chrome/common/apps/platform_apps/api",
     "//chrome/common/apps/platform_apps/api:apps_features",
     "//extensions/common",
+    "//ipc",
   ]
 }
diff --git a/chrome/common/apps/platform_apps/OWNERS b/chrome/common/apps/platform_apps/OWNERS
new file mode 100644
index 0000000..42444bc
--- /dev/null
+++ b/chrome/common/apps/platform_apps/OWNERS
@@ -0,0 +1,2 @@
+per-file *_messages*.h=set noparent
+per-file *_messages*.h=file://ipc/SECURITY_OWNERS
diff --git a/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc b/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc
index 9380f85b..fcf51395 100644
--- a/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc
+++ b/chrome/common/apps/platform_apps/chrome_apps_api_permissions.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/common/apps/platform_apps/chrome_apps_api_permissions.h"
 
-#include "extensions/common/permissions/media_galleries_permission.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 
 namespace chrome_apps_api_permissions {
 namespace {
@@ -25,7 +25,7 @@
      extensions::APIPermissionInfo::kFlagCannotBeOptional},
     {extensions::APIPermission::kMediaGalleries, "mediaGalleries",
      extensions::APIPermissionInfo::kFlagNone,
-     &CreateAPIPermission<extensions::MediaGalleriesPermission>},
+     &CreateAPIPermission<chrome_apps::MediaGalleriesPermission>},
 };
 
 }  // namespace
diff --git a/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc b/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc
new file mode 100644
index 0000000..bbfda936
--- /dev/null
+++ b/chrome/common/apps/platform_apps/chrome_apps_message_generator.cc
@@ -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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "chrome/common/apps/platform_apps/chrome_apps_message_generator.h"
+}  // namespace IPC
+
diff --git a/chrome/common/apps/platform_apps/chrome_apps_message_generator.h b/chrome/common/apps/platform_apps/chrome_apps_message_generator.h
new file mode 100644
index 0000000..1903b73
--- /dev/null
+++ b/chrome/common/apps/platform_apps/chrome_apps_message_generator.h
@@ -0,0 +1,11 @@
+// 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.
+
+// Multiply-included file, hence no include guard.
+
+#undef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#include "chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#error "Failed to include header chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#endif
diff --git a/chrome/common/apps/platform_apps/chrome_apps_messages.h b/chrome/common/apps/platform_apps/chrome_apps_messages.h
new file mode 100644
index 0000000..3169f24
--- /dev/null
+++ b/chrome/common/apps/platform_apps/chrome_apps_messages.h
@@ -0,0 +1,17 @@
+// 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_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
+
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START ChromeAppsMsgStart
+
+IPC_STRUCT_TRAITS_BEGIN(chrome_apps::MediaGalleriesPermissionData)
+  IPC_STRUCT_TRAITS_MEMBER(permission())
+IPC_STRUCT_TRAITS_END()
+
+#endif  // CHROME_COMMON_APPS_PLATFORM_APPS_CHROME_APPS_MESSAGES_H_
diff --git a/extensions/common/permissions/media_galleries_permission.cc b/chrome/common/apps/platform_apps/media_galleries_permission.cc
similarity index 77%
rename from extensions/common/permissions/media_galleries_permission.cc
rename to chrome/common/apps/platform_apps/media_galleries_permission.cc
index 10fb24c..621bb130 100644
--- a/extensions/common/permissions/media_galleries_permission.cc
+++ b/chrome/common/apps/platform_apps/media_galleries_permission.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "extensions/common/permissions/media_galleries_permission.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 
 #include <stddef.h>
 
@@ -12,16 +12,17 @@
 #include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
 #include "extensions/common/permissions/permissions_info.h"
-#include "extensions/strings/grit/extensions_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 
-namespace extensions {
+namespace chrome_apps {
 
 namespace {
 
 // copyTo permission requires delete permission as a prerequisite.
 // delete permission requires read permission as a prerequisite.
-bool IsValidPermissionSet(bool has_read, bool has_copy_to, bool has_delete,
+bool IsValidPermissionSet(bool has_read,
+                          bool has_copy_to,
+                          bool has_delete,
                           std::string* error) {
   if (has_copy_to) {
     if (has_read && has_delete)
@@ -49,13 +50,11 @@
 const char MediaGalleriesPermission::kDeletePermission[] = "delete";
 
 MediaGalleriesPermission::MediaGalleriesPermission(
-    const APIPermissionInfo* info)
-  : SetDisjunctionPermission<MediaGalleriesPermissionData,
-                             MediaGalleriesPermission>(info) {
-}
+    const extensions::APIPermissionInfo* info)
+    : SetDisjunctionPermission<MediaGalleriesPermissionData,
+                               MediaGalleriesPermission>(info) {}
 
-MediaGalleriesPermission::~MediaGalleriesPermission() {
-}
+MediaGalleriesPermission::~MediaGalleriesPermission() {}
 
 bool MediaGalleriesPermission::FromValue(
     const base::Value* value,
@@ -64,14 +63,12 @@
   size_t unhandled_permissions_count = 0;
   if (unhandled_permissions)
     unhandled_permissions_count = unhandled_permissions->size();
-  bool parsed_ok =
-      SetDisjunctionPermission<MediaGalleriesPermissionData,
-                               MediaGalleriesPermission>::FromValue(
-                                   value, error, unhandled_permissions);
+  bool parsed_ok = SetDisjunctionPermission<
+      MediaGalleriesPermissionData,
+      MediaGalleriesPermission>::FromValue(value, error, unhandled_permissions);
   if (unhandled_permissions) {
     for (size_t i = unhandled_permissions_count;
-         i < unhandled_permissions->size();
-         i++) {
+         i < unhandled_permissions->size(); i++) {
       (*unhandled_permissions)[i] =
           "{\"mediaGalleries\": [" + (*unhandled_permissions)[i] + "]}";
     }
@@ -83,7 +80,8 @@
   bool has_copy_to = false;
   bool has_delete = false;
   for (std::set<MediaGalleriesPermissionData>::const_iterator it =
-      data_set_.begin(); it != data_set_.end(); ++it) {
+           data_set_.begin();
+       it != data_set_.end(); ++it) {
     if (it->permission() == kAllAutoDetectedPermission) {
       continue;
     }
@@ -110,8 +108,8 @@
   return IsValidPermissionSet(has_read, has_copy_to, has_delete, error);
 }
 
-PermissionIDSet MediaGalleriesPermission::GetPermissions() const {
-  PermissionIDSet result;
+extensions::PermissionIDSet MediaGalleriesPermission::GetPermissions() const {
+  extensions::PermissionIDSet result;
 
   bool has_all_auto_detected = false;
   bool has_read = false;
@@ -144,18 +142,18 @@
 
   // Separate PermissionMessage IDs for read, copyTo, and delete. Otherwise an
   // extension can silently gain new access capabilities.
-  result.insert(APIPermission::kMediaGalleriesAllGalleriesRead);
+  result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesRead);
 
   // For copyTo and delete, the proper combined permission message will be
   // derived in ChromePermissionMessageProvider::GetWarningMessages(), such
   // that the user get 1 entry for all media galleries access permissions,
   // rather than several separate entries.
   if (has_copy_to)
-    result.insert(APIPermission::kMediaGalleriesAllGalleriesCopyTo);
+    result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesCopyTo);
   if (has_delete)
-    result.insert(APIPermission::kMediaGalleriesAllGalleriesDelete);
+    result.insert(extensions::APIPermission::kMediaGalleriesAllGalleriesDelete);
 
   return result;
 }
 
-}  // namespace extensions
+}  // namespace chrome_apps
diff --git a/extensions/common/permissions/media_galleries_permission.h b/chrome/common/apps/platform_apps/media_galleries_permission.h
similarity index 67%
rename from extensions/common/permissions/media_galleries_permission.h
rename to chrome/common/apps/platform_apps/media_galleries_permission.h
index 706cd5ba..c5792fda 100644
--- a/extensions/common/permissions/media_galleries_permission.h
+++ b/chrome/common/apps/platform_apps/media_galleries_permission.h
@@ -2,14 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
-#define EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
 
+#include "chrome/common/apps/platform_apps/chrome_apps_messages.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
 #include "extensions/common/permissions/api_permission.h"
-#include "extensions/common/permissions/media_galleries_permission_data.h"
 #include "extensions/common/permissions/set_disjunction_permission.h"
 
-namespace extensions {
+namespace chrome_apps {
 
 // Media Galleries permissions are as follows:
 //   <media-galleries-permission-pattern>
@@ -27,16 +28,16 @@
 //   "mediaGalleries",
 // TODO(devlin): Move this class to chrome/common/apps/platform_apps.
 class MediaGalleriesPermission
-  : public SetDisjunctionPermission<MediaGalleriesPermissionData,
-                                    MediaGalleriesPermission> {
+    : public extensions::SetDisjunctionPermission<MediaGalleriesPermissionData,
+                                                  MediaGalleriesPermission> {
  public:
-  struct CheckParam : public APIPermission::CheckParam {
+  struct CheckParam : public extensions::APIPermission::CheckParam {
     explicit CheckParam(const std::string& permission)
-      : permission(permission) {}
+        : permission(permission) {}
     const std::string permission;
   };
 
-  explicit MediaGalleriesPermission(const APIPermissionInfo* info);
+  explicit MediaGalleriesPermission(const extensions::APIPermissionInfo* info);
   ~MediaGalleriesPermission() override;
 
   // SetDisjunctionPermission overrides.
@@ -46,8 +47,8 @@
                  std::string* error,
                  std::vector<std::string>* unhandled_permissions) override;
 
-  // APIPermission overrides.
-  PermissionIDSet GetPermissions() const override;
+  // extensions::APIPermission overrides.
+  extensions::PermissionIDSet GetPermissions() const override;
 
   // Permission strings.
   static const char kAllAutoDetectedPermission[];
@@ -57,6 +58,6 @@
   static const char kDeletePermission[];
 };
 
-}  // namespace extensions
+}  // namespace chrome_apps
 
-#endif  // EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_H_
+#endif  // CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_H_
diff --git a/extensions/common/permissions/media_galleries_permission_data.cc b/chrome/common/apps/platform_apps/media_galleries_permission_data.cc
similarity index 79%
rename from extensions/common/permissions/media_galleries_permission_data.cc
rename to chrome/common/apps/platform_apps/media_galleries_permission_data.cc
index 330923d..148abba 100644
--- a/extensions/common/permissions/media_galleries_permission_data.cc
+++ b/chrome/common/apps/platform_apps/media_galleries_permission_data.cc
@@ -2,19 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "extensions/common/permissions/media_galleries_permission_data.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
 
 #include "base/strings/string_util.h"
 #include "base/values.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
 
-namespace extensions {
+namespace chrome_apps {
 
-MediaGalleriesPermissionData::MediaGalleriesPermissionData() {
-}
+MediaGalleriesPermissionData::MediaGalleriesPermissionData() {}
 
 bool MediaGalleriesPermissionData::Check(
-    const APIPermission::CheckParam* param) const {
+    const extensions::APIPermission::CheckParam* param) const {
   if (!param)
     return false;
 
@@ -24,7 +23,7 @@
 }
 
 std::unique_ptr<base::Value> MediaGalleriesPermissionData::ToValue() const {
-  return std::unique_ptr<base::Value>(new base::Value(permission_));
+  return std::make_unique<base::Value>(permission_);
 }
 
 bool MediaGalleriesPermissionData::FromValue(const base::Value* value) {
@@ -58,4 +57,4 @@
   return permission_ == rhs.permission_;
 }
 
-}  // namespace extensions
+}  // namespace chrome_apps
diff --git a/extensions/common/permissions/media_galleries_permission_data.h b/chrome/common/apps/platform_apps/media_galleries_permission_data.h
similarity index 77%
rename from extensions/common/permissions/media_galleries_permission_data.h
rename to chrome/common/apps/platform_apps/media_galleries_permission_data.h
index db8df839..495cb48d 100644
--- a/extensions/common/permissions/media_galleries_permission_data.h
+++ b/chrome/common/apps/platform_apps/media_galleries_permission_data.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
-#define EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+#ifndef CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+#define CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
 
 #include <memory>
 #include <string>
@@ -14,7 +14,7 @@
 class Value;
 }
 
-namespace extensions {
+namespace chrome_apps {
 
 // A MediaGalleriesPermissionData instance represents a single part of the
 // MediaGalleriesPermission. e.g. "read" or "allAutoDetected".
@@ -24,7 +24,7 @@
 
   // Check if |param| (which must be a MediaGalleriesPermission::CheckParam)
   // matches the encapsulated attribute.
-  bool Check(const APIPermission::CheckParam* param) const;
+  bool Check(const extensions::APIPermission::CheckParam* param) const;
 
   // Convert |this| into a base::Value.
   std::unique_ptr<base::Value> ToValue() const;
@@ -45,6 +45,6 @@
   std::string permission_;
 };
 
-}  // namespace extensions
+}  // namespace chrome_apps
 
-#endif  // EXTENSIONS_COMMON_PERMISSIONS_MEDIA_GALLERIES_PERMISSION_DATA_H_
+#endif  // CHROME_COMMON_APPS_PLATFORM_APPS_MEDIA_GALLERIES_PERMISSION_DATA_H_
diff --git a/chrome/common/extensions/permissions/media_galleries_permission_unittest.cc b/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc
similarity index 85%
rename from chrome/common/extensions/permissions/media_galleries_permission_unittest.cc
rename to chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc
index 15c29bc..d8fbc24 100644
--- a/chrome/common/extensions/permissions/media_galleries_permission_unittest.cc
+++ b/chrome/common/apps/platform_apps/media_galleries_permission_unittest.cc
@@ -7,20 +7,21 @@
 #include <memory>
 
 #include "base/values.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission.h"
+#include "chrome/common/apps/platform_apps/media_galleries_permission_data.h"
 #include "extensions/common/permissions/api_permission.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
-#include "extensions/common/permissions/media_galleries_permission_data.h"
 #include "extensions/common/permissions/permissions_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using content::SocketPermissionRequest;
 using extensions::SocketPermissionData;
 
-namespace extensions {
+namespace chrome_apps {
 
 namespace {
 
-void CheckFromValue(APIPermission* permission, base::ListValue* value,
+void CheckFromValue(extensions::APIPermission* permission,
+                    base::ListValue* value,
                     bool success_expected) {
   std::string error;
   std::vector<std::string> unhandled;
@@ -30,10 +31,11 @@
 }
 
 TEST(MediaGalleriesPermissionTest, GoodValues) {
-  const APIPermissionInfo* permission_info =
-    PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission(
+  std::unique_ptr<extensions::APIPermission> permission(
       permission_info->CreateAPIPermission());
 
   // access_type + all_detected
@@ -87,10 +89,11 @@
 }
 
 TEST(MediaGalleriesPermissionTest, BadValues) {
-  const APIPermissionInfo* permission_info =
-    PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission(
+  std::unique_ptr<extensions::APIPermission> permission(
       permission_info->CreateAPIPermission());
 
   // copyTo and delete without read
@@ -133,10 +136,11 @@
 TEST(MediaGalleriesPermissionTest, UnknownValues) {
   std::string error;
   std::vector<std::string> unhandled;
-  const APIPermissionInfo* permission_info =
-      PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission(
+  std::unique_ptr<extensions::APIPermission> permission(
       permission_info->CreateAPIPermission());
 
   // A good one and an unknown one.
@@ -168,12 +172,13 @@
 }
 
 TEST(MediaGalleriesPermissionTest, Equal) {
-  const APIPermissionInfo* permission_info =
-    PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission1(
+  std::unique_ptr<extensions::APIPermission> permission1(
       permission_info->CreateAPIPermission());
-  std::unique_ptr<APIPermission> permission2(
+  std::unique_ptr<extensions::APIPermission> permission2(
       permission_info->CreateAPIPermission());
 
   std::unique_ptr<base::ListValue> value(new base::ListValue());
@@ -220,12 +225,13 @@
 }
 
 TEST(MediaGalleriesPermissionTest, NotEqual) {
-  const APIPermissionInfo* permission_info =
-    PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission1(
+  std::unique_ptr<extensions::APIPermission> permission1(
       permission_info->CreateAPIPermission());
-  std::unique_ptr<APIPermission> permission2(
+  std::unique_ptr<extensions::APIPermission> permission2(
       permission_info->CreateAPIPermission());
 
   std::unique_ptr<base::ListValue> value(new base::ListValue());
@@ -243,12 +249,13 @@
 }
 
 TEST(MediaGalleriesPermissionTest, ToFromValue) {
-  const APIPermissionInfo* permission_info =
-    PermissionsInfo::GetInstance()->GetByID(APIPermission::kMediaGalleries);
+  const extensions::APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(
+          extensions::APIPermission::kMediaGalleries);
 
-  std::unique_ptr<APIPermission> permission1(
+  std::unique_ptr<extensions::APIPermission> permission1(
       permission_info->CreateAPIPermission());
-  std::unique_ptr<APIPermission> permission2(
+  std::unique_ptr<extensions::APIPermission> permission2(
       permission_info->CreateAPIPermission());
 
   std::unique_ptr<base::ListValue> value(new base::ListValue());
@@ -294,4 +301,4 @@
 
 }  // namespace
 
-}  // namespace extensions
+}  // namespace chrome_apps
diff --git a/chrome/common/available_offline_content.mojom b/chrome/common/available_offline_content.mojom
index 2bf374d..f6ac36e 100644
--- a/chrome/common/available_offline_content.mojom
+++ b/chrome/common/available_offline_content.mojom
@@ -68,5 +68,5 @@
   // Opens one of the items returned by List().
   LaunchItem(string item_id, string name_space);
   // Opens the downloads page to view all offline content.
-  LaunchDownloadsPage();
+  LaunchDownloadsPage(bool open_prefetched_articles_tab);
 };
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index e8099d1..e065d53e 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -138,6 +138,12 @@
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
+#if defined(OS_MACOSX)
+// Enables the suggested text touch bar for autocomplete in textfields.
+const base::Feature kTextSuggestionsTouchBar{"TextSuggestionsTouchBar",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 // Enables the blocking of third-party modules.
 // Note: Due to a limitation in the implementation of this feature, it is
@@ -206,6 +212,10 @@
 const base::Feature kDesktopPWAsLinkCapturing{
     "DesktopPWAsLinkCapturing", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Determines whether in scope requests are always opened in the same window.
+const base::Feature kDesktopPWAsStayInWindow{"DesktopPWAsStayInWindow",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Disables downloads of unsafe file types over HTTP.
 const base::Feature kDisallowUnsafeHttpDownloads{
     "DisallowUnsafeHttpDownloads", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 94821af..ab785e67 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -131,6 +131,9 @@
 extern const base::Feature kDesktopPWAsLinkCapturing;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsStayInWindow;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDisallowUnsafeHttpDownloads;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const char kDisallowUnsafeHttpDownloadsParamName[];
@@ -371,6 +374,11 @@
 COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kTabMetricsLogging;
 #endif
 
+#if defined(OS_MACOSX)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kTextSuggestionsTouchBar;
+#endif
+
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kThirdPartyModulesBlocking;
diff --git a/chrome/common/extensions/api/bookmark_manager_private.json b/chrome/common/extensions/api/bookmark_manager_private.json
index 526b4e09..c65c190 100644
--- a/chrome/common/extensions/api/bookmark_manager_private.json
+++ b/chrome/common/extensions/api/bookmark_manager_private.json
@@ -149,6 +149,12 @@
             "minItems": 1
           },
           {
+            "name": "dragNodeIndex",
+            "type": "integer",
+            "minimum": 0,
+            "description": "The index of the dragged node in |idList|"
+          },
+          {
             "name": "isFromTouch",
             "type": "boolean",
             "description": "True if the drag was initiated from touch."
diff --git a/chrome/common/extensions/api/developer_private.idl b/chrome/common/extensions/api/developer_private.idl
index 836318c..a63582e 100644
--- a/chrome/common/extensions/api/developer_private.idl
+++ b/chrome/common/extensions/api/developer_private.idl
@@ -194,16 +194,31 @@
     DOMString[] submessages;
   };
 
+  dictionary SiteControl {
+    // The host pattern for the site.
+    DOMString host;
+    // Whether the pattern has been granted.
+    boolean granted;
+  };
+
+  dictionary SpecificSiteControls {
+    // True if |hosts| contains an all hosts like pattern.
+    boolean hasAllHosts;
+
+    // The site controls for all granted and requested patterns.
+    SiteControl[] hosts;
+  };
+
   dictionary Permissions {
     Permission[] simplePermissions;
 
-    // Only populated for extensions affected by the runtime host
-    // permissions feature.
+    // Only populated for extensions that can be affected by the runtime host
+    // permissions feature.The current host access.
     HostAccess? hostAccess;
 
     // Only populated for extensions affected by the runtime host
     // permissions feature and |hostAccess| is equal to ON_SPECIFIC_SITES.
-    DOMString[]? runtimeHostPermissions;
+    SpecificSiteControls? specificSiteControls;
   };
 
   dictionary ExtensionInfo {
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index ffa1d20..42d769d 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1704,7 +1704,7 @@
     std::string* console_message) {
   *console_message = base::StringPrintf(
       "The SSL certificate used to load resources from %s"
-      " will be distrusted in M70. Once distrusted, users will be prevented"
+      " will be distrusted very soon. Once distrusted, users will be prevented"
       " from loading these resources. See https://g.co/chrome/symantecpkicerts"
       " for more information.",
       url::Origin::Create(url).Serialize().c_str());
diff --git a/chrome/renderer/net/available_offline_content_helper.cc b/chrome/renderer/net/available_offline_content_helper.cc
index 304b31df..d918e1d9 100644
--- a/chrome/renderer/net/available_offline_content_helper.cc
+++ b/chrome/renderer/net/available_offline_content_helper.cc
@@ -19,6 +19,7 @@
 namespace {
 
 using chrome::mojom::AvailableOfflineContentPtr;
+using chrome::mojom::AvailableContentType;
 
 base::Value AvailableContentToValue(const AvailableOfflineContentPtr& content) {
   base::Value value(base::Value::Type::DICTIONARY);
@@ -111,13 +112,12 @@
   if (!BindProvider())
     return;
 
-  provider_->LaunchItem(id, name_space);
-
   for (const AvailableOfflineContentPtr& item : fetched_content_) {
     if (item->id == id && item->name_space == name_space) {
       UMA_HISTOGRAM_ENUMERATION("Net.ErrorPageCounts.SuggestionClicked",
                                 item->content_type);
       RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED);
+      provider_->LaunchItem(id, name_space);
       return;
     }
   }
@@ -128,16 +128,22 @@
   if (!BindProvider())
     return;
   RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED);
-  provider_->LaunchDownloadsPage();
+  provider_->LaunchDownloadsPage(has_prefetched_content_);
 }
 
 void AvailableOfflineContentHelper::AvailableContentReceived(
     base::OnceCallback<void(const std::string& offline_content_json)> callback,
     std::vector<AvailableOfflineContentPtr> content) {
+  has_prefetched_content_ = false;
   fetched_content_ = std::move(content);
   std::string json;
 
   if (!fetched_content_.empty()) {
+    // As prefetched content has the highest priority if at least one piece is
+    // available it will be the at the first position on the list.
+    has_prefetched_content_ = fetched_content_.front()->content_type ==
+                              AvailableContentType::kPrefetchedPage;
+
     RecordSuggestionPresented(fetched_content_);
     RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN);
     base::JSONWriter::Write(AvailableContentListToValue(fetched_content_),
@@ -153,9 +159,12 @@
 void AvailableOfflineContentHelper::SummaryReceived(
     base::OnceCallback<void(const std::string& content_summary_json)> callback,
     chrome::mojom::AvailableOfflineContentSummaryPtr summary) {
+  has_prefetched_content_ = false;
   if (summary->total_items == 0) {
     std::move(callback).Run("");
   } else {
+    has_prefetched_content_ = summary->has_prefetched_page;
+
     std::string json;
     base::JSONWriter::Write(AvailableContentSummaryToValue(summary), &json);
     RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_CONTENT_SUMMARY_SHOWN);
diff --git a/chrome/renderer/net/available_offline_content_helper.h b/chrome/renderer/net/available_offline_content_helper.h
index 18335e06..bfa2855c 100644
--- a/chrome/renderer/net/available_offline_content_helper.h
+++ b/chrome/renderer/net/available_offline_content_helper.h
@@ -65,6 +65,10 @@
   // only so that metrics can be recorded properly on call to LaunchItem().
   std::vector<chrome::mojom::AvailableOfflineContentPtr> fetched_content_;
 
+  // Records if the last received content message indicated that prefetched
+  // articles are available or not.
+  bool has_prefetched_content_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(AvailableOfflineContentHelper);
 };
 
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index 82de7af0..cebdac45 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -2670,7 +2670,7 @@
 
   MOCK_METHOD2(LaunchItem,
                void(const std::string& item_ID, const std::string& name_space));
-  MOCK_METHOD0(LaunchDownloadsPage, void());
+  MOCK_METHOD1(LaunchDownloadsPage, void(bool open_prefetched_articles_tab));
 
   void AddBinding(mojo::ScopedMessagePipeHandle handle) {
     bindings_.AddBinding(this,
diff --git a/chrome/services/isolated_xr_device/BUILD.gn b/chrome/services/isolated_xr_device/BUILD.gn
index ad93a68..f8ed241 100644
--- a/chrome/services/isolated_xr_device/BUILD.gn
+++ b/chrome/services/isolated_xr_device/BUILD.gn
@@ -19,6 +19,7 @@
 
   deps = [
     "//base",
+    "//chrome/common",
     "//device/vr:vr",
     "//device/vr/public/mojom",
     "//device/vr/public/mojom:test_mojom",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 54b519f9..03a8523 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1968,6 +1968,7 @@
         "../browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm",
         "../browser/ui/cocoa/ssl_client_certificate_selector_cocoa_browsertest.mm",
         "../browser/ui/cocoa/touchbar/browser_window_touch_bar_controller_browsertest.mm",
+        "../browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_browsertest.mm",
 
         ## TODO(crbug/845389): Re-Enable the following, which were temporarily
         ## omitted from the build, but still in use by the Cocoa browser.
@@ -2318,6 +2319,7 @@
     "../browser/android/explore_sites/get_catalog_task_unittest.cc",
     "../browser/android/explore_sites/get_images_task_unittest.cc",
     "../browser/android/explore_sites/history_statistics_reporter_unittest.cc",
+    "../browser/android/explore_sites/image_helper_unittest.cc",
     "../browser/android/explore_sites/import_catalog_task_unittest.cc",
     "../browser/android/explore_sites/ntp_json_fetcher_unittest.cc",
     "../browser/android/history_report/data_observer_unittest.cc",
@@ -3718,6 +3720,7 @@
       "../browser/sync_file_system/sync_file_system_test_util.h",
       "../browser/sync_file_system/sync_process_runner_unittest.cc",
       "../browser/sync_file_system/syncable_file_system_util_unittest.cc",
+      "../common/apps/platform_apps/media_galleries_permission_unittest.cc",
       "../common/extensions/api/commands/commands_manifest_unittest.cc",
       "../common/extensions/api/common_extension_api_unittest.cc",
       "../common/extensions/api/extension_action/browser_action_manifest_unittest.cc",
@@ -3769,7 +3772,6 @@
       "../common/extensions/manifest_unittest.cc",
       "../common/extensions/permissions/chrome_permission_message_provider_unittest.cc",
       "../common/extensions/permissions/chrome_permission_message_rules_unittest.cc",
-      "../common/extensions/permissions/media_galleries_permission_unittest.cc",
       "../common/extensions/permissions/permission_set_unittest.cc",
       "../common/extensions/permissions/permissions_data_unittest.cc",
       "../common/extensions/permissions/settings_override_permission_unittest.cc",
@@ -4148,6 +4150,7 @@
         "../browser/ui/cocoa/test/run_loop_testing_unittest.mm",
         "../browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm",
         "../browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm",
+        "../browser/ui/cocoa/touchbar/text_suggestions_touch_bar_controller_unittest.mm",
         "../browser/ui/cocoa/url_drop_target_unittest.mm",
         "../browser/ui/cocoa/view_resizer_pong.h",
         "../browser/ui/cocoa/view_resizer_pong.mm",
@@ -4670,6 +4673,7 @@
       "../browser/ui/search/local_ntp_uitest.cc",
       "../browser/ui/send_mouse_move_uitest_win.cc",
       "../browser/ui/signin_view_controller_interactive_uitest.cc",
+      "../browser/ui/startup/invalid_user_data_dir_interactive_uitest.cc",
       "../browser/ui/startup/startup_browser_creator_interactive_uitest.cc",
       "../browser/ui/tabs/window_activity_watcher_interactive_uitest.cc",
       "../browser/ui/translate/translate_bubble_test_utils.h",
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc
index 582f7ceb..e4c73afe 100644
--- a/chrome/test/base/in_process_browser_test.cc
+++ b/chrome/test/base/in_process_browser_test.cc
@@ -213,12 +213,10 @@
     command_line->AppendSwitchASCII(switches::kHostWindowBounds,
                                     "0+0-1280x800");
   }
-#elif defined(USE_X11)
-  DCHECK(!display::Screen::GetScreen());
-  display::Screen::SetScreenInstance(
-      views::test::TestDesktopScreenX11::GetInstance());
 #endif
 
+  SetScreenInstance();
+
   // Always use a mocked password storage if OS encryption is used (which is
   // when anything sensitive gets stored, including Cookies). Without this on
   // Mac, many tests will hang waiting for a user to approve KeyChain access.
@@ -373,6 +371,14 @@
   return true;
 }
 
+void InProcessBrowserTest::SetScreenInstance() {
+#if defined(USE_X11) && !defined(OS_CHROMEOS)
+  DCHECK(!display::Screen::GetScreen());
+  display::Screen::SetScreenInstance(
+      views::test::TestDesktopScreenX11::GetInstance());
+#endif
+}
+
 #if !defined(OS_MACOSX)
 void InProcessBrowserTest::OpenDevToolsWindow(
     content::WebContents* web_contents) {
diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h
index 2fd1820c..e7464399 100644
--- a/chrome/test/base/in_process_browser_test.h
+++ b/chrome/test/base/in_process_browser_test.h
@@ -181,6 +181,9 @@
   // successful.
   virtual bool SetUpUserDataDirectory() WARN_UNUSED_RESULT;
 
+  // Initializes the display::Screen instance on X11.
+  virtual void SetScreenInstance();
+
   // BrowserTestBase:
   void PreRunTestOnMainThread() override;
   void PostRunTestOnMainThread() override;
diff --git a/chrome/test/data/android/feed/feed_large.gcl.bin b/chrome/test/data/android/feed/feed_large.gcl.bin
index b7653a2e..8e8462e 100644
--- a/chrome/test/data/android/feed/feed_large.gcl.bin
+++ b/chrome/test/data/android/feed/feed_large.gcl.bin
Binary files differ
diff --git a/chrome/test/data/android/feed/feed_paging.gcl.bin b/chrome/test/data/android/feed/feed_paging.gcl.bin
index b2a0ff6..79212ba 100644
--- a/chrome/test/data/android/feed/feed_paging.gcl.bin
+++ b/chrome/test/data/android/feed/feed_paging.gcl.bin
Binary files differ
diff --git a/chrome/test/data/android/feed/feed_with_hero.gcl.bin b/chrome/test/data/android/feed/feed_with_hero.gcl.bin
index b78d6ed..60ce43b 100644
--- a/chrome/test/data/android/feed/feed_with_hero.gcl.bin
+++ b/chrome/test/data/android/feed/feed_with_hero.gcl.bin
Binary files differ
diff --git a/chrome/test/data/android/feed/feed_with_overlay.gcl.bin b/chrome/test/data/android/feed/feed_with_overlay.gcl.bin
index b93b406..62c7504 100644
--- a/chrome/test/data/android/feed/feed_with_overlay.gcl.bin
+++ b/chrome/test/data/android/feed/feed_with_overlay.gcl.bin
Binary files differ
diff --git a/chrome/test/data/arc_default_apps/test_app1/icon_100p_32.png b/chrome/test/data/arc_default_apps/test_app1/icon_100p_32.png
new file mode 100644
index 0000000..1ce8901
--- /dev/null
+++ b/chrome/test/data/arc_default_apps/test_app1/icon_100p_32.png
Binary files differ
diff --git a/chrome/test/data/arc_default_apps/test_app1/icon_200p_32.png b/chrome/test/data/arc_default_apps/test_app1/icon_200p_32.png
new file mode 100644
index 0000000..b40f6a2
--- /dev/null
+++ b/chrome/test/data/arc_default_apps/test_app1/icon_200p_32.png
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/enterprise_device_attributes/update_manifest.xml b/chrome/test/data/extensions/api_test/enterprise_device_attributes/update_manifest.xml
index cbf82cd..3e87870 100644
--- a/chrome/test/data/extensions/api_test/enterprise_device_attributes/update_manifest.xml
+++ b/chrome/test/data/extensions/api_test/enterprise_device_attributes/update_manifest.xml
@@ -1,7 +1,8 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <!--
-  This update manifest points to the enterprise_device_attributes.crx file. It
-  is meant to be used with a URLRequestMockHTTPJob.
+  This update manifest points to the enterprise_device_attributes.crx file.
+  "mock.http" is a placeholder that gets substituted with the test server
+  address in runtime.
 -->
 <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
   <app appid='nbiliclbejdndfpchgkbmfoppjplbdok'>
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml b/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml
index 9487a30..ae6a807 100644
--- a/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml
@@ -1,7 +1,8 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <!--
-  This update manifest points to the enterprise_platform_keys.crx file. It is
-  meant to be used with a URLRequestMockHTTPJob.
+  This update manifest points to the enterprise_platform_keys.crx file.
+  "mock.http" is a placeholder that gets substituted with the test server
+  address in runtime.
 -->
 <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
   <app appid='aecpbnckhoppanpmefllkdkohionpmig'>
diff --git a/chrome/test/data/extensions/signin_screen_manual_test_app/update_manifest.xml b/chrome/test/data/extensions/signin_screen_manual_test_app/update_manifest.xml
index 78037784..2bc4030 100644
--- a/chrome/test/data/extensions/signin_screen_manual_test_app/update_manifest.xml
+++ b/chrome/test/data/extensions/signin_screen_manual_test_app/update_manifest.xml
@@ -1,8 +1,8 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <!--
-  This update manifest points to the ./app_signed_by_webstore.crx file. It is
-  meant to be used with a URLRequestMockHTTPJob, and the hostname must be kept
-  in sync.
+  This update manifest points to the ./app_signed_by_webstore.crx file.
+  "mock.http" is a placeholder that gets substituted with the test server
+  address in runtime.
 -->
 <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
   <app appid='bjaiihebfngildkcjkjckolinodhliff'>
diff --git a/chrome/test/data/extensions/trivial_extension/update_manifest.xml b/chrome/test/data/extensions/trivial_extension/update_manifest.xml
index 86bc6f6..bf1dd7b 100644
--- a/chrome/test/data/extensions/trivial_extension/update_manifest.xml
+++ b/chrome/test/data/extensions/trivial_extension/update_manifest.xml
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <!--
-  This update manifest points to the ./extension.crx file. It is meant to be
-  used with a URLRequestMockHTTPJob, and the hostname must be kept in sync.
+  This update manifest points to the ./extension.crx file. "mock.http" is a
+  placeholder that gets substituted with the test server address in runtime.
 -->
 <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
   <app appid='mockepjebcnmhmhcahfddgfcdgkdifnc'>
diff --git a/chrome/test/data/extensions/trivial_platform_app/update_manifest.xml b/chrome/test/data/extensions/trivial_platform_app/update_manifest.xml
index 14071838..2cceb13b 100644
--- a/chrome/test/data/extensions/trivial_platform_app/update_manifest.xml
+++ b/chrome/test/data/extensions/trivial_platform_app/update_manifest.xml
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <!--
-  This update manifest points to the ./app.crx file. It is meant to be used with
-  a URLRequestMockHTTPJob, and the hostname must be kept in sync.
+  This update manifest points to the ./app.crx file. "mock.http" is a
+  placeholder that gets substituted with the test server address in runtime.
 -->
 <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
   <app appid='mockapnacjbcdncmpkjngjalkhphojek'>
diff --git a/chrome/test/data/webui/cr_elements/cr_slider_test.js b/chrome/test/data/webui/cr_elements/cr_slider_test.js
index 2978110..a811afb 100644
--- a/chrome/test/data/webui/cr_elements/cr_slider_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_slider_test.js
@@ -61,8 +61,9 @@
     pointerEvent('pointermove', ratio);
   }
 
-  function pointerUp(ratio) {
-    pointerEvent('pointerup', ratio);
+  function pointerUp() {
+    // Ignores clientX for pointerup event.
+    pointerEvent('pointerup', 0);
   }
 
   // Ensure that value-changed event bubbles, since users of cr-slider rely on
@@ -118,13 +119,35 @@
     assertEquals(0, crSlider.value);
     pointerMove(2);
     assertEquals(100, crSlider.value);
-    pointerUp(0);
+    pointerUp();
     assertEquals(100, crSlider.value);
     assertEquals(0, crSlider.draggingEventTracker_.listeners_.length);
     pointerMove(.25);
     assertEquals(100, crSlider.value);
   });
 
+  test('update value instantly both off and on', () => {
+    crSlider.updateValueInstantly = false;
+    assertEquals(0, crSlider.value);
+    pointerDown(.5);
+    assertEquals(0, crSlider.value);
+    pointerUp();
+    assertEquals(50, crSlider.value);
+
+    // Once |updateValueInstantly| is turned on, |value| should start updating
+    // again during drag.
+    pointerDown(0);
+    assertEquals(50, crSlider.value);
+    crSlider.updateValueInstantly = true;
+    pointerMove(0);
+    assertEquals(0, crSlider.value);
+    crSlider.updateValueInstantly = false;
+    pointerMove(.4);
+    assertEquals(0, crSlider.value);
+    pointerUp();
+    assertEquals(40, crSlider.value);
+  });
+
   test('snaps to closest value', () => {
     crSlider.snaps = true;
     pointerDown(.501);
@@ -164,11 +187,13 @@
     assertEquals('8', crSlider.getAttribute('aria-valuemax'));
     assertEquals('4', crSlider.getAttribute('aria-valuetext'));
     assertEquals('4', crSlider.getAttribute('aria-valuenow'));
+    assertEquals('', crSlider.$.label.innerHTML.trim());
     assertEquals(2, crSlider.value);
     crSlider.value = 100;
     assertEquals(3, crSlider.value);
     assertEquals('8', crSlider.getAttribute('aria-valuetext'));
     assertEquals('8', crSlider.getAttribute('aria-valuenow'));
+    assertEquals('', crSlider.$.label.innerHTML.trim());
     crSlider.ticks = [
       {
         value: 10,
@@ -188,9 +213,11 @@
     assertEquals('1', crSlider.getAttribute('aria-valuemin'));
     assertEquals('3', crSlider.getAttribute('aria-valuemax'));
     assertEquals('Third', crSlider.getAttribute('aria-valuetext'));
+    assertEquals('Third', crSlider.$.label.innerHTML.trim());
     assertEquals('3', crSlider.getAttribute('aria-valuenow'));
     crSlider.value = 1;
     assertEquals('Second', crSlider.getAttribute('aria-valuetext'));
     assertEquals('20', crSlider.getAttribute('aria-valuenow'));
+    assertEquals('Second', crSlider.$.label.innerHTML.trim());
   });
 });
diff --git a/chrome/test/data/webui/extensions/runtime_host_permissions_test.js b/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
index 308fe44ee..a86b2a6a 100644
--- a/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
+++ b/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
@@ -27,7 +27,6 @@
     const permissions = {
       simplePermissions: ['permission 1', 'permission 2'],
       hostAccess: HostAccess.ON_CLICK,
-      runtimeHostPermissions: [],
     };
 
     element.set('permissions', permissions);
@@ -52,9 +51,13 @@
     // Setting the mode to on specific sites should display the runtime hosts
     // list.
     element.set('permissions.hostAccess', HostAccess.ON_SPECIFIC_SITES);
-    element.set(
-        'permissions.runtimeHostPermissions',
-        ['https://example.com', 'https://chromium.org']);
+    element.set('permissions.specificSiteControls', {
+      hasAllHosts: false,
+      hosts: [
+        {host: 'https://example.com', granted: true},
+        {host: 'https://chromium.org', granted: true}
+      ],
+    });
     Polymer.dom.flush();
     expectEquals(HostAccess.ON_SPECIFIC_SITES, selectHostAccess.value);
     expectTrue(testIsVisible('#hosts'));
@@ -66,7 +69,6 @@
     const permissions = {
       simplePermissions: ['permission 1', 'permission 2'],
       hostAccess: HostAccess.ON_CLICK,
-      runtimeHostPermissions: [],
     };
 
     element.set('permissions', permissions);
@@ -98,9 +100,7 @@
 
   test('on select sites cancel', function() {
     const permissions = {
-      simplePermissions: ['permission 1', 'permission 2'],
       hostAccess: HostAccess.ON_CLICK,
-      runtimeHostPermissions: [],
     };
 
     element.permissions = permissions;
@@ -133,7 +133,6 @@
     const permissions = {
       simplePermissions: ['permission 1', 'permission 2'],
       hostAccess: HostAccess.ON_CLICK,
-      runtimeHostPermissions: [],
     };
 
     element.set('permissions', permissions);
@@ -172,7 +171,13 @@
     const permissions = {
       simplePermissions: [],
       hostAccess: HostAccess.ON_SPECIFIC_SITES,
-      runtimeHostPermissions: ['http://www.example.com'],
+      specificSiteControls: {
+        hasAllHosts: false,
+        hosts: [
+          {host: 'https://www.example.com/*', granted: true},
+          {host: 'https://*.google.com', granted: false}
+        ],
+      },
     };
 
     element.set('permissions', permissions);
@@ -195,7 +200,13 @@
     const permissions = {
       simplePermissions: [],
       hostAccess: HostAccess.ON_SPECIFIC_SITES,
-      runtimeHostPermissions: ['https://example.com', 'https://chromium.org'],
+      specificSiteControls: {
+        hasAllHosts: false,
+        hosts: [
+          {host: 'https://example.com', granted: true},
+          {host: 'https://chromium.org', granted: true}
+        ],
+      },
     };
     element.set('permissions', permissions);
     Polymer.dom.flush();
@@ -213,7 +224,7 @@
     remove.click();
     return delegate.whenCalled('removeRuntimeHostPermission').then((args) => {
       expectEquals(ITEM_ID, args[0] /* id */);
-      expectEquals('https://example.com', args[1] /* site */);
+      expectEquals('https://chromium.org', args[1] /* site */);
       expectFalse(actionMenu.open);
     });
   });
@@ -222,7 +233,13 @@
     const permissions = {
       simplePermissions: [],
       hostAccess: HostAccess.ON_SPECIFIC_SITES,
-      runtimeHostPermissions: ['https://example.com', 'https://chromium.org'],
+      specificSiteControls: {
+        hasAllHosts: false,
+        hosts: [
+          {host: 'https://example.com', granted: true},
+          {host: 'https://chromium.org', granted: true}
+        ],
+      },
     };
     element.set('permissions', permissions);
     Polymer.dom.flush();
@@ -240,6 +257,6 @@
     assertTrue(!!dialog);
     expectTrue(dialog.$.dialog.open);
     expectFalse(dialog.updateHostAccess);
-    expectEquals('https://example.com', dialog.currentSite);
+    expectEquals('https://chromium.org', dialog.currentSite);
   });
 });
diff --git a/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js b/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
index 6a709ba..b31575ffc 100644
--- a/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
+++ b/chrome/test/data/webui/md_bookmarks/dnd_manager_test.js
@@ -65,15 +65,14 @@
     };
   }
 
-  function startInternalDrag(dragElement) {
-    MockInteractions.down(dragElement);
+  function simulateDragStart(dragElement) {
+    dispatchDragEvent('dragstart', dragElement);
     move(dragElement, MockInteractions.topLeftOfNode(dragElement));
   }
 
   function move(target, dest) {
-    MockInteractions.move(
-        target, {x: 0, y: 0}, dest || MockInteractions.middleOfNode(target),
-        -1);
+    dispatchDragEvent(
+        'dragover', target, dest || MockInteractions.middleOfNode(target));
   }
 
   function getDragIds() {
@@ -109,6 +108,10 @@
     store.replaceSingleton();
 
     chrome.bookmarks.move = function(id, details) {};
+    chrome.bookmarkManagerPrivate.startDrag = function(
+        idList, dragNodeIndex, isFromTouch) {
+      dndManager.dragInfo_.setNativeDragData(createDragData(idList));
+    };
 
     app = document.createElement('bookmarks-app');
     replaceBody(app);
@@ -137,7 +140,7 @@
     const dragElement = getListItem('13');
     let dragTarget = getListItem('12');
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     assertDeepEquals(['13'], getDragIds());
 
@@ -173,7 +176,7 @@
     const dragElement = getFolderNode('112');
     const dragTarget = getFolderNode('111');
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     assertEquals(
         DropPosition.ON | DropPosition.ABOVE,
@@ -186,7 +189,7 @@
   test('drag an item into a sidebar folder', function() {
     const dragElement = getListItem('13');
     let dragTarget = getFolderNode('2');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     // Items can only be dragged onto sidebar folders, not above or below.
     assertEquals(
@@ -206,7 +209,7 @@
     const dragTarget = getFolderNode('112');
 
     // Folders cannot be dragged into their descendants.
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertEquals(
         DropPosition.NONE, dndManager.calculateValidDropPositions_(dragTarget));
 
@@ -219,7 +222,7 @@
     const dragElement = getFolderNode('15');
     const dragTarget = getFolderNode('11');
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     // Drags below an open folder are not allowed.
     assertEquals(
@@ -230,13 +233,13 @@
 
     assertDragStyle(dragTarget, DRAG_STYLE.ON);
 
-    MockInteractions.up(dragElement);
+    dispatchDragEvent('dragend', dragElement);
     assertDragStyle(dragTarget, DRAG_STYLE.NONE);
 
     store.data.folderOpenState.set('11', false);
     store.notifyObservers();
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     // Drags below a closed folder are allowed.
     assertEquals(
@@ -248,7 +251,7 @@
     // Dragging multiple items.
     store.data.selection.items = new Set(['13', '15']);
     let dragElement = getListItem('13');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['13', '15'], getDragIds());
 
     // The dragged items should not be allowed to be dragged around any selected
@@ -262,17 +265,17 @@
     assertEquals(
         DropPosition.NONE,
         dndManager.calculateValidDropPositions_(getListItem('15')));
-    MockInteractions.up(dragElement);
+    dispatchDragEvent('dragend', dragElement);
 
     // Dragging an unselected item should only drag the unselected item.
     dragElement = getListItem('14');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['14'], getDragIds());
-    MockInteractions.up(dragElement);
+    dispatchDragEvent('dragend', dragElement);
 
     // Dragging a folder node should only drag the node.
     dragElement = getListItem('11');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['11'], getDragIds());
   });
 
@@ -297,13 +300,13 @@
     const dragTarget = getListItem('13');
 
     // Drag a folder onto the list.
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['112'], getDragIds());
 
     move(dragTarget, MockInteractions.topLeftOfNode(dragTarget));
     assertDragStyle(dragTarget, DRAG_STYLE.ABOVE);
 
-    MockInteractions.up(dragTarget);
+    dispatchDragEvent('dragend', dragTarget);
 
     // Folders should not be able to dragged onto themselves in the list.
     dndManager.handleChromeDragEnter_(createDragData(['11']));
@@ -368,11 +371,6 @@
   });
 
   test('simple native drop end to end', function() {
-    let draggedIds;
-    chrome.bookmarkManagerPrivate.startDrag = function(nodes, isTouch) {
-      draggedIds = nodes;
-    };
-
     let dropParentId;
     let dropIndex;
     chrome.bookmarkManagerPrivate.drop = function(parentId, index) {
@@ -383,14 +381,9 @@
     const dragElement = getListItem('13');
     const dragTarget = getListItem('12');
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['13'], getDragIds());
 
-    dispatchDragEvent('dragstart', dragElement);
-    assertEquals(null, dndManager.internalDragElement_);
-    assertDeepEquals(['13'], draggedIds);
-    dndManager.handleChromeDragEnter_(createDragData(draggedIds));
-
     dispatchDragEvent(
         'dragover', dragTarget, MockInteractions.topLeftOfNode(dragTarget));
     assertDragStyle(dragTarget, DRAG_STYLE.ABOVE);
@@ -403,34 +396,6 @@
     assertDragStyle(dragTarget, DRAG_STYLE.NONE);
   });
 
-
-  test('simple internal drop end to end', function() {
-    let moveId;
-    let moveParentId;
-    let moveIndex;
-    chrome.bookmarks.move = function(id, details) {
-      moveId = id;
-      moveParentId = details.parentId;
-      moveIndex = details.index;
-    };
-
-    const dragElement = getListItem('13');
-    const dragTarget = getListItem('12');
-
-    startInternalDrag(dragElement);
-    assertDeepEquals(['13'], getDragIds());
-
-    move(dragTarget, MockInteractions.topLeftOfNode(dragTarget));
-    assertDragStyle(dragTarget, DRAG_STYLE.ABOVE);
-
-    MockInteractions.up(dragTarget);
-    assertEquals('13', moveId);
-    assertEquals('1', moveParentId);
-    assertEquals(1, moveIndex);
-
-    assertDragStyle(dragTarget, DRAG_STYLE.NONE);
-  });
-
   test('auto expander', function() {
     const timerProxy = new bookmarks.TestTimerProxy();
     timerProxy.immediatelyResolveTimeouts = false;
@@ -445,7 +410,7 @@
     const dragElement = getFolderNode('14');
     let dragTarget = getFolderNode('15');
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     // Dragging onto folders without children doesn't update the auto expander.
     move(dragTarget);
@@ -499,20 +464,20 @@
     const dragTarget = list;
 
     // Dragging onto an empty list.
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     move(dragTarget);
     assertEquals(
         DropPosition.ON, dndManager.calculateValidDropPositions_(dragTarget));
     assertDragStyle(dragTarget, DRAG_STYLE.ON);
 
-    MockInteractions.up(dragTarget);
+    dispatchDragEvent('dragend', dragTarget);
 
     // Dragging onto a non-empty list.
     store.data.selectedFolder = '11';
     store.notifyObservers();
 
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     move(dragTarget);
     assertEquals(
@@ -529,15 +494,15 @@
     // Dragging an item not in the selection selects the dragged item and
     // deselects the previous selection.
     let dragElement = getListItem('14');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals(['14'], normalizeIterable(store.data.selection.items));
-    MockInteractions.up(dragElement);
+    dispatchDragEvent('dragend', dragElement);
 
     // Dragging a folder node deselects any selected items in the bookmark list.
     dragElement = getFolderNode('15');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertDeepEquals([], normalizeIterable(store.data.selection.items));
-    MockInteractions.up(dragElement);
+    dispatchDragEvent('dragend', dragElement);
   });
 
   test('cannot drag items when editing is disabled', function() {
@@ -545,7 +510,7 @@
     store.notifyObservers();
 
     const dragElement = getFolderNode('11');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertFalse(dndManager.dragInfo_.isDragValid());
   });
 
@@ -554,11 +519,11 @@
     store.notifyObservers();
 
     let dragElement = getFolderNode('1');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertFalse(dndManager.dragInfo_.isDragValid());
 
     dragElement = getFolderNode('2');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
     assertFalse(dndManager.dragInfo_.isDragValid());
   });
 
@@ -567,7 +532,7 @@
     store.notifyObservers();
 
     const dragElement = getListItem('12');
-    startInternalDrag(dragElement);
+    simulateDragStart(dragElement);
 
     // Can't drag onto the unmodifiable node.
     const dragTarget = getFolderNode('2');
@@ -575,32 +540,4 @@
     assertEquals(
         DropPosition.NONE, dndManager.calculateValidDropPositions_(dragTarget));
   });
-
-  test('ensure drag and drop chip shows', function() {
-    const dragElement = getListItem('13');
-    const dragTarget = getFolderNode('2');
-
-    startInternalDrag(dragElement);
-
-    move(dragTarget, {x: 50, y: 80});
-    assertTrue(!!dndManager.chip_);
-    const dndChip = dndManager.dndChip;
-    assertEquals('50px', dndChip.style.getPropertyValue('--mouse-x'));
-    assertEquals('80px', dndChip.style.getPropertyValue('--mouse-y'));
-    assertTrue(dndChip.showing_);
-
-    MockInteractions.up(dragElement);
-    assertFalse(dndChip.showing_);
-  });
-
-  test('drag starts after minimal move distance', function() {
-    const dragElement = getListItem('13');
-
-    MockInteractions.down(dragElement);
-    move(dragElement);
-    assertFalse(dndManager.dragInfo_.isDragValid());
-
-    move(dragElement, MockInteractions.topLeftOfNode(dragElement));
-    assertTrue(dndManager.dragInfo_.isDragValid());
-  });
 });
diff --git a/chrome/test/data/webui/md_history/history_list_test.js b/chrome/test/data/webui/md_history/history_list_test.js
index 698c8abb..fc5c12df 100644
--- a/chrome/test/data/webui/md_history/history_list_test.js
+++ b/chrome/test/data/webui/md_history/history_list_test.js
@@ -218,6 +218,7 @@
     app.historyResult(createHistoryInfo(), ADDITIONAL_RESULTS);
 
     return PolymerTest.flushTasks().then(function() {
+      Polymer.dom.flush();
       const items = polymerSelectAll(element, 'history-item');
       assertTrue(items[3].isCardStart);
       assertTrue(items[5].isCardEnd);
@@ -241,6 +242,7 @@
           return PolymerTest.flushTasks();
         })
         .then(function() {
+          Polymer.dom.flush();
           const items = polymerSelectAll(element, 'history-item');
 
           assertEquals(element.historyData_.length, 5);
diff --git a/chrome/test/data/webui/settings/device_page_tests.js b/chrome/test/data/webui/settings/device_page_tests.js
index 41c61eb..b7cd8d8 100644
--- a/chrome/test/data/webui/settings/device_page_tests.js
+++ b/chrome/test/data/webui/settings/device_page_tests.js
@@ -717,7 +717,7 @@
             width: 1920,
             height: 1080,
           },
-          availableDisplayZoomFactors: [1],
+          availableDisplayZoomFactors: [1, 1.25, 1.5, 2],
         };
         fakeSystemDisplay.addDisplayForTest(display);
       };
@@ -831,6 +831,25 @@
             expectTrue(displayPage.displays[0].isPrimary);
             expectTrue(displayPage.showMirror_(false, displayPage.displays));
             expectTrue(displayPage.isMirrored_(displayPage.displays));
+
+            // Ensure that the zoom value remains unchanged while draggging.
+            function pointerEvent(eventType, ratio) {
+              const crSlider = displayPage.$.displaySizeSlider.$.slider;
+              const rect = crSlider.$.barContainer.getBoundingClientRect();
+              crSlider.dispatchEvent(new PointerEvent(eventType, {
+                buttons: 1,
+                pointerId: 1,
+                clientX: rect.left + (ratio * rect.width),
+              }));
+            }
+
+            expectEquals(1, displayPage.selectedZoomPref_.value);
+            pointerEvent('pointerdown', .6);
+            expectEquals(1, displayPage.selectedZoomPref_.value);
+            pointerEvent('pointermove', .3);
+            expectEquals(1, displayPage.selectedZoomPref_.value);
+            pointerEvent('pointerup', 0);
+            expectEquals(1.25, displayPage.selectedZoomPref_.value);
           });
     });
 
diff --git a/chrome/test/data/webui/settings/multidevice_page_tests.js b/chrome/test/data/webui/settings/multidevice_page_tests.js
index 1e03e972..3d63530 100644
--- a/chrome/test/data/webui/settings/multidevice_page_tests.js
+++ b/chrome/test/data/webui/settings/multidevice_page_tests.js
@@ -138,15 +138,19 @@
   });
 
   setup(function() {
+    settings.navigateTo(settings.routes.MULTIDEVICE);
     browserProxy = new TestMultideviceBrowserProxy();
     settings.MultiDeviceBrowserProxyImpl.instance_ = browserProxy;
+    const whenInitialized = browserProxy.whenCalled('getPageContentData');
 
     PolymerTest.clearBody();
     multidevicePage = document.createElement('settings-multidevice-page');
     assertTrue(!!multidevicePage);
 
     document.body.appendChild(multidevicePage);
-    Polymer.dom.flush();
+    return whenInitialized.then(() => {
+      Polymer.dom.flush();
+    });
   });
 
   teardown(function() {
diff --git a/chrome/test/data/webui/settings/multidevice_subpage_tests.js b/chrome/test/data/webui/settings/multidevice_subpage_tests.js
index 23cee3f..7a254b41 100644
--- a/chrome/test/data/webui/settings/multidevice_subpage_tests.js
+++ b/chrome/test/data/webui/settings/multidevice_subpage_tests.js
@@ -148,9 +148,9 @@
         assertFalse(!!multideviceSubpage.$$('#messagesItem'));
       });
 
-  test('clicking EasyUnlock item routes to screen lock page', function() {
+  test('clicking SmartLock item routes to SmartLock subpage', function() {
     multideviceSubpage.$$('#smartLockItem').$.card.click();
-    assertEquals(settings.getCurrentRoute(), settings.routes.LOCK_SCREEN);
+    assertEquals(settings.getCurrentRoute(), settings.routes.SMART_LOCK);
   });
 
   test('AndroidMessages item shows button when not set up', function() {
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 04d375f..ae3d1ff7 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/path_service.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
@@ -39,13 +40,19 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/net_errors.h"
+#include "net/ssl/ssl_info.h"
 #include "ppapi/shared_impl/test_utils.h"
 #include "rlz/buildflags/buildflags.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/network_service_test.mojom.h"
+#include "services/network/public/mojom/tcp_socket.mojom.h"
+#include "services/network/public/mojom/tls_socket.mojom.h"
 #include "services/network/public/mojom/udp_socket.mojom.h"
+#include "services/network/test/test_network_context.h"
 #include "services/service_manager/public/cpp/connector.h"
 
 #if defined(OS_MACOSX)
@@ -326,12 +333,24 @@
 PPAPI_SOCKET_TEST(TCPSocket_Interface_1_0);
 PPAPI_SOCKET_TEST(TCPSocket_UnexpectedCalls);
 
-TEST_PPAPI_OUT_OF_PROCESS_VIA_HTTP(TCPServerSocketPrivate)
-TEST_PPAPI_NACL(TCPServerSocketPrivate)
+TEST_PPAPI_OUT_OF_PROCESS_VIA_HTTP(TCPServerSocketPrivate_Listen)
+TEST_PPAPI_OUT_OF_PROCESS_VIA_HTTP(TCPServerSocketPrivate_Backlog)
+TEST_PPAPI_NACL(TCPServerSocketPrivate_Listen)
+TEST_PPAPI_NACL(TCPServerSocketPrivate_Backlog)
 
-TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_Basic)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_ReadWrite)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_ReadWriteSSL)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_ConnectAddress)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_SetOption)
+TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivate_LargeRead)
 
-TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_Basic)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_ReadWrite)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_ReadWriteSSL)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_ConnectAddress)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_SetOption)
+TEST_PPAPI_NACL_WITH_SSL_SERVER(TCPSocketPrivate_LargeRead)
 
 TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivateTrusted)
 
@@ -350,6 +369,569 @@
   RunTestViaHTTP(STRIP_PREFIXES(TCPSocketPrivateCrash_Resolve));
 }
 
+namespace {
+
+// Different types of TCPSocket failures to simulate. *Error means to keep the
+// pipe alive while invoking the callback with an error code, and *PipeError
+// means to close the pipe that the method was invoked on (not the pipe passed
+// to the callback, if there was one) without invoking the callback.
+//
+// Note that closing a pipe after a successful operation isn't too interesting,
+// as it looks just like closing the pipe on the next operation, since the
+// message filter classes don't generally watch for pipe closure.
+enum class TCPFailureType {
+  // Makes creation calls for all socket types (server, connected, and bound)
+  // close all Mojo pipes without doing anything.
+  kClosePipeOnCreate,
+
+  kBindClosePipe,
+  kBindError,
+
+  // These apply to both CreateTCPServerSocket and TCPBoundSocket::Listen().
+  kCreateTCPServerSocketClosePipe,
+  kCreateTCPServerSocketError,
+
+  kAcceptDropPipe,
+  kAcceptError,
+
+  kConnectClosePipe,
+  kConnectError,
+  kWriteClosePipe,
+  kWriteError,
+  kReadClosePipe,
+  kReadError,
+
+  // These apply to all TCPConnectedSocket configuration methods.
+  kSetOptionsClosePipe,
+  kSetOptionsError,
+
+  kUpgradeToTLSClosePipe,
+  kUpgradeToTLSError,
+  kSSLWriteClosePipe,
+  kSSLWriteError,
+  kSSLReadClosePipe,
+  kSSLReadError,
+};
+
+net::IPEndPoint LocalAddress() {
+  return net::IPEndPoint(net::IPAddress::IPv4Localhost(), 1234);
+}
+
+net::IPEndPoint RemoteAddress() {
+  return net::IPEndPoint(net::IPAddress::IPv4Localhost(), 12345);
+}
+
+// Use the same class for TCPConnectedSocket and, if it's upgraded,
+// TLSClientSocket, since the TLSClientSocket interface doesn't do anything.
+class MockTCPConnectedSocket : public network::mojom::TCPConnectedSocket,
+                               public network::mojom::TLSClientSocket {
+ public:
+  MockTCPConnectedSocket(
+      TCPFailureType tcp_failure_type,
+      network::mojom::TCPConnectedSocketRequest request,
+      network::mojom::SocketObserverPtr observer,
+      network::mojom::NetworkContext::CreateTCPConnectedSocketCallback callback)
+      : tcp_failure_type_(tcp_failure_type),
+        observer_(std::move(observer)),
+        binding_(this, std::move(request)),
+        tls_client_socket_binding_(this) {
+    if (tcp_failure_type_ == TCPFailureType::kConnectError) {
+      std::move(callback).Run(
+          net::ERR_FAILED, base::nullopt /* local_addr */,
+          base::nullopt /* peer_addr */,
+          mojo::ScopedDataPipeConsumerHandle() /* receive_stream */,
+          mojo::ScopedDataPipeProducerHandle() /* send_stream */);
+      return;
+    }
+
+    mojo::DataPipe send_pipe;
+    mojo::DataPipe receive_pipe;
+
+    receive_pipe_handle_ = std::move(receive_pipe.producer_handle);
+    send_pipe_handle_ = std::move(send_pipe.consumer_handle);
+
+    std::move(callback).Run(net::OK, LocalAddress(), RemoteAddress(),
+                            std::move(receive_pipe.consumer_handle),
+                            std::move(send_pipe.producer_handle));
+    ClosePipeIfNeeded();
+  }
+
+  MockTCPConnectedSocket(
+      TCPFailureType tcp_failure_type,
+      network::mojom::SocketObserverPtr observer,
+      network::mojom::TCPServerSocket::AcceptCallback callback)
+      : tcp_failure_type_(tcp_failure_type),
+        observer_(std::move(observer)),
+        binding_(this),
+        tls_client_socket_binding_(this) {
+    if (tcp_failure_type_ == TCPFailureType::kAcceptError) {
+      std::move(callback).Run(
+          net::ERR_FAILED, base::nullopt /* remote_addr */,
+          nullptr /* connected_socket */,
+          mojo::ScopedDataPipeConsumerHandle() /* receive_stream */,
+          mojo::ScopedDataPipeProducerHandle() /* send_stream */);
+      return;
+    }
+
+    mojo::DataPipe send_pipe;
+    mojo::DataPipe receive_pipe;
+
+    receive_pipe_handle_ = std::move(receive_pipe.producer_handle);
+    send_pipe_handle_ = std::move(send_pipe.consumer_handle);
+
+    network::mojom::TCPConnectedSocketPtr connected_socket;
+    binding_.Bind(mojo::MakeRequest(&connected_socket));
+    std::move(callback).Run(net::OK, RemoteAddress(),
+                            std::move(connected_socket),
+                            std::move(receive_pipe.consumer_handle),
+                            std::move(send_pipe.producer_handle));
+    ClosePipeIfNeeded();
+  }
+
+  ~MockTCPConnectedSocket() override {}
+
+  // mojom::TCPConnectedSocket implementation:
+
+  void UpgradeToTLS(
+      const net::HostPortPair& host_port_pair,
+      network::mojom::TLSClientSocketOptionsPtr socket_options,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+      network::mojom::TLSClientSocketRequest request,
+      network::mojom::SocketObserverPtr observer,
+      network::mojom::TCPConnectedSocket::UpgradeToTLSCallback callback)
+      override {
+    // Succeed or fail, keep these pipes open (Their state shouldn't matter when
+    // checking for failures).
+    observer_ = std::move(observer);
+    tls_client_socket_binding_.Bind(std::move(request));
+
+    if (tcp_failure_type_ == TCPFailureType::kUpgradeToTLSClosePipe) {
+      binding_.Close();
+      return;
+    }
+    if (tcp_failure_type_ == TCPFailureType::kUpgradeToTLSError) {
+      std::move(callback).Run(
+          net::ERR_FAILED, mojo::ScopedDataPipeConsumerHandle(),
+          mojo::ScopedDataPipeProducerHandle(), base::nullopt /* ssl_info */);
+      return;
+    }
+
+    // Invoke callback immediately, without waiting for pipes to close - tests
+    // that use a real NetworkContext already make sure that the class correctly
+    // closes the sockets when upgrading.
+
+    mojo::DataPipe send_pipe;
+    mojo::DataPipe receive_pipe;
+    receive_pipe_handle_ = std::move(receive_pipe.producer_handle);
+    send_pipe_handle_ = std::move(send_pipe.consumer_handle);
+
+    std::move(callback).Run(net::OK, std::move(receive_pipe.consumer_handle),
+                            std::move(send_pipe.producer_handle),
+                            net::SSLInfo());
+
+    if (tcp_failure_type_ == TCPFailureType::kSSLWriteClosePipe) {
+      observer_.reset();
+      send_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kSSLWriteError) {
+      observer_->OnWriteError(net::ERR_FAILED);
+      send_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kSSLReadClosePipe) {
+      observer_.reset();
+      receive_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kSSLReadError) {
+      observer_->OnReadError(net::ERR_FAILED);
+      receive_pipe_handle_.reset();
+    }
+  }
+
+  void SetSendBufferSize(int send_buffer_size,
+                         SetSendBufferSizeCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kSetOptionsClosePipe) {
+      binding_.Close();
+      return;
+    }
+    DCHECK_EQ(tcp_failure_type_, TCPFailureType::kSetOptionsError);
+    std::move(callback).Run(net::ERR_FAILED);
+  }
+
+  void SetReceiveBufferSize(int send_buffer_size,
+                            SetSendBufferSizeCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kSetOptionsClosePipe) {
+      binding_.Close();
+      return;
+    }
+    DCHECK_EQ(tcp_failure_type_, TCPFailureType::kSetOptionsError);
+    std::move(callback).Run(net::ERR_FAILED);
+  }
+
+  void SetNoDelay(bool no_delay, SetNoDelayCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kSetOptionsClosePipe) {
+      binding_.Close();
+      return;
+    }
+    DCHECK_EQ(tcp_failure_type_, TCPFailureType::kSetOptionsError);
+    std::move(callback).Run(false);
+  }
+
+  void SetKeepAlive(bool enable,
+                    int32_t delay_secs,
+                    SetKeepAliveCallback callback) override {
+    NOTREACHED();
+  }
+
+ private:
+  void ClosePipeIfNeeded() {
+    if (tcp_failure_type_ == TCPFailureType::kWriteClosePipe) {
+      observer_.reset();
+      send_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kWriteError) {
+      observer_->OnWriteError(net::ERR_FAILED);
+      send_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kReadClosePipe) {
+      observer_.reset();
+      receive_pipe_handle_.reset();
+    } else if (tcp_failure_type_ == TCPFailureType::kReadError) {
+      observer_->OnReadError(net::ERR_FAILED);
+      receive_pipe_handle_.reset();
+    }
+  }
+
+  const TCPFailureType tcp_failure_type_;
+
+  network::mojom::SocketObserverPtr observer_;
+
+  mojo::ScopedDataPipeProducerHandle receive_pipe_handle_;
+  mojo::ScopedDataPipeConsumerHandle send_pipe_handle_;
+
+  mojo::Binding<network::mojom::TCPConnectedSocket> binding_;
+  mojo::Binding<network::mojom::TLSClientSocket> tls_client_socket_binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockTCPConnectedSocket);
+};
+
+class MockTCPServerSocket : public network::mojom::TCPServerSocket {
+ public:
+  // CreateTCPServerSocket constructor.
+  MockTCPServerSocket(
+      TCPFailureType tcp_failure_type,
+      network::mojom::TCPServerSocketRequest request,
+      network::mojom::NetworkContext::CreateTCPServerSocketCallback callback)
+      : tcp_failure_type_(tcp_failure_type), binding_(this) {
+    binding_.Bind(std::move(request));
+    if (tcp_failure_type_ == TCPFailureType::kCreateTCPServerSocketError) {
+      std::move(callback).Run(net::ERR_FAILED, base::nullopt /* local_addr */);
+      return;
+    }
+    std::move(callback).Run(net::OK, LocalAddress());
+  }
+
+  // TCPBoundSocket::Listen constructor.
+  MockTCPServerSocket(TCPFailureType tcp_failure_type,
+                      network::mojom::TCPServerSocketRequest request,
+                      network::mojom::TCPBoundSocket::ListenCallback callback)
+      : tcp_failure_type_(tcp_failure_type), binding_(this) {
+    binding_.Bind(std::move(request));
+    if (tcp_failure_type_ == TCPFailureType::kCreateTCPServerSocketError) {
+      std::move(callback).Run(net::ERR_FAILED);
+      return;
+    }
+    std::move(callback).Run(net::OK);
+  }
+
+  ~MockTCPServerSocket() override {}
+
+  // TCPServerSocket implementation:
+  void Accept(network::mojom::SocketObserverPtr observer,
+              AcceptCallback callback) override {
+    // This falls through just to keep the observer alive.
+    if (tcp_failure_type_ == TCPFailureType::kAcceptDropPipe)
+      binding_.Close();
+    connected_socket_ = std::make_unique<MockTCPConnectedSocket>(
+        tcp_failure_type_, std::move(observer), std::move(callback));
+  }
+
+ private:
+  const TCPFailureType tcp_failure_type_;
+
+  std::unique_ptr<MockTCPConnectedSocket> connected_socket_;
+
+  mojo::Binding<network::mojom::TCPServerSocket> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockTCPServerSocket);
+};
+
+class MockTCPBoundSocket : public network::mojom::TCPBoundSocket {
+ public:
+  MockTCPBoundSocket(
+      TCPFailureType tcp_failure_type,
+      network::mojom::TCPBoundSocketRequest request,
+      network::mojom::NetworkContext::CreateTCPBoundSocketCallback callback)
+      : tcp_failure_type_(tcp_failure_type), binding_(this) {
+    binding_.Bind(std::move(request));
+    if (tcp_failure_type_ == TCPFailureType::kBindError) {
+      std::move(callback).Run(net::ERR_FAILED, base::nullopt /* local_addr */);
+      return;
+    }
+    std::move(callback).Run(net::OK, LocalAddress());
+  }
+
+  ~MockTCPBoundSocket() override {}
+
+  // mojom::TCPBoundSocket implementation:
+  void Listen(uint32_t backlog,
+              network::mojom::TCPServerSocketRequest request,
+              ListenCallback callback) override {
+    // If closing the pipe, create ServerSocket anyways, to keep |request|
+    // alive. The callback invocation will have no effect, since it uses the
+    // TCPBoundSocket's pipe, which was just closed.
+    if (tcp_failure_type_ == TCPFailureType::kCreateTCPServerSocketClosePipe)
+      binding_.Close();
+    server_socket_ = std::make_unique<MockTCPServerSocket>(
+        tcp_failure_type_, std::move(request), std::move(callback));
+  }
+
+  void Connect(
+      const net::AddressList& remote_addr,
+      network::mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options,
+      network::mojom::TCPConnectedSocketRequest request,
+      network::mojom::SocketObserverPtr observer,
+      ConnectCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kConnectClosePipe)
+      binding_.Close();
+    connected_socket_ = std::make_unique<MockTCPConnectedSocket>(
+        tcp_failure_type_, std::move(request), std::move(observer),
+        std::move(callback));
+  }
+
+ private:
+  const TCPFailureType tcp_failure_type_;
+
+  // Needs to be destroyed after |binding_|, as it may be holding onto a
+  // callback bound to the Binding.
+  std::unique_ptr<MockTCPServerSocket> server_socket_;
+  std::unique_ptr<MockTCPConnectedSocket> connected_socket_;
+
+  mojo::Binding<network::mojom::TCPBoundSocket> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockTCPBoundSocket);
+};
+
+class MockNetworkContext : public network::TestNetworkContext {
+ public:
+  explicit MockNetworkContext(TCPFailureType tcp_failure_type,
+                              network::mojom::NetworkContextRequest request)
+      : tcp_failure_type_(tcp_failure_type),
+        binding_(this, std::move(request)) {}
+
+  ~MockNetworkContext() override {}
+
+  // network::mojom::NetworkContext implementation:
+
+  void CreateTCPServerSocket(
+      const net::IPEndPoint& local_addr,
+      uint32_t backlog,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+      network::mojom::TCPServerSocketRequest request,
+      CreateTCPServerSocketCallback callback) override {
+    // If closing the pipe, create ServerSocket anyways, to keep |request|
+    // alive. The callback invocation will have no effect, since it uses the
+    // TCPBoundSocket's pipe, which was just closed.
+    if (tcp_failure_type_ == TCPFailureType::kCreateTCPServerSocketClosePipe)
+      binding_.Close();
+    server_socket_ = std::make_unique<MockTCPServerSocket>(
+        tcp_failure_type_, std::move(request), std::move(callback));
+  }
+
+  void CreateTCPConnectedSocket(
+      const base::Optional<net::IPEndPoint>& local_addr,
+      const net::AddressList& remote_addr_list,
+      network::mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+      network::mojom::TCPConnectedSocketRequest socket,
+      network::mojom::SocketObserverPtr observer,
+      CreateTCPConnectedSocketCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kConnectClosePipe)
+      binding_.Close();
+    connected_socket_ = std::make_unique<MockTCPConnectedSocket>(
+        tcp_failure_type_, std::move(socket), std::move(observer),
+        std::move(callback));
+  }
+
+  void CreateTCPBoundSocket(
+      const net::IPEndPoint& local_addr,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+      network::mojom::TCPBoundSocketRequest request,
+      CreateTCPBoundSocketCallback callback) override {
+    if (tcp_failure_type_ == TCPFailureType::kBindClosePipe)
+      binding_.Close();
+    // These tests only create at most one object of a given type at a time.
+    bound_socket_ = std::make_unique<MockTCPBoundSocket>(
+        tcp_failure_type_, std::move(request), std::move(callback));
+  }
+
+  void ResolveHost(
+      const net::HostPortPair& host,
+      network::mojom::ResolveHostParametersPtr optional_parameters,
+      network::mojom::ResolveHostClientPtr response_client) override {
+    response_client->OnComplete(net::OK, net::AddressList(LocalAddress()));
+  }
+
+ private:
+  TCPFailureType tcp_failure_type_;
+
+  std::unique_ptr<MockTCPServerSocket> server_socket_;
+  std::unique_ptr<MockTCPBoundSocket> bound_socket_;
+  std::unique_ptr<MockTCPConnectedSocket> connected_socket_;
+
+  mojo::Binding<network::mojom::NetworkContext> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockNetworkContext);
+};
+
+// Runs a TCP test using a MockNetworkContext, through a Mojo pipe. Using a Mojo
+// pipe makes sure that everything happens asynchronously through a pipe.
+#define RUN_TCP_FAILURE_TEST(test_name, failure_type)                         \
+  do {                                                                        \
+    network::mojom::NetworkContextPtr network_context_proxy;                  \
+    MockNetworkContext network_context(                                       \
+        failure_type, mojo::MakeRequest(&network_context_proxy));             \
+    ppapi::SetPepperTCPNetworkContextForTesting(network_context_proxy.get()); \
+    RunTestViaHTTP(LIST_TEST(test_name));                                     \
+    ppapi::SetPepperTCPNetworkContextForTesting(nullptr);                     \
+  } while (false)
+
+}  // namespace
+
+// Macro for tests that use |WrappedUDPSocket| to simulate errors. |test_name|
+// and |_test| are separate values because there are often multiple ways to get
+// the same error pattern (Dropped mojo pipe and failed call, generally).
+#define TCP_SOCKET_FAILURE_TEST(test_name, _test, failure_type)              \
+  IN_PROC_BROWSER_TEST_F(OutOfProcessPPAPITest, test_name) {                 \
+    RUN_TCP_FAILURE_TEST(_test, failure_type);                               \
+  }                                                                          \
+  IN_PROC_BROWSER_TEST_F(PPAPINaClNewlibTest, MAYBE_PPAPI_NACL(test_name)) { \
+    RUN_TCP_FAILURE_TEST(_test, failure_type);                               \
+  }                                                                          \
+  IN_PROC_BROWSER_TEST_F(PPAPINaClGLibcTest, MAYBE_GLIBC(test_name)) {       \
+    RUN_TCP_FAILURE_TEST(_test, failure_type);                               \
+  }                                                                          \
+  IN_PROC_BROWSER_TEST_F(PPAPINaClPNaClTest, MAYBE_PPAPI_PNACL(test_name)) { \
+    RUN_TCP_FAILURE_TEST(_test, failure_type);                               \
+  }                                                                          \
+  IN_PROC_BROWSER_TEST_F(PPAPINaClPNaClNonSfiTest,                           \
+                         MAYBE_PNACL_NONSFI(test_name)) {                    \
+    RUN_TCP_FAILURE_TEST(_test, failure_type);                               \
+  }
+
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ConnectClosePipe,
+                        TCPSocket_ConnectFails,
+                        TCPFailureType::kConnectClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ConnectError,
+                        TCPSocket_ConnectFails,
+                        TCPFailureType::kConnectError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_WriteClosePipe,
+                        TCPSocket_WriteFails,
+                        TCPFailureType::kWriteClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_WriteError,
+                        TCPSocket_WriteFails,
+                        TCPFailureType::kWriteError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ReadClosePipe,
+                        TCPSocket_ReadFails,
+                        TCPFailureType::kReadClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ReadError,
+                        TCPSocket_ReadFails,
+                        TCPFailureType::kReadError);
+
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetSendBufferSizeClosePipe,
+                        TCPSocket_SetSendBufferSizeFails,
+                        TCPFailureType::kSetOptionsClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetSendBufferSizeError,
+                        TCPSocket_SetSendBufferSizeFails,
+                        TCPFailureType::kSetOptionsError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetReceiveBufferSizeClosePipe,
+                        TCPSocket_SetReceiveBufferSizeFails,
+                        TCPFailureType::kSetOptionsClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetReceiveBufferSizeError,
+                        TCPSocket_SetReceiveBufferSizeFails,
+                        TCPFailureType::kSetOptionsError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetNoDelayClosePipe,
+                        TCPSocket_SetNoDelayFails,
+                        TCPFailureType::kSetOptionsClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_SetNoDelayError,
+                        TCPSocket_SetNoDelayFails,
+                        TCPFailureType::kSetOptionsError);
+
+// Can't use TCPSocket_BindFailsConnectSucceeds for this one, because
+// BindClosePipe has to close the NetworkContext pipe.
+TCP_SOCKET_FAILURE_TEST(TCPSocket_BindClosePipe,
+                        TCPSocket_BindFails,
+                        TCPFailureType::kBindClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_BindError,
+                        TCPSocket_BindFailsConnectSucceeds,
+                        TCPFailureType::kBindError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ListenClosePipe,
+                        TCPSocket_ListenFails,
+                        TCPFailureType::kCreateTCPServerSocketClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_ListenError,
+                        TCPSocket_ListenFails,
+                        TCPFailureType::kCreateTCPServerSocketError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptClosePipe,
+                        TCPSocket_AcceptFails,
+                        TCPFailureType::kAcceptDropPipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptError,
+                        TCPSocket_AcceptFails,
+                        TCPFailureType::kAcceptError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptedSocketWriteClosePipe,
+                        TCPSocket_AcceptedSocketWriteFails,
+                        TCPFailureType::kWriteClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptedSocketWriteError,
+                        TCPSocket_AcceptedSocketWriteFails,
+                        TCPFailureType::kWriteError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptedSocketReadClosePipe,
+                        TCPSocket_AcceptedSocketReadFails,
+                        TCPFailureType::kReadClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_AcceptedSocketReadError,
+                        TCPSocket_AcceptedSocketReadFails,
+                        TCPFailureType::kReadError);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_BindConnectClosePipe,
+                        TCPSocket_BindConnectFails,
+                        TCPFailureType::kConnectClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocket_BindConnectError,
+                        TCPSocket_BindConnectFails,
+                        TCPFailureType::kConnectError);
+
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLHandshakeClosePipe,
+                        TCPSocketPrivate_SSLHandshakeFails,
+                        TCPFailureType::kUpgradeToTLSClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLHandshakeError,
+                        TCPSocketPrivate_SSLHandshakeFails,
+                        TCPFailureType::kUpgradeToTLSError);
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLWriteClosePipe,
+                        TCPSocketPrivate_SSLWriteFails,
+                        TCPFailureType::kSSLWriteClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLWriteError,
+                        TCPSocketPrivate_SSLWriteFails,
+                        TCPFailureType::kSSLWriteError);
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLReadClosePipe,
+                        TCPSocketPrivate_SSLReadFails,
+                        TCPFailureType::kSSLReadClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPSocketPrivate_SSLReadError,
+                        TCPSocketPrivate_SSLReadFails,
+                        TCPFailureType::kSSLReadError);
+
+TCP_SOCKET_FAILURE_TEST(TCPServerSocketPrivate_ListenClosePipe,
+                        TCPServerSocketPrivate_ListenFails,
+                        TCPFailureType::kCreateTCPServerSocketClosePipe);
+TCP_SOCKET_FAILURE_TEST(TCPServerSocketPrivate_ListenError,
+                        TCPServerSocketPrivate_ListenFails,
+                        TCPFailureType::kCreateTCPServerSocketError);
+TCP_SOCKET_FAILURE_TEST(TCPServerSocketPrivate_AcceptClosePipe,
+                        TCPServerSocketPrivate_AcceptFails,
+                        TCPFailureType::kAcceptDropPipe);
+TCP_SOCKET_FAILURE_TEST(TCPServerSocketPrivate_AcceptError,
+                        TCPServerSocketPrivate_AcceptFails,
+                        TCPFailureType::kAcceptError);
+
 // UDPSocket tests.
 
 // Split tests into multiple tests, making it easier to isolate which tests are
diff --git a/chromecast/common/extensions_api/cast_api_permissions.cc b/chromecast/common/extensions_api/cast_api_permissions.cc
index a1049a3..7836419a 100644
--- a/chromecast/common/extensions_api/cast_api_permissions.cc
+++ b/chromecast/common/extensions_api/cast_api_permissions.cc
@@ -12,7 +12,6 @@
 #include "base/memory/ptr_util.h"
 #include "extensions/common/permissions/api_permission.h"
 #include "extensions/common/permissions/api_permission_set.h"
-#include "extensions/common/permissions/media_galleries_permission.h"
 #include "extensions/common/permissions/permissions_info.h"
 #include "extensions/common/permissions/settings_override_permission.h"
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 05dcf0ff..714b855 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -735,18 +735,12 @@
 
 std::string AssistantManagerServiceImpl::BuildUserAgent(
     const std::string& arc_version) const {
-  int32_t os_major_version = 0;
-  int32_t os_minor_version = 0;
-  int32_t os_bugfix_version = 0;
-  base::SysInfo::OperatingSystemVersionNumbers(
-      &os_major_version, &os_minor_version, &os_bugfix_version);
-
   std::string user_agent;
   base::StringAppendF(&user_agent,
-                      "Mozilla/5.0 (X11; CrOS %s %d.%d.%d; %s) "
+                      "Mozilla/5.0 (X11; CrOS %s %s; %s) "
                       "AppleWebKit/%d.%d (KHTML, like Gecko)",
                       base::SysInfo::OperatingSystemArchitecture().c_str(),
-                      os_major_version, os_minor_version, os_bugfix_version,
+                      base::SysInfo::OperatingSystemVersion().c_str(),
                       base::SysInfo::GetLsbReleaseBoard().c_str(),
                       WEBKIT_VERSION_MAJOR, WEBKIT_VERSION_MINOR);
 
diff --git a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.cc b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.cc
index 18e88d3c..5aeb0f7 100644
--- a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.cc
+++ b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.cc
@@ -13,9 +13,6 @@
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/services/multidevice_setup/host_status_provider_impl.h"
 #include "chromeos/services/multidevice_setup/setup_flow_completion_recorder.h"
-#include "components/cryptauth/proto/cryptauth_api.pb.h"
-#include "components/cryptauth/remote_device_ref.h"
-#include "components/cryptauth/software_feature_state.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -76,7 +73,7 @@
   registry->RegisterInt64Pref(kExistingUserChromebookAddedPrefName,
                               kTimestampNotSet);
   registry->RegisterStringPref(
-      kHostDeviceIdFromMostRecentHostStatusUpdatePrefName, kNoHost);
+      kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName, kNoHost);
 }
 
 AccountStatusChangeDelegateNotifierImpl::
@@ -103,9 +100,12 @@
     kExistingUserChromebookAddedPrefName[] =
         "multidevice_setup.existing_user_chromebook_added";
 
+// Note that, despite the pref string name, this pref only records the IDs of
+// verified hosts. In particular, if a host has been set but is waiting for
+// verification, it will not recorded.
 // static
 const char AccountStatusChangeDelegateNotifierImpl::
-    kHostDeviceIdFromMostRecentHostStatusUpdatePrefName[] =
+    kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName[] =
         "multidevice_setup.host_device_id_from_most_recent_sync";
 
 AccountStatusChangeDelegateNotifierImpl::
@@ -118,7 +118,7 @@
       pref_service_(pref_service),
       setup_flow_completion_recorder_(setup_flow_completion_recorder),
       clock_(clock) {
-  host_device_id_from_most_recent_update_ =
+  verified_host_device_id_from_most_recent_update_ =
       LoadHostDeviceIdFromEndOfPreviousSession();
   host_status_provider_->AddObserver(this);
 }
@@ -137,27 +137,30 @@
     return;
   }
 
-  // Track and update host info.
-  base::Optional<std::string> host_device_id_before_update =
-      host_device_id_from_most_recent_update_;
+  // Track and update verified host info.
+  base::Optional<std::string> verified_host_device_id_before_update =
+      verified_host_device_id_from_most_recent_update_;
 
-  // Check if a host has been set.
-  if (host_status_with_device.host_device()) {
-    host_device_id_from_most_recent_update_ =
+  // Check if a host has been verified.
+  if (host_status_with_device.host_status() ==
+      mojom::HostStatus::kHostVerified) {
+    verified_host_device_id_from_most_recent_update_ =
         host_status_with_device.host_device()->GetDeviceId();
     pref_service_->SetString(
-        kHostDeviceIdFromMostRecentHostStatusUpdatePrefName,
-        *host_device_id_from_most_recent_update_);
+        kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName,
+        *verified_host_device_id_from_most_recent_update_);
   } else {
     // No host set.
-    host_device_id_from_most_recent_update_.reset();
+    verified_host_device_id_from_most_recent_update_.reset();
+    pref_service_->SetString(
+        kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName, kNoHost);
   }
 
   CheckForNewUserPotentialHostExistsEvent(host_status_with_device);
   CheckForExistingUserHostSwitchedEvent(host_status_with_device,
-                                        host_device_id_before_update);
-  CheckForExistingUserChromebookAddedEvent(host_status_with_device,
-                                           host_device_id_before_update);
+                                        verified_host_device_id_before_update);
+  CheckForExistingUserChromebookAddedEvent(
+      host_status_with_device, verified_host_device_id_before_update);
 }
 
 void AccountStatusChangeDelegateNotifierImpl::
@@ -165,7 +168,7 @@
         const HostStatusProvider::HostStatusWithDevice&
             host_status_with_device) {
   // We only check for new user events if there is no enabled host.
-  if (host_device_id_from_most_recent_update_)
+  if (verified_host_device_id_from_most_recent_update_)
     return;
 
   // If the observer has been notified of this event before, the user is not
@@ -190,14 +193,17 @@
 void AccountStatusChangeDelegateNotifierImpl::
     CheckForExistingUserHostSwitchedEvent(
         const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
-        const base::Optional<std::string>& host_device_id_before_update) {
-  // The host switched event requires both a pre-update and a post-update host.
-  if (!host_device_id_from_most_recent_update_ ||
-      !host_device_id_before_update) {
+        const base::Optional<std::string>&
+            verified_host_device_id_before_update) {
+  // The host switched event requires both a pre-update and a post-update
+  // verified host.
+  if (!verified_host_device_id_from_most_recent_update_ ||
+      !verified_host_device_id_before_update) {
     return;
   }
   // If the host stayed the same, there was no switch.
-  if (*host_device_id_from_most_recent_update_ == *host_device_id_before_update)
+  if (*verified_host_device_id_from_most_recent_update_ ==
+      *verified_host_device_id_before_update)
     return;
 
   delegate()->OnConnectedHostSwitchedForExistingUser(
@@ -209,11 +215,13 @@
 void AccountStatusChangeDelegateNotifierImpl::
     CheckForExistingUserChromebookAddedEvent(
         const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
-        const base::Optional<std::string>& host_device_id_before_update) {
-  // The Chromebook added event requires that a set host was found by the
-  // update, i.e. there was no host before the host status update but afterward
-  // there is a set host.
-  if (!host_device_id_from_most_recent_update_ || host_device_id_before_update)
+        const base::Optional<std::string>&
+            verified_host_device_id_before_update) {
+  // The Chromebook added event requires that a verified host was found by the
+  // update, i.e. there was no verified host before the host status update but
+  // afterward there was a verified host.
+  if (!verified_host_device_id_from_most_recent_update_ ||
+      verified_host_device_id_before_update)
     return;
 
   delegate()->OnNewChromebookAddedForExistingUser(
@@ -224,11 +232,12 @@
 
 base::Optional<std::string> AccountStatusChangeDelegateNotifierImpl::
     LoadHostDeviceIdFromEndOfPreviousSession() {
-  std::string host_device_id_from_most_recent_update = pref_service_->GetString(
-      kHostDeviceIdFromMostRecentHostStatusUpdatePrefName);
-  if (host_device_id_from_most_recent_update.empty())
+  std::string verified_host_device_id_from_most_recent_update =
+      pref_service_->GetString(
+          kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName);
+  if (verified_host_device_id_from_most_recent_update.empty())
     return base::nullopt;
-  return host_device_id_from_most_recent_update;
+  return verified_host_device_id_from_most_recent_update;
 }
 
 }  // namespace multidevice_setup
diff --git a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h
index 66950f8..de12cfb 100644
--- a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h
+++ b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h
@@ -62,7 +62,8 @@
   static const char kExistingUserHostSwitchedPrefName[];
   static const char kExistingUserChromebookAddedPrefName[];
 
-  static const char kHostDeviceIdFromMostRecentHostStatusUpdatePrefName[];
+  static const char
+      kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName[];
 
   AccountStatusChangeDelegateNotifierImpl(
       HostStatusProvider* host_status_provider,
@@ -84,17 +85,17 @@
       const HostStatusProvider::HostStatusWithDevice& host_status_with_device);
   void CheckForExistingUserHostSwitchedEvent(
       const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
-      const base::Optional<std::string>& host_device_id_before_update);
+      const base::Optional<std::string>& verified_host_device_id_before_update);
   void CheckForExistingUserChromebookAddedEvent(
       const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
-      const base::Optional<std::string>& host_device_id_before_update);
+      const base::Optional<std::string>& verified_host_device_id_before_update);
 
   // Loads data from previous session using PrefService.
   base::Optional<std::string> LoadHostDeviceIdFromEndOfPreviousSession();
 
   // Set to base::nullopt if there was no enabled host in the most recent
   // host status update.
-  base::Optional<std::string> host_device_id_from_most_recent_update_;
+  base::Optional<std::string> verified_host_device_id_from_most_recent_update_;
 
   mojom::AccountStatusChangeDelegatePtr delegate_ptr_;
   HostStatusProvider* host_status_provider_;
diff --git a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl_unittest.cc b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl_unittest.cc
index 2e7aeb6..aa3a476 100644
--- a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl_unittest.cc
@@ -15,6 +15,7 @@
 #include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
 #include "chromeos/services/multidevice_setup/fake_setup_flow_completion_recorder.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "components/cryptauth/remote_device_ref.h"
 #include "components/cryptauth/remote_device_test_util.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -90,7 +91,7 @@
         cryptauth::RemoteDevice::GenerateDeviceId(old_host_key);
     test_pref_service_->SetString(
         AccountStatusChangeDelegateNotifierImpl::
-            kHostDeviceIdFromMostRecentHostStatusUpdatePrefName,
+            kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName,
         old_host_device_id);
   }
 
@@ -112,6 +113,12 @@
             kExistingUserChromebookAddedPrefName);
   }
 
+  std::string GetMostRecentVerifiedHostDeviceIdPref() {
+    return test_pref_service_->GetString(
+        AccountStatusChangeDelegateNotifierImpl::
+            kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName);
+  }
+
   FakeAccountStatusChangeDelegate* fake_delegate() {
     return fake_delegate_.get();
   }
@@ -216,35 +223,35 @@
        NotifiesObserverForHostSwitchEvents) {
   BuildAccountStatusChangeDelegateNotifier();
   SetAccountStatusChangeDelegatePtr();
-  // Verify the delegate initializes to 0.
+  // Check the delegate initializes to 0.
   EXPECT_EQ(0u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
 
-  // Set initial host.
+  // Set initially verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   // Host was set but has never been switched.
   EXPECT_EQ(0u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
 
-  // Switch hosts.
+  // Switch to new verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified,
                     cryptauth::RemoteDeviceRefBuilder()
-                        .SetPublicKey(kFakePhoneKey + "#1")
-                        .SetName(kFakePhoneName + "#1")
+                        .SetPublicKey(kFakePhoneKey + "A")
+                        .SetName(kFakePhoneName + "A")
                         .Build());
   EXPECT_EQ(1u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
 
-  // Switch to another new host.
+  // Switch to a different new verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified,
                     cryptauth::RemoteDeviceRefBuilder()
-                        .SetPublicKey(kFakePhoneKey + "#2")
-                        .SetName(kFakePhoneName + "#2")
+                        .SetPublicKey(kFakePhoneKey + "B")
+                        .SetName(kFakePhoneName + "B")
                         .Build());
   EXPECT_EQ(2u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
 
-  // Switch back to initial host.
+  // Switch back to initial host (verified).
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   EXPECT_EQ(3u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
@@ -254,7 +261,7 @@
        SettingSameHostTriggersNoHostSwitchedEvent) {
   BuildAccountStatusChangeDelegateNotifier();
   SetAccountStatusChangeDelegatePtr();
-  // Set initial host.
+  // Set initially verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   // Set to host with identical information.
   SetHostWithStatus(mojom::HostStatus::kHostVerified,
@@ -270,9 +277,9 @@
        ChangingHostDevicesTriggersHostSwitchEventWhenHostNameIsUnchanged) {
   BuildAccountStatusChangeDelegateNotifier();
   SetAccountStatusChangeDelegatePtr();
-  // Set initial host.
+  // Set initially verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
-  // Set to host with same name but different key.
+  // Set to verified host with same name but different key.
   SetHostWithStatus(mojom::HostStatus::kHostVerified,
                     cryptauth::RemoteDeviceRefBuilder()
                         .SetPublicKey(kFakePhoneKey + "alternate")
@@ -283,22 +290,79 @@
 }
 
 TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
-       VerifyingHostTriggersNoHostSwtichEvent) {
+       VerifyingSetHostTriggersNoHostSwtichEvent) {
   BuildAccountStatusChangeDelegateNotifier();
   SetAccountStatusChangeDelegatePtr();
-  // Set initial host but do not verify
+  // Set initial host but do not verify.
   SetHostWithStatus(mojom::HostStatus::kHostSetButNotYetVerified, kFakePhone);
-  // Verify host
+  // Verify host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   EXPECT_EQ(0u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
 }
 
 TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
+       OnlyVerifiedHostCausesHostSwitchedEvent) {
+  BuildAccountStatusChangeDelegateNotifier();
+  SetAccountStatusChangeDelegatePtr();
+  // Set initially verified host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
+
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+
+  // Set a new host without verifying.
+  SetHostWithStatus(mojom::HostStatus::kHostSetButNotYetVerified,
+                    cryptauth::RemoteDeviceRefBuilder()
+                        .SetPublicKey(kFakePhoneKey + "A")
+                        .SetName(kFakePhoneName + "A")
+                        .Build());
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+
+  // Set a different new host without confirming with backend so host is
+  // unverified.
+  SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      cryptauth::RemoteDeviceRefBuilder()
+          .SetPublicKey(kFakePhoneKey + "B")
+          .SetName(kFakePhoneName + "B")
+          .Build());
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+}
+
+TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
+       ForgettingAndThenSwitchingHostsDoesNotTriggerHostSwitchedEvent) {
+  BuildAccountStatusChangeDelegateNotifier();
+  SetAccountStatusChangeDelegatePtr();
+  // Set initially verified host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
+
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+
+  // Forget host.
+  SetHostWithStatus(mojom::HostStatus::kEligibleHostExistsButNoHostSet,
+                    base::nullopt /* host_device */);
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+
+  // Set a new verified host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified,
+                    cryptauth::RemoteDeviceRefBuilder()
+                        .SetPublicKey(kFakePhoneKey + "alternate")
+                        .SetName(kFakePhoneName + "alternate")
+                        .Build());
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+}
+
+TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
        HostSwitchedBetweenSessions) {
   SetHostFromPreviousSession(kFakePhoneKey + "-old");
   BuildAccountStatusChangeDelegateNotifier();
-  // Host switched between sessions.
+  // Host switched and verified between sessions.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   SetAccountStatusChangeDelegatePtr();
   EXPECT_EQ(1u,
@@ -312,7 +376,7 @@
   // No enabled host initially.
   SetHostWithStatus(mojom::HostStatus::kEligibleHostExistsButNoHostSet,
                     base::nullopt /* host_device */);
-  // Set host.
+  // Set and verify host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   EXPECT_EQ(0u,
             fake_delegate()->num_existing_user_host_switched_events_handled());
@@ -321,7 +385,7 @@
 TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
        NoHostSwitchedEventWithoutObserverSet) {
   BuildAccountStatusChangeDelegateNotifier();
-  // Set initial host.
+  // Set initially verified host.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   // All conditions for host switched event are satisfied except for setting a
   // delegate.
@@ -338,20 +402,66 @@
        NotifiesObserverForChromebookAddedEvents) {
   BuildAccountStatusChangeDelegateNotifier();
   SetAccountStatusChangeDelegatePtr();
-  // Verify the delegate initializes to 0.
+  // Check the delegate initializes to 0.
   EXPECT_EQ(
       0u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
 
-  // Host is set from another Chromebook while this one is logged in.
+  // Host is set and verified from another Chromebook while this one is logged
+  // in.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
   EXPECT_EQ(
       1u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
 }
 
 TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
+       OnlyVerifiedHostCausesChromebookAddedEvent) {
+  BuildAccountStatusChangeDelegateNotifier();
+  SetAccountStatusChangeDelegatePtr();
+  // Start with potential hosts but none set.
+  SetHostWithStatus(mojom::HostStatus::kEligibleHostExistsButNoHostSet,
+                    base::nullopt /* host_device */);
+
+  // Set a host without verifying.
+  SetHostWithStatus(mojom::HostStatus::kHostSetButNotYetVerified, kFakePhone);
+  EXPECT_EQ(
+      0u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
+
+  // Verify the new host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
+  EXPECT_EQ(
+      1u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
+}
+
+TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
+       ReplacingUnverifiedHostAWithVerifiedHostBCausesChromebookAddedEvent) {
+  BuildAccountStatusChangeDelegateNotifier();
+  SetAccountStatusChangeDelegatePtr();
+  // Start with potential hosts but none set.
+  // Set initial host but do not verify.
+  SetHostWithStatus(mojom::HostStatus::kHostSetButNotYetVerified,
+                    cryptauth::RemoteDeviceRefBuilder()
+                        .SetPublicKey(kFakePhoneKey + "A")
+                        .SetName(kFakePhoneName + "A")
+                        .Build());
+
+  // Replace unverified Phone A with verified Phone B.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified,
+                    cryptauth::RemoteDeviceRefBuilder()
+                        .SetPublicKey(kFakePhoneKey + "B")
+                        .SetName(kFakePhoneName + "B")
+                        .Build());
+  // This causes a 'Chromebook added' event.
+  EXPECT_EQ(
+      1u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
+  // It does not cause a 'host switched' event.
+  EXPECT_EQ(0u,
+            fake_delegate()->num_existing_user_host_switched_events_handled());
+}
+
+TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
        ChromebookAddedBetweenSessionsTriggersEvents) {
   BuildAccountStatusChangeDelegateNotifier();
-  // Host is set before this Chromebook is logged in.
+  // Host is set and verified before this Chromebook is logged in.
   SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
 
   SetAccountStatusChangeDelegatePtr();
@@ -387,6 +497,40 @@
       0u, fake_delegate()->num_existing_user_chromebook_added_events_handled());
 }
 
+TEST_F(MultiDeviceSetupAccountStatusChangeDelegateNotifierTest,
+       VerifiedHostIdStaysUpToDateInPrefs) {
+  BuildAccountStatusChangeDelegateNotifier();
+  SetAccountStatusChangeDelegatePtr();
+  // Check the delegate initializes to empty.
+  EXPECT_EQ(GetMostRecentVerifiedHostDeviceIdPref(), "");
+
+  // Set initially verified host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhone);
+  EXPECT_EQ(GetMostRecentVerifiedHostDeviceIdPref(), kFakePhone.GetDeviceId());
+
+  const cryptauth::RemoteDeviceRef kFakePhoneAlternate =
+      cryptauth::RemoteDeviceRefBuilder()
+          .SetPublicKey(kFakePhoneKey + "alternate")
+          .SetName(kFakePhoneName + "alternate")
+          .Build();
+
+  // Switch to an unverified host.
+  SetHostWithStatus(mojom::HostStatus::kHostSetButNotYetVerified,
+                    kFakePhoneAlternate);
+  // The host is set but not verified so the pref should be set to empty.
+  EXPECT_EQ(GetMostRecentVerifiedHostDeviceIdPref(), "");
+
+  // Verify the new host.
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, kFakePhoneAlternate);
+  EXPECT_EQ(GetMostRecentVerifiedHostDeviceIdPref(),
+            kFakePhoneAlternate.GetDeviceId());
+
+  // Forget host.
+  SetHostWithStatus(mojom::HostStatus::kEligibleHostExistsButNoHostSet,
+                    base::nullopt /* host_device */);
+  EXPECT_EQ(GetMostRecentVerifiedHostDeviceIdPref(), "");
+}
+
 }  // namespace multidevice_setup
 
 }  // namespace chromeos
diff --git a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
index 13eca47..dc148ec 100644
--- a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
+++ b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
@@ -45,7 +45,7 @@
 
   int timeout_ms = proto_.wait_for_dom().timeout_ms();
   if (timeout_ms > 0)
-    check_rounds = std::min(
+    check_rounds = std::max(
         1,
         static_cast<int>(std::ceil(timeout_ms / kCheckPeriodInMilliseconds)));
 
diff --git a/components/download/internal/common/download_response_handler.cc b/components/download/internal/common/download_response_handler.cc
index 5921bf4..4523b77 100644
--- a/components/download/internal/common/download_response_handler.cc
+++ b/components/download/internal/common/download_response_handler.cc
@@ -220,9 +220,10 @@
     return;
   }
 
-  // OnComplete() called without OnReceiveResponse(). This should only
+  // OnComplete() called without OnResponseStarted(). This should only
   // happen when the request was aborted.
-  create_info_ = CreateDownloadCreateInfo(network::ResourceResponseHead());
+  if (!create_info_)
+    create_info_ = CreateDownloadCreateInfo(network::ResourceResponseHead());
   create_info_->result = reason;
 
   OnResponseStarted(mojom::DownloadStreamHandlePtr());
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index 2186aa3..0c32c268 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -20,8 +20,8 @@
 #include "ash/wm/drag_window_resizer.h"
 #include "ash/wm/overview/window_selector_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
+#include "ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_resizer.h"
 #include "ash/wm/window_state.h"
@@ -1171,8 +1171,7 @@
   const base::TimeDelta duration =
       event_generator->CalculateScrollDurationForFlingVelocity(
           start, end,
-          ash::TabletModeAppWindowDragController::kFlingToOverviewThreshold +
-              10.f,
+          ash::TabletModeWindowDragDelegate::kFlingToOverviewThreshold + 10.f,
           200);
   event_generator->GestureScrollSequence(start, end, duration, 200);
   EXPECT_TRUE(shell->window_selector_controller()->IsSelecting());
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc
index d337f34..a984418 100644
--- a/components/exo/wayland/server.cc
+++ b/components/exo/wayland/server.cc
@@ -2674,9 +2674,7 @@
       const gfx::Rect& bounds = display.bounds();
       const gfx::Insets& insets = display.GetWorkAreaInsets();
 
-      double device_scale_factor = WMHelper::GetInstance()
-                                       ->GetDisplayInfo(display.id())
-                                       .device_scale_factor();
+      double device_scale_factor = display.device_scale_factor();
 
       uint32_t display_id_hi = static_cast<uint32_t>(display.id() >> 32);
       uint32_t display_id_lo = static_cast<uint32_t>(display.id());
diff --git a/components/history/core/browser/history_database.cc b/components/history/core/browser/history_database.cc
index c393b4a..d062389 100644
--- a/components/history/core/browser/history_database.cc
+++ b/components/history/core/browser/history_database.cc
@@ -257,12 +257,15 @@
       std::max(base::Time::Now() - base::TimeDelta::FromDays(30), base::Time());
 
   sql::Statement url_sql(db_.GetUniqueStatement(
-      "SELECT u.url, u.visit_count "
+      "SELECT u.url, COUNT(u.id) "
       "FROM urls u JOIN visits v ON u.id = v.url "
-      "WHERE last_visit_time > ? "
-      "AND (v.transition & ?) != 0 "              // CHAIN_END
-      "AND (transition & ?) NOT IN (?, ?, ?)"));  // NO SUBFRAME or
-                                                  // KEYWORD_GENERATED
+      "WHERE v.visit_time > ? "
+      "AND (v.transition & ?) != 0 "                 // CHAIN_END
+      "AND (v.transition & ?) NOT IN (?, ?, ?, ?) "  // NO SUBFRAME or
+                                                     // KEYWORD_GENERATED or
+                                                     // RELOAD
+      "GROUP BY u.url "
+      "ORDER BY u.last_visit_time DESC"));
 
   url_sql.BindInt64(0, one_month_ago.ToInternalValue());
   url_sql.BindInt(1, ui::PAGE_TRANSITION_CHAIN_END);
@@ -270,6 +273,7 @@
   url_sql.BindInt(3, ui::PAGE_TRANSITION_AUTO_SUBFRAME);
   url_sql.BindInt(4, ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
   url_sql.BindInt(5, ui::PAGE_TRANSITION_KEYWORD_GENERATED);
+  url_sql.BindInt(6, ui::PAGE_TRANSITION_RELOAD);
 
   // Collect a map from host to visit count.
   base::hash_map<std::string, int> host_count;
@@ -278,14 +282,20 @@
     if (!(url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("ftp"))))
       continue;
 
-    int64_t visit_count = url_sql.ColumnInt64(1);
-    host_count[HostForTopHosts(url)] += visit_count;
+    std::string host_for_top_host = HostForTopHosts(url);
 
     // kMaxHostsInMemory is well above typical values for
-    // History.MonthlyHostCount, but here to guard against unbounded memory
+    // History.MonthlyHostCount, but is used to guard against unbounded memory
     // growth in the event of an atypical history.
-    if (host_count.size() >= kMaxHostsInMemory)
-      break;
+    // Continue in the case where the host limit is reached and this is a newly
+    // encountered host that would add an additional entry to the map.
+    if (host_count.size() >= kMaxHostsInMemory &&
+        host_count.find(host_for_top_host) == host_count.end()) {
+      continue;
+    }
+
+    int64_t visit_count = url_sql.ColumnInt64(1);
+    host_count[host_for_top_host] += visit_count;
   }
 
   // Collect the top 100 hosts by visit count, into the range
diff --git a/components/history/core/browser/history_service_unittest.cc b/components/history/core/browser/history_service_unittest.cc
index 6790a486..c92c899 100644
--- a/components/history/core/browser/history_service_unittest.cc
+++ b/components/history/core/browser/history_service_unittest.cc
@@ -21,6 +21,9 @@
 
 #include <stdint.h>
 
+#include <algorithm>
+#include <string>
+
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/location.h"
@@ -58,11 +61,6 @@
 
   ~HistoryServiceTest() override {}
 
-  void OnMostVisitedURLsAvailable(const MostVisitedURLList* url_list) {
-    most_visited_urls_ = *url_list;
-    base::RunLoop::QuitCurrentWhenIdleDeprecated();
-  }
-
  protected:
   friend class BackendDelegate;
 
@@ -140,8 +138,8 @@
     base::RunLoop run_loop;
     history_service_->QueryRedirectsFrom(
         url,
-        base::Bind(&HistoryServiceTest::OnRedirectQueryComplete,
-                   base::Unretained(this), run_loop.QuitClosure()),
+        base::BindRepeating(&HistoryServiceTest::OnRedirectQueryComplete,
+                            base::Unretained(this), run_loop.QuitClosure()),
         &tracker_);
     run_loop.Run();  // Will be exited in *QueryComplete.
   }
@@ -157,11 +155,48 @@
     std::move(done).Run();
   }
 
+  void QueryMostVisitedURLs() {
+    const int kResultCount = 20;
+    const int kDaysBack = 90;
+
+    base::RunLoop run_loop;
+    history_service_->QueryMostVisitedURLs(
+        kResultCount, kDaysBack,
+        base::BindRepeating(&HistoryServiceTest::OnQueryMostVisitedURLsComplete,
+                            base::Unretained(this), run_loop.QuitClosure()),
+        &tracker_);
+    run_loop.Run();  // Will be exited in *QueryComplete.
+  }
+
+  void OnQueryMostVisitedURLsComplete(base::OnceClosure done,
+                                      const MostVisitedURLList* url_list) {
+    most_visited_urls_ = *url_list;
+    std::move(done).Run();
+  }
+
+  void GetTopHosts() {
+    const int kNumHosts = 20;
+
+    base::RunLoop run_loop;
+    history_service_->TopHosts(
+        kNumHosts,
+        base::BindRepeating(&HistoryServiceTest::OnTopHostsComplete,
+                            base::Unretained(this), run_loop.QuitClosure()));
+    run_loop.Run();  // Will be exited in *QueryComplete.
+  }
+
+  void OnTopHostsComplete(base::OnceClosure done,
+                          const TopHostsList& top_hosts_list) {
+    top_hosts_list_ = top_hosts_list;
+    std::move(done).Run();
+  }
+
   base::ScopedTempDir temp_dir_;
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
   MostVisitedURLList most_visited_urls_;
+  TopHostsList top_hosts_list_;
 
   // When non-NULL, this will be deleted on tear down and we will block until
   // the backend thread has completed. This allows tests for the history
@@ -499,13 +534,8 @@
       url1, base::Time::Now(), context_id, 0, GURL(),
       history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
       history::SOURCE_BROWSED, false);
-  history_service_->QueryMostVisitedURLs(
-      20,
-      90,
-      base::Bind(&HistoryServiceTest::OnMostVisitedURLsAvailable,
-                 base::Unretained(this)),
-      &tracker_);
-  base::RunLoop().Run();
+
+  QueryMostVisitedURLs();
 
   EXPECT_EQ(2U, most_visited_urls_.size());
   EXPECT_EQ(url0, most_visited_urls_[0].url);
@@ -516,13 +546,8 @@
       url2, base::Time::Now(), context_id, 0, GURL(),
       history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
       history::SOURCE_BROWSED, false);
-  history_service_->QueryMostVisitedURLs(
-      20,
-      90,
-      base::Bind(&HistoryServiceTest::OnMostVisitedURLsAvailable,
-                 base::Unretained(this)),
-      &tracker_);
-  base::RunLoop().Run();
+
+  QueryMostVisitedURLs();
 
   EXPECT_EQ(3U, most_visited_urls_.size());
   EXPECT_EQ(url0, most_visited_urls_[0].url);
@@ -534,13 +559,8 @@
       url2, base::Time::Now(), context_id, 0, GURL(),
       history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
       history::SOURCE_BROWSED, false);
-  history_service_->QueryMostVisitedURLs(
-      20,
-      90,
-      base::Bind(&HistoryServiceTest::OnMostVisitedURLsAvailable,
-                 base::Unretained(this)),
-      &tracker_);
-  base::RunLoop().Run();
+
+  QueryMostVisitedURLs();
 
   EXPECT_EQ(3U, most_visited_urls_.size());
   EXPECT_EQ(url2, most_visited_urls_[0].url);
@@ -552,13 +572,8 @@
       url1, base::Time::Now(), context_id, 0, GURL(),
       history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
       history::SOURCE_BROWSED, false);
-  history_service_->QueryMostVisitedURLs(
-      20,
-      90,
-      base::Bind(&HistoryServiceTest::OnMostVisitedURLsAvailable,
-                 base::Unretained(this)),
-      &tracker_);
-  base::RunLoop().Run();
+
+  QueryMostVisitedURLs();
 
   EXPECT_EQ(3U, most_visited_urls_.size());
   EXPECT_EQ(url1, most_visited_urls_[0].url);
@@ -571,13 +586,8 @@
       url4, base::Time::Now(), context_id, 0, GURL(),
       redirects, ui::PAGE_TRANSITION_TYPED,
       history::SOURCE_BROWSED, false);
-  history_service_->QueryMostVisitedURLs(
-      20,
-      90,
-      base::Bind(&HistoryServiceTest::OnMostVisitedURLsAvailable,
-                 base::Unretained(this)),
-      &tracker_);
-  base::RunLoop().Run();
+
+  QueryMostVisitedURLs();
 
   EXPECT_EQ(4U, most_visited_urls_.size());
   EXPECT_EQ(url1, most_visited_urls_[0].url);
@@ -587,6 +597,104 @@
   EXPECT_EQ(2U, most_visited_urls_[3].redirects.size());
 }
 
+TEST_F(HistoryServiceTest, TopHosts) {
+  ASSERT_TRUE(history_service_.get());
+
+  const std::string top_host0_espn_host_name = "espn.com";
+  const std::string top_host1_google_host_name = "google.com";
+  const std::string top_host2_cnn_host_name = "cnn.com";
+
+  const int top_host0_espn_host_count = 4;
+  const int top_host1_google_host_count = 3;
+  const int top_host2_cnn_host_count = 2;
+
+  const GURL url0_espn0("http://www.espn.com/");
+  const GURL url1_google0("http://www.google.com/url1/google0");
+  const GURL url2_google1("http://www.google.com/url2/google1");
+  const GURL url3_google2("http://www.google.com/url3/google2");
+  const GURL url4_espn1("http://www.espn.com/");
+  const GURL url5_cnn0("http://www.cnn.com/url5/cnn0");
+  const GURL url6_espn2("http://www.espn.com/");
+  const GURL url7_cnn1("http://www.cnn.com/url7/cnn1");
+  const GURL url8_espn3("http://www.espn.com/");
+
+  const GURL url9_espn4_expired("http://www.espn.com/");
+  const GURL url10_google3_expired("http://www.espn.com/url10/google3");
+
+  const GURL url11_espn5_reload("http://www.espn.com/");
+  const GURL url12_google4_reload("http://www.espn.com/url12/google4");
+
+  const ContextID context_id = reinterpret_cast<ContextID>(1);
+
+  // Add unexpired pages.
+  history_service_->AddPage(url0_espn0, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url1_google0, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url2_google1, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url3_google2, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url4_espn1, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url5_cnn0, base::Time::Now(), context_id, 0, GURL(),
+                            history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
+                            history::SOURCE_BROWSED, false);
+  history_service_->AddPage(url6_espn2, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url7_cnn1, base::Time::Now(), context_id, 0, GURL(),
+                            history::RedirectList(), ui::PAGE_TRANSITION_TYPED,
+                            history::SOURCE_BROWSED, false);
+  history_service_->AddPage(url8_espn3, base::Time::Now(), context_id, 0,
+                            GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+
+  // Add expired pages.
+  base::Time thirty_one_days_ago =
+      std::max(base::Time::Now() - base::TimeDelta::FromDays(31), base::Time());
+  history_service_->AddPage(url9_espn4_expired, thirty_one_days_ago, context_id,
+                            0, GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url10_google3_expired, thirty_one_days_ago,
+                            context_id, 0, GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
+                            false);
+
+  // Add reload pages.
+  history_service_->AddPage(url11_espn5_reload, base::Time::Now(), context_id,
+                            0, GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_RELOAD, history::SOURCE_BROWSED,
+                            false);
+  history_service_->AddPage(url12_google4_reload, base::Time::Now(), context_id,
+                            0, GURL(), history::RedirectList(),
+                            ui::PAGE_TRANSITION_RELOAD, history::SOURCE_BROWSED,
+                            false);
+
+  GetTopHosts();
+
+  ASSERT_EQ(3U, top_hosts_list_.size());
+  EXPECT_EQ(top_host0_espn_host_name, top_hosts_list_[0].first);
+  EXPECT_EQ(top_host1_google_host_name, top_hosts_list_[1].first);
+  EXPECT_EQ(top_host2_cnn_host_name, top_hosts_list_[2].first);
+  EXPECT_EQ(top_host0_espn_host_count, top_hosts_list_[0].second);
+  EXPECT_EQ(top_host1_google_host_count, top_hosts_list_[1].second);
+  EXPECT_EQ(top_host2_cnn_host_count, top_hosts_list_[2].second);
+}
+
 namespace {
 
 // A HistoryDBTask implementation. Each time RunOnDBThread is invoked
diff --git a/components/ntp_tiles/custom_links_manager.h b/components/ntp_tiles/custom_links_manager.h
index 99ef677..df527c1f 100644
--- a/components/ntp_tiles/custom_links_manager.h
+++ b/components/ntp_tiles/custom_links_manager.h
@@ -26,7 +26,7 @@
 // modifies the link, it will no longer be considered Most Visited and will not
 // be deleted when history is cleared.
 //
-// TODO(crbug/861831): Add Chrome sync support.
+// The current list of links is kept in sync with any changes from Chrome sync.
 class CustomLinksManager {
  public:
   struct Link {
@@ -80,7 +80,7 @@
 
   // Registers a callback that will be invoked when custom links are updated by
   // sources other than this interface's methods (i.e. when links are deleted by
-  // history clear).
+  // history clear or when links are updated by Chrome sync).
   virtual std::unique_ptr<base::CallbackList<void()>::Subscription>
   RegisterCallbackForOnChanged(base::RepeatingClosure callback) = 0;
 };
diff --git a/components/ntp_tiles/custom_links_manager_impl.cc b/components/ntp_tiles/custom_links_manager_impl.cc
index d0a6150..e59b77ec 100644
--- a/components/ntp_tiles/custom_links_manager_impl.cc
+++ b/components/ntp_tiles/custom_links_manager_impl.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "base/auto_reset.h"
 #include "components/ntp_tiles/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
@@ -32,6 +33,13 @@
     history_service_observer_.Add(history_service);
   if (IsInitialized())
     current_links_ = store_.RetrieveLinks();
+
+  base::RepeatingClosure callback =
+      base::BindRepeating(&CustomLinksManagerImpl::OnPreferenceChanged,
+                          weak_ptr_factory_.GetWeakPtr());
+  pref_change_registrar_.Init(prefs_);
+  pref_change_registrar_.Add(prefs::kCustomLinksInitialized, callback);
+  pref_change_registrar_.Add(prefs::kCustomLinksList, callback);
 }
 
 CustomLinksManagerImpl::~CustomLinksManagerImpl() = default;
@@ -43,14 +51,20 @@
   for (const NTPTile& tile : tiles)
     current_links_.emplace_back(Link{tile.url, tile.title, true});
 
-  store_.StoreLinks(current_links_);
-  prefs_->SetBoolean(prefs::kCustomLinksInitialized, true);
+  {
+    base::AutoReset<bool> auto_reset(&updating_preferences_, true);
+    prefs_->SetBoolean(prefs::kCustomLinksInitialized, true);
+  }
+  StoreLinks();
   return true;
 }
 
 void CustomLinksManagerImpl::Uninitialize() {
+  {
+    base::AutoReset<bool> auto_reset(&updating_preferences_, true);
+    prefs_->SetBoolean(prefs::kCustomLinksInitialized, false);
+  }
   ClearLinks();
-  prefs_->SetBoolean(prefs::kCustomLinksInitialized, false);
 }
 
 bool CustomLinksManagerImpl::IsInitialized() const {
@@ -74,7 +88,7 @@
 
   previous_links_ = current_links_;
   current_links_.emplace_back(Link{url, title, false});
-  store_.StoreLinks(current_links_);
+  StoreLinks();
   return true;
 }
 
@@ -106,7 +120,7 @@
     it->title = new_title;
   it->is_most_visited = false;
 
-  store_.StoreLinks(current_links_);
+  StoreLinks();
   return true;
 }
 
@@ -120,7 +134,7 @@
 
   previous_links_ = current_links_;
   current_links_.erase(it);
-  store_.StoreLinks(current_links_);
+  StoreLinks();
   return true;
 }
 
@@ -131,16 +145,24 @@
   // Replace the current links with the previous state.
   current_links_ = *previous_links_;
   previous_links_ = base::nullopt;
-  store_.StoreLinks(current_links_);
+  StoreLinks();
   return true;
 }
 
 void CustomLinksManagerImpl::ClearLinks() {
-  store_.ClearLinks();
+  {
+    base::AutoReset<bool> auto_reset(&updating_preferences_, true);
+    store_.ClearLinks();
+  }
   current_links_.clear();
   previous_links_ = base::nullopt;
 }
 
+void CustomLinksManagerImpl::StoreLinks() {
+  base::AutoReset<bool> auto_reset(&updating_preferences_, true);
+  store_.StoreLinks(current_links_);
+}
+
 std::vector<CustomLinksManager::Link>::iterator
 CustomLinksManagerImpl::FindLinkWithUrl(const GURL& url) {
   return std::find_if(current_links_.begin(), current_links_.end(),
@@ -172,7 +194,7 @@
         current_links_.erase(it);
     }
   }
-  store_.StoreLinks(current_links_);
+  StoreLinks();
   previous_links_ = base::nullopt;
 
   // Alert MostVisitedSites that some links have been deleted.
@@ -185,10 +207,24 @@
   history_service_observer_.RemoveAll();
 }
 
+void CustomLinksManagerImpl::OnPreferenceChanged() {
+  if (updating_preferences_)
+    return;
+
+  if (IsInitialized())
+    current_links_ = store_.RetrieveLinks();
+  else
+    current_links_.clear();
+  previous_links_ = base::nullopt;
+  callback_list_.Notify();
+}
+
 // static
 void CustomLinksManagerImpl::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* user_prefs) {
-  user_prefs->RegisterBooleanPref(prefs::kCustomLinksInitialized, false);
+  user_prefs->RegisterBooleanPref(
+      prefs::kCustomLinksInitialized, false,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   CustomLinksStore::RegisterProfilePrefs(user_prefs);
 }
 
diff --git a/components/ntp_tiles/custom_links_manager_impl.h b/components/ntp_tiles/custom_links_manager_impl.h
index 20e2c59..cc5902b 100644
--- a/components/ntp_tiles/custom_links_manager_impl.h
+++ b/components/ntp_tiles/custom_links_manager_impl.h
@@ -17,6 +17,7 @@
 #include "components/ntp_tiles/custom_links_manager.h"
 #include "components/ntp_tiles/custom_links_store.h"
 #include "components/ntp_tiles/ntp_tile.h"
+#include "components/prefs/pref_change_registrar.h"
 
 class PrefService;
 
@@ -60,6 +61,11 @@
 
  private:
   void ClearLinks();
+
+  // Stores the current list to the profile's preferences. Does not notify
+  // |OnPreferenceChanged|.
+  void StoreLinks();
+
   // Returns an iterator into |custom_links_|.
   std::vector<Link>::iterator FindLinkWithUrl(const GURL& url);
 
@@ -71,6 +77,11 @@
   void HistoryServiceBeingDeleted(
       history::HistoryService* history_service) override;
 
+  // Called when the current list of links and/or initialization state in
+  // PrefService is modified. Saves the new set of links in |current_links_|
+  // and notifies |callback_list_|.
+  void OnPreferenceChanged();
+
   PrefService* const prefs_;
   CustomLinksStore store_;
   std::vector<Link> current_links_;
@@ -86,6 +97,13 @@
   ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
       history_service_observer_;
 
+  // Observer for Chrome sync changes to |prefs::kCustomLinksList| and
+  // |prefs::kCustomLinksInitialized|.
+  PrefChangeRegistrar pref_change_registrar_;
+  // Used to ignore notifications from |pref_change_registrar_| that we trigger
+  // ourselves when updating the preferences.
+  bool updating_preferences_ = false;
+
   base::WeakPtrFactory<CustomLinksManagerImpl> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(CustomLinksManagerImpl);
diff --git a/components/ntp_tiles/custom_links_manager_impl_unittest.cc b/components/ntp_tiles/custom_links_manager_impl_unittest.cc
index b70c124..bf54bfc 100644
--- a/components/ntp_tiles/custom_links_manager_impl_unittest.cc
+++ b/components/ntp_tiles/custom_links_manager_impl_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/history/core/test/history_service_test_util.h"
+#include "components/ntp_tiles/pref_names.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -44,6 +45,16 @@
 const char kTestTitle[] = "Test";
 const char kTestUrl[] = "http://test.com/";
 
+base::Value::ListStorage FillTestListStorage(const char* url,
+                                             const char* title) {
+  base::Value::ListStorage new_link_list;
+  base::DictionaryValue new_link;
+  new_link.SetKey("url", base::Value(url));
+  new_link.SetKey("title", base::Value(title));
+  new_link_list.push_back(std::move(new_link));
+  return new_link_list;
+}
+
 void AddTile(NTPTilesVector* tiles, const char* url, const char* title) {
   NTPTile tile;
   tile.url = GURL(url);
@@ -602,4 +613,114 @@
   scoped_task_environment_.RunUntilIdle();
 }
 
+TEST_F(CustomLinksManagerImplTest, UpdateListAfterRemoteChange) {
+  Link remote_link{GURL(kTestUrl), base::UTF8ToUTF16(kTestTitle), false};
+  NTPTilesVector initial_tiles;
+  std::vector<Link> initial_links;
+  std::vector<Link> links_after_add = FillTestLinks(kTestCase1);
+  links_after_add[0].is_most_visited = false;
+  std::vector<Link> remote_links;
+  remote_links.emplace_back(remote_link);
+
+  // Set up Most Visited callback.
+  base::MockCallback<base::RepeatingClosure> callback;
+  std::unique_ptr<base::CallbackList<void()>::Subscription> subscription =
+      custom_links_->RegisterCallbackForOnChanged(callback.Get());
+
+  // Initialize.
+  ASSERT_TRUE(custom_links_->Initialize(initial_tiles));
+  ASSERT_EQ(initial_links, custom_links_->GetLinks());
+
+  // Modifying ourselves should not notify.
+  EXPECT_CALL(callback, Run()).Times(0);
+  EXPECT_TRUE(custom_links_->AddLink(GURL(kTestCase1[0].url),
+                                     base::UTF8ToUTF16(kTestCase1[0].title)));
+  EXPECT_EQ(links_after_add, custom_links_->GetLinks());
+
+  // Modify the preference. This should notify and update the current list of
+  // links.
+  EXPECT_CALL(callback, Run());
+  prefs_.SetUserPref(
+      prefs::kCustomLinksList,
+      std::make_unique<base::Value>(FillTestListStorage(kTestUrl, kTestTitle)));
+  EXPECT_EQ(remote_links, custom_links_->GetLinks());
+}
+
+TEST_F(CustomLinksManagerImplTest, InitializeListAfterRemoteChange) {
+  Link remote_link{GURL(kTestUrl), base::UTF8ToUTF16(kTestTitle), false};
+  NTPTilesVector initial_tiles;
+  std::vector<Link> initial_links;
+  std::vector<Link> remote_links(initial_links);
+  remote_links.emplace_back(remote_link);
+
+  // Set up Most Visited callback.
+  base::MockCallback<base::RepeatingClosure> callback;
+  std::unique_ptr<base::CallbackList<void()>::Subscription> subscription =
+      custom_links_->RegisterCallbackForOnChanged(callback.Get());
+
+  ASSERT_FALSE(custom_links_->IsInitialized());
+
+  // Modify the preference. This should notify and initialize custom links.
+  EXPECT_CALL(callback, Run()).Times(2);
+  prefs_.SetUserPref(prefs::kCustomLinksInitialized,
+                     std::make_unique<base::Value>(true));
+  prefs_.SetUserPref(
+      prefs::kCustomLinksList,
+      std::make_unique<base::Value>(FillTestListStorage(kTestUrl, kTestTitle)));
+  EXPECT_TRUE(custom_links_->IsInitialized());
+  EXPECT_EQ(remote_links, custom_links_->GetLinks());
+}
+
+TEST_F(CustomLinksManagerImplTest, UninitializeListAfterRemoteChange) {
+  NTPTilesVector initial_tiles = FillTestTiles(kTestCase1);
+  std::vector<Link> initial_links = FillTestLinks(kTestCase1);
+  std::vector<Link> remote_links;
+
+  // Set up Most Visited callback.
+  base::MockCallback<base::RepeatingClosure> callback;
+  std::unique_ptr<base::CallbackList<void()>::Subscription> subscription =
+      custom_links_->RegisterCallbackForOnChanged(callback.Get());
+
+  // Initialize.
+  ASSERT_TRUE(custom_links_->Initialize(initial_tiles));
+  ASSERT_EQ(initial_links, custom_links_->GetLinks());
+
+  // Modify the preference. This should notify and uninitialize custom links.
+  EXPECT_CALL(callback, Run()).Times(2);
+  prefs_.SetUserPref(prefs::kCustomLinksInitialized,
+                     std::make_unique<base::Value>(false));
+  prefs_.SetUserPref(prefs::kCustomLinksList,
+                     std::make_unique<base::Value>(base::Value::ListStorage()));
+  EXPECT_FALSE(custom_links_->IsInitialized());
+  EXPECT_EQ(remote_links, custom_links_->GetLinks());
+}
+
+TEST_F(CustomLinksManagerImplTest, ClearThenUninitializeListAfterRemoteChange) {
+  NTPTilesVector initial_tiles = FillTestTiles(kTestCase1);
+  std::vector<Link> initial_links = FillTestLinks(kTestCase1);
+  std::vector<Link> remote_links;
+
+  // Set up Most Visited callback.
+  base::MockCallback<base::RepeatingClosure> callback;
+  std::unique_ptr<base::CallbackList<void()>::Subscription> subscription =
+      custom_links_->RegisterCallbackForOnChanged(callback.Get());
+
+  // Initialize.
+  ASSERT_TRUE(custom_links_->Initialize(initial_tiles));
+  ASSERT_EQ(initial_links, custom_links_->GetLinks());
+
+  // Modify the preference. Simulates when the list preference is synced before
+  // the initialized preference. This should notify and uninitialize custom
+  // links.
+  EXPECT_CALL(callback, Run()).Times(2);
+  prefs_.SetUserPref(prefs::kCustomLinksList,
+                     std::make_unique<base::Value>(base::Value::ListStorage()));
+  EXPECT_TRUE(custom_links_->IsInitialized());
+  EXPECT_EQ(remote_links, custom_links_->GetLinks());
+  prefs_.SetUserPref(prefs::kCustomLinksInitialized,
+                     std::make_unique<base::Value>(false));
+  EXPECT_FALSE(custom_links_->IsInitialized());
+  EXPECT_EQ(remote_links, custom_links_->GetLinks());
+}
+
 }  // namespace ntp_tiles
diff --git a/components/ntp_tiles/custom_links_store.cc b/components/ntp_tiles/custom_links_store.cc
index 84211f41..53f38616 100644
--- a/components/ntp_tiles/custom_links_store.cc
+++ b/components/ntp_tiles/custom_links_store.cc
@@ -71,7 +71,8 @@
 // static
 void CustomLinksStore::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* user_prefs) {
-  user_prefs->RegisterListPref(prefs::kCustomLinksList);
+  user_prefs->RegisterListPref(prefs::kCustomLinksList,
+                               user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
 }
 
 }  // namespace ntp_tiles
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.h b/components/offline_pages/core/model/offline_page_model_taskified.h
index 391d2beb..0d5dc55 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.h
+++ b/components/offline_pages/core/model/offline_page_model_taskified.h
@@ -79,7 +79,6 @@
   // OfflinePageModel implementation.
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-
   void SavePage(const SavePageParams& save_page_params,
                 std::unique_ptr<OfflinePageArchiver> archiver,
                 content::WebContents* web_contents,
@@ -108,10 +107,8 @@
                      MultipleOfflinePageItemCallback callback) override;
   void GetPagesByNamespace(const std::string& name_space,
                            MultipleOfflinePageItemCallback callback) override;
-  // Get all pages in the namespaces that will be removed on cache reset.
   void GetPagesRemovedOnCacheReset(
       MultipleOfflinePageItemCallback callback) override;
-  // Get all pages in the namespaces that are shown in download ui.
   void GetPagesSupportedByDownloads(
       MultipleOfflinePageItemCallback callback) override;
   void GetPagesByRequestOrigin(
@@ -130,16 +127,11 @@
   void HasThumbnailForOfflineId(
       int64_t offline_id,
       base::OnceCallback<void(bool)> callback) override;
-
   const base::FilePath& GetInternalArchiveDirectory(
       const std::string& name_space) const override;
   bool IsArchiveInInternalDir(const base::FilePath& file_path) const override;
-
   ClientPolicyController* GetPolicyController() override;
-
   OfflineEventLogger* GetLogger() override;
-
-  // Publish an offline page from our internal directory to a public directory.
   void PublishInternalArchive(
       const OfflinePageItem& offline_page,
       std::unique_ptr<OfflinePageArchiver> archiver,
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 3c11e555..3bb7a0cc 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -512,12 +512,20 @@
        i != providers_.end(); ++i)
     result_.AppendMatches(input_, (*i)->matches());
 
+  if (OmniboxFieldTrial::GetPedalSuggestionMode() ==
+      OmniboxFieldTrial::PedalSuggestionMode::DEDICATED)
+    result_.AppendDedicatedPedalMatches(provider_client_.get(), input_);
+
   // Sort the matches and trim to a small number of "best" matches.
   result_.SortAndCull(input_, template_url_service_);
 
   if (OmniboxFieldTrial::IsTabSwitchSuggestionsEnabled())
     result_.ConvertOpenTabMatches(provider_client_.get(), &input_);
 
+  if (OmniboxFieldTrial::GetPedalSuggestionMode() ==
+      OmniboxFieldTrial::PedalSuggestionMode::IN_SUGGESTION)
+    result_.ConvertInSuggestionPedalMatches(provider_client_.get());
+
   // Need to validate before invoking CopyOldMatches as the old matches are not
   // valid against the current input.
 #if DCHECK_IS_ON()
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index addc22e8..32314f6 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -23,6 +23,7 @@
 #include "components/omnibox/browser/autocomplete_provider.h"
 #include "components/omnibox/browser/buildflags.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/omnibox_pedal.h"
 #include "components/omnibox/browser/suggestion_answer.h"
 #include "components/search_engines/template_url.h"
 #include "components/search_engines/template_url_service.h"
@@ -143,6 +144,7 @@
                              ? new AutocompleteMatch(*match.associated_keyword)
                              : nullptr),
       keyword(match.keyword),
+      pedal(match.pedal),
       from_previous(match.from_previous),
       search_terms_args(
           match.search_terms_args
@@ -188,6 +190,7 @@
           ? new AutocompleteMatch(*match.associated_keyword)
           : nullptr);
   keyword = match.keyword;
+  pedal = match.pedal;
   from_previous = match.from_previous;
   search_terms_args.reset(
       match.search_terms_args
@@ -278,6 +281,9 @@
     case Type::SEARCH_SUGGEST_TAIL:
       return omnibox::kBlankIcon;
 
+    case Type::PEDAL:
+      return omnibox::kPedalIcon;
+
     case Type::NUM_TYPES:
       NOTREACHED();
       break;
@@ -683,6 +689,19 @@
   return answer ? answer->image_url() : GURL(image_url);
 }
 
+void AutocompleteMatch::ApplyPedal() {
+  type = Type::PEDAL;
+  contents = pedal->GetLabelStrings().suggestion_contents;
+  destination_url = pedal->GetNavigationUrl();
+
+  // Normally this is computed by the match using a TemplateURLService
+  // but Pedal URLs are not typical and unknown, and we don't want them to
+  // be deduped, e.g. after stripping a query parameter that may do something
+  // meaningful like indicate the viewable scope of a settings page.  So here
+  // we keep the URL exactly as the Pedal specifies it.
+  stripped_destination_url = destination_url;
+}
+
 void AutocompleteMatch::RecordAdditionalInfo(const std::string& property,
                                              const std::string& value) {
   DCHECK(!property.empty());
@@ -789,7 +808,11 @@
 }
 
 bool AutocompleteMatch::ShouldShowTabMatch() const {
-  return has_tab_match && !associated_keyword;
+  // TODO(orinj): If side button Pedal presentation mode is not kept,
+  // the simpler logic (with no pedal checks) can be restored, and if it is
+  // kept then some minor refactoring (or at least renaming) is in order.
+  return (has_tab_match && !associated_keyword) ||
+         (pedal && pedal->ShouldPresentButton());
 }
 
 #if DCHECK_IS_ON()
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index 5b348fe..a2c987d 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -23,6 +23,7 @@
 #include "url/gurl.h"
 
 class AutocompleteProvider;
+class OmniboxPedal;
 class SuggestionAnswer;
 class TemplateURL;
 class TemplateURLService;
@@ -303,6 +304,9 @@
   // there isn't an image URL, returns an empty GURL (test with is_empty()).
   GURL ImageUrl() const;
 
+  // Changes properties to make use of the Pedal (e.g. content, URLs...).
+  void ApplyPedal();
+
   // Adds optional information to the |additional_info| dictionary.
   void RecordAdditionalInfo(const std::string& property,
                             const std::string& value);
@@ -477,6 +481,10 @@
   // it!
   base::string16 keyword;
 
+  // Set to a matching pedal if appropriate.  The pedal is not owned, and the
+  // owning OmniboxPedalProvider must outlive this.
+  OmniboxPedal* pedal = nullptr;
+
   // True if this match is from a previous result.
   bool from_previous;
 
diff --git a/components/omnibox/browser/autocomplete_match_type.cc b/components/omnibox/browser/autocomplete_match_type.cc
index cd315dc..246ad99 100644
--- a/components/omnibox/browser/autocomplete_match_type.cc
+++ b/components/omnibox/browser/autocomplete_match_type.cc
@@ -42,6 +42,7 @@
     "physical-web-overflow",
     "tab-search",
     "document",
+    "pedal",
   };
   // clang-format on
   static_assert(arraysize(strings) == AutocompleteMatchType::NUM_TYPES,
@@ -125,6 +126,9 @@
       0,                               // PHYSICAL_WEB_OVERFLOW_DEPRECATED
       IDS_ACC_AUTOCOMPLETE_HISTORY,    // TAB_SEARCH_DEPRECATED
       0,                               // DOCUMENT_SUGGESTION
+
+      // TODO(orinj): Determine appropriate accessibility labels for Pedals
+      0,  // PEDAL
   };
   static_assert(arraysize(message_ids) == AutocompleteMatchType::NUM_TYPES,
                 "message_ids must have NUM_TYPES elements");
diff --git a/components/omnibox/browser/autocomplete_match_type.h b/components/omnibox/browser/autocomplete_match_type.h
index d9441d9..3f3af82 100644
--- a/components/omnibox/browser/autocomplete_match_type.h
+++ b/components/omnibox/browser/autocomplete_match_type.h
@@ -64,6 +64,7 @@
     TAB_SEARCH_DEPRECATED       = 23,  // A suggested open tab, based on its
                                        // URL or title, via HQP (deprecated).
     DOCUMENT_SUGGESTION         = 24,  // A suggested document.
+    PEDAL                       = 25,  // An omnibox pedal suggestion.
     NUM_TYPES,
   };
   // clang-format on
diff --git a/components/omnibox/browser/autocomplete_provider_client.h b/components/omnibox/browser/autocomplete_provider_client.h
index 9bf2735..8f3cc36 100644
--- a/components/omnibox/browser/autocomplete_provider_client.h
+++ b/components/omnibox/browser/autocomplete_provider_client.h
@@ -26,6 +26,7 @@
 class GURL;
 class InMemoryURLIndex;
 class KeywordProvider;
+class OmniboxPedalProvider;
 class PrefService;
 class ShortcutsBackend;
 
@@ -64,6 +65,7 @@
       bool create_if_necessary) const = 0;
   virtual DocumentSuggestionsService* GetDocumentSuggestionsService(
       bool create_if_necessary) const = 0;
+  virtual OmniboxPedalProvider* GetPedalProvider() const = 0;
   virtual scoped_refptr<ShortcutsBackend> GetShortcutsBackend() = 0;
   virtual scoped_refptr<ShortcutsBackend> GetShortcutsBackendIfExists() = 0;
   virtual std::unique_ptr<KeywordExtensionsDelegate>
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index af578d6..742c670 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -8,6 +8,7 @@
 #include <functional>
 #include <iterator>
 #include <string>
+#include <unordered_set>
 
 #include "base/command_line.h"
 #include "base/logging.h"
@@ -22,6 +23,8 @@
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/match_compare.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/omnibox_pedal.h"
+#include "components/omnibox/browser/omnibox_pedal_provider.h"
 #include "components/omnibox/browser/omnibox_switches.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/url_fixer.h"
@@ -226,6 +229,49 @@
       GURL() : ComputeAlternateNavUrl(input, *default_match_);
 }
 
+void AutocompleteResult::AppendDedicatedPedalMatches(
+    AutocompleteProviderClient* client,
+    const AutocompleteInput& input) {
+  ACMatches pedal_suggestions;
+  const OmniboxPedalProvider* provider = client->GetPedalProvider();
+  for (const auto& match : matches_) {
+    if (match.pedal)
+      continue;
+    OmniboxPedal* pedal = provider->FindPedalMatch(match.contents);
+    if (pedal) {
+      AutocompleteMatch suggestion = match;
+      suggestion.relevance--;
+      suggestion.pedal = pedal;
+      suggestion.ApplyPedal();
+      pedal_suggestions.push_back(suggestion);
+    }
+  }
+  if (!pedal_suggestions.empty()) {
+    AppendMatches(input, pedal_suggestions);
+  }
+}
+
+void AutocompleteResult::ConvertInSuggestionPedalMatches(
+    AutocompleteProviderClient* client) {
+  const OmniboxPedalProvider* provider = client->GetPedalProvider();
+  // Used to ensure we keep only one Pedal of each kind.
+  std::unordered_set<OmniboxPedal*> pedals_found;
+  for (auto& match : matches_) {
+    // Skip matches that will not show Pedal because they already
+    // have a tab match or associated keyword.  Also skip matches
+    // that have already detected their Pedal.
+    if (match.has_tab_match || match.associated_keyword || match.pedal)
+      continue;
+
+    OmniboxPedal* const pedal = provider->FindPedalMatch(match.contents);
+    if (pedal) {
+      const auto result = pedals_found.insert(pedal);
+      if (result.second)
+        match.pedal = pedal;
+    }
+  }
+}
+
 void AutocompleteResult::ConvertOpenTabMatches(
     AutocompleteProviderClient* client,
     const AutocompleteInput* input) {
@@ -239,6 +285,7 @@
       match.has_tab_match = true;
   }
 }
+
 bool AutocompleteResult::HasCopiedMatches() const {
   for (auto i(begin()); i != end(); ++i) {
     if (i->from_previous)
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index 06dfd23a..afd1841 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -55,6 +55,13 @@
   void SortAndCull(const AutocompleteInput& input,
                    TemplateURLService* template_url_service);
 
+  // Creates and adds any dedicated Pedal matches triggered by existing match.
+  void AppendDedicatedPedalMatches(AutocompleteProviderClient* client,
+                                   const AutocompleteInput& input);
+
+  // Sets |pedal| in matches that have Pedal-triggering text.
+  void ConvertInSuggestionPedalMatches(AutocompleteProviderClient* client);
+
   // Sets |has_tab_match| in matches whose URL matches an open tab's URL.
   // Also, fixes up the description if not using another UI element to
   // annotate (e.g. tab switch button). |input| can be null; if provided,
diff --git a/components/omnibox/browser/mock_autocomplete_provider_client.cc b/components/omnibox/browser/mock_autocomplete_provider_client.cc
index 3ea1bb7..4b4f390 100644
--- a/components/omnibox/browser/mock_autocomplete_provider_client.cc
+++ b/components/omnibox/browser/mock_autocomplete_provider_client.cc
@@ -16,6 +16,7 @@
           /*identity_manager=*/nullptr, GetURLLoaderFactory());
   document_suggestions_service_ = std::make_unique<DocumentSuggestionsService>(
       /*identity_manager=*/nullptr, GetURLLoaderFactory());
+  pedal_provider_ = std::make_unique<OmniboxPedalProvider>();
 }
 
 MockAutocompleteProviderClient::~MockAutocompleteProviderClient() {
diff --git a/components/omnibox/browser/mock_autocomplete_provider_client.h b/components/omnibox/browser/mock_autocomplete_provider_client.h
index 4d13a42..0cc0d0f8 100644
--- a/components/omnibox/browser/mock_autocomplete_provider_client.h
+++ b/components/omnibox/browser/mock_autocomplete_provider_client.h
@@ -15,6 +15,7 @@
 #include "components/omnibox/browser/autocomplete_scheme_classifier.h"
 #include "components/omnibox/browser/contextual_suggestions_service.h"
 #include "components/omnibox/browser/document_suggestions_service.h"
+#include "components/omnibox/browser/omnibox_pedal_provider.h"
 #include "components/search_engines/template_url_service.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -60,6 +61,9 @@
       bool create_if_necessary) const override {
     return document_suggestions_service_.get();
   }
+  OmniboxPedalProvider* GetPedalProvider() const override {
+    return pedal_provider_.get();
+  }
 
   // Can't mock scoped_refptr :\.
   scoped_refptr<ShortcutsBackend> GetShortcutsBackend() override {
@@ -116,6 +120,7 @@
 
   std::unique_ptr<ContextualSuggestionsService> contextual_suggestions_service_;
   std::unique_ptr<DocumentSuggestionsService> document_suggestions_service_;
+  std::unique_ptr<OmniboxPedalProvider> pedal_provider_;
   std::unique_ptr<TemplateURLService> template_url_service_;
 
   DISALLOW_COPY_AND_ASSIGN(MockAutocompleteProviderClient);
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 4c9881a..6b62d0fb 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -32,6 +32,7 @@
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_log.h"
 #include "components/omnibox/browser/omnibox_navigation_observer.h"
+#include "components/omnibox/browser/omnibox_pedal.h"
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/omnibox/browser/omnibox_popup_view.h"
 #include "components/omnibox/browser/omnibox_view.h"
@@ -635,6 +636,20 @@
   autocomplete_controller()->UpdateMatchDestinationURLWithQueryFormulationTime(
       elapsed_time_since_user_first_modified_omnibox, &match);
 
+  // TODO(orinj): This is being used to distinguish between button
+  // press and other (keyboard/click) acceptance of suggestion but
+  // if in-suggestion side button Pedals are liked/kept by UX & PM then
+  // the meaning should be clarified.  Instead of relying on SWITCH_TO_TAB,
+  // it may make sense to add a new disposition and change/move this code.
+  const bool button_pressed =
+      disposition == WindowOpenDisposition::SWITCH_TO_TAB;
+  if (match.pedal && match.pedal->ShouldExecute(button_pressed)) {
+    OmniboxPedal::ExecutionContext context(*client_, *controller_,
+                                           match_selection_timestamp);
+    match.pedal->Execute(context);
+    return;
+  }
+
   base::string16 input_text(pasted_text);
   if (input_text.empty())
     input_text = user_input_in_progress_ ? user_text_ : url_for_editing_;
@@ -768,6 +783,7 @@
     base::RecordAction(
         base::UserMetricsAction("OmniboxDestinationURLIsSearchOnDSP"));
   }
+
   if (match.destination_url.is_valid()) {
     // This calls RevertAll again.
     base::AutoReset<bool> tmp(&in_revert_, true);
@@ -1394,10 +1410,14 @@
         (!popup_model() || !popup_model()->has_selected_match()))
       *alternate_nav_url = result().alternate_nav_url();
   } else {
+    base::string16 text_for_match_generation =
+        (user_input_in_progress() || GetQueryInOmniboxSearchTerms(nullptr))
+            ? view_->GetText()
+            : url_for_editing_;
+
     client_->GetAutocompleteClassifier()->Classify(
-        MaybePrependKeyword(user_input_in_progress_ ? view_->GetText()
-                                                    : url_for_editing_),
-        is_keyword_selected(), true, ClassifyPage(), match, alternate_nav_url);
+        MaybePrependKeyword(text_for_match_generation), is_keyword_selected(),
+        true, ClassifyPage(), match, alternate_nav_url);
   }
 }
 
diff --git a/components/omnibox/browser/omnibox_edit_model_unittest.cc b/components/omnibox/browser/omnibox_edit_model_unittest.cc
index 3607bd3..fd0dcfc2 100644
--- a/components/omnibox/browser/omnibox_edit_model_unittest.cc
+++ b/components/omnibox/browser/omnibox_edit_model_unittest.cc
@@ -141,7 +141,7 @@
     AutocompleteMatch match;
     match.type = AutocompleteMatchType::NAVSUGGEST;
     match.destination_url = GURL(input[i].match_destination_url);
-    model()->SetCurrentMatch(match);
+    model()->SetCurrentMatchForTest(match);
 
     base::string16 result = base::ASCIIToUTF16(input[i].input);
     GURL url;
@@ -254,15 +254,32 @@
       client->alternate_nav_match().fill_into_edit));
 }
 
-TEST_F(OmniboxEditModelTest, GenerateMatchesFromFullFormattedUrl) {
+TEST_F(OmniboxEditModelTest, CurrentMatch) {
   toolbar_model()->set_formatted_full_url(
       base::ASCIIToUTF16("http://localhost/"));
   toolbar_model()->set_url_for_display(base::ASCIIToUTF16("localhost"));
   model()->ResetDisplayTexts();
 
-  // Bypass the test class's mock method to test the real behavior.
-  AutocompleteMatch match = model()->OmniboxEditModel::CurrentMatch(nullptr);
-  EXPECT_EQ(AutocompleteMatchType::URL_WHAT_YOU_TYPED, match.type);
+  // Tests that we use the formatted full URL instead of the elided URL to
+  // generate matches.
+  {
+    AutocompleteMatch match = model()->CurrentMatch(nullptr);
+    EXPECT_EQ(AutocompleteMatchType::URL_WHAT_YOU_TYPED, match.type);
+    EXPECT_TRUE(model()->CurrentTextIsURL());
+  }
+
+  // Tests that when there is a Query in Omnibox, generate matches from the
+  // query, instead of the full formatted URL.
+  TestOmniboxClient* client =
+      static_cast<TestOmniboxClient*>(model()->client());
+  client->SetFakeSearchTermsForQueryInOmnibox(base::ASCIIToUTF16("foobar"));
+  model()->ResetDisplayTexts();
+
+  {
+    AutocompleteMatch match = model()->CurrentMatch(nullptr);
+    EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, match.type);
+    EXPECT_FALSE(model()->CurrentTextIsURL());
+  }
 }
 
 TEST_F(OmniboxEditModelTest, DisplayText) {
diff --git a/components/omnibox/browser/omnibox_metrics_provider.cc b/components/omnibox/browser/omnibox_metrics_provider.cc
index de326d20..4a5447a 100644
--- a/components/omnibox/browser/omnibox_metrics_provider.cc
+++ b/components/omnibox/browser/omnibox_metrics_provider.cc
@@ -66,6 +66,10 @@
       return OmniboxEventProto::Suggestion::CLIPBOARD;
     case AutocompleteMatchType::DOCUMENT_SUGGESTION:
       return OmniboxEventProto::Suggestion::DOCUMENT;
+    case AutocompleteMatchType::PEDAL:
+      // TODO(orinj): Add a new OmniboxEventProto type for Pedals.
+      // return OmniboxEventProto::Suggestion::PEDAL;
+      return OmniboxEventProto::Suggestion::NAVSUGGEST;
     case AutocompleteMatchType::VOICE_SUGGEST:
       // VOICE_SUGGEST matches are only used in Java and are not logged,
       // so we should never reach this case.
diff --git a/components/omnibox/browser/omnibox_pedal.cc b/components/omnibox/browser/omnibox_pedal.cc
index 860c7fa..97848d85 100644
--- a/components/omnibox/browser/omnibox_pedal.cc
+++ b/components/omnibox/browser/omnibox_pedal.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/omnibox/browser/omnibox_pedal.h"
+
 #include "base/strings/utf_string_conversions.h"
 #include "components/omnibox/browser/omnibox_client.h"
 #include "components/omnibox/browser/omnibox_edit_controller.h"
@@ -26,6 +27,14 @@
   return strings_;
 }
 
+bool OmniboxPedal::IsNavigation() const {
+  return !url_.is_empty();
+}
+
+const GURL& OmniboxPedal::GetNavigationUrl() const {
+  return url_;
+}
+
 bool OmniboxPedal::ShouldExecute(bool button_pressed) const {
   const auto mode = OmniboxFieldTrial::GetPedalSuggestionMode();
   return (mode == OmniboxFieldTrial::PedalSuggestionMode::DEDICATED) ||
@@ -38,16 +47,20 @@
          OmniboxFieldTrial::PedalSuggestionMode::IN_SUGGESTION;
 }
 
+void OmniboxPedal::Execute(OmniboxPedal::ExecutionContext& context) const {
+  DCHECK(IsNavigation());
+  OpenURL(context, url_);
+}
+
 bool OmniboxPedal::IsTriggerMatch(const base::string16& match_text) const {
   return triggers_.find(match_text) != triggers_.end();
 }
 
 void OmniboxPedal::OpenURL(OmniboxPedal::ExecutionContext& context,
                            const GURL& url) const {
-  // TODO(orinj): This will use AutocompleteMatchType::PEDAL
   context.controller_.OnAutocompleteAccept(
       url, WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_GENERATED,
-      AutocompleteMatchType::NAVSUGGEST, context.match_selection_timestamp_);
+      AutocompleteMatchType::PEDAL, context.match_selection_timestamp_);
 }
 
 // =============================================================================
@@ -57,6 +70,7 @@
           IDS_OMNIBOX_PEDAL_CLEAR_BROWSING_DATA_HINT,
           IDS_OMNIBOX_PEDAL_CLEAR_BROWSING_DATA_HINT_SHORT,
           IDS_OMNIBOX_PEDAL_CLEAR_BROWSING_DATA_SUGGESTION_CONTENTS)) {
+  url_ = GURL("chrome://settings/clearBrowserData");
   // TODO(orinj): Move all trigger strings to files (maybe even per language).
   const auto triggers = {
       "how to clear browsing data on chrome",
@@ -85,7 +99,110 @@
   }
 }
 
-void OmniboxPedalClearBrowsingData::Execute(
-    OmniboxPedal::ExecutionContext& context) const {
-  OpenURL(context, GURL("chrome://settings/clearBrowserData"));
+// =============================================================================
+
+OmniboxPedalChangeSearchEngine::OmniboxPedalChangeSearchEngine()
+    : OmniboxPedal(OmniboxPedal::LabelStrings(
+          IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_HINT,
+          IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_HINT_SHORT,
+          IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_SUGGESTION_CONTENTS)) {
+  url_ = GURL("chrome://settings/searchEngines");
+  const auto triggers = {
+      "how to change search engine",
+      "how to change default search engine",
+      "how to change default search engine in chrome",
+      "how to change search engine on chrome",
+      "how to set google as default search engine on chrome",
+      "how to set google as default search engine in chrome",
+      "how to make google default search engine",
+      "how to change default search engine in google chrome",
+      "change search engine",
+      "change google search engine",
+      "change chrome searh engine",
+      "change default search engine in chrome",
+      "change search engine chrome",
+      "change default search chrome",
+      "change search chrome",
+      "switch chrome search engine",
+      "switch search engine",
+  };
+
+  for (const auto* trigger : triggers) {
+    triggers_.insert(base::ASCIIToUTF16(trigger));
+  }
+}
+
+// =============================================================================
+
+OmniboxPedalManagePasswords::OmniboxPedalManagePasswords()
+    : OmniboxPedal(OmniboxPedal::LabelStrings(
+          IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_HINT,
+          IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_HINT_SHORT,
+          IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_SUGGESTION_CONTENTS)) {
+  url_ = GURL("chrome://settings/passwords");
+  const auto triggers = {
+      "passwords",
+      "find my passwords",
+      "save passwords in chrome",
+      "view saved passwords",
+      "delete passwords",
+      "find saved passwords",
+      "where does chrome store passwords",
+      "how to see passwords in chrome",
+  };
+
+  for (const auto* trigger : triggers) {
+    triggers_.insert(base::ASCIIToUTF16(trigger));
+  }
+}
+
+// =============================================================================
+
+OmniboxPedalChangeHomePage::OmniboxPedalChangeHomePage()
+    : OmniboxPedal(OmniboxPedal::LabelStrings(
+          IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_HINT,
+          IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_HINT_SHORT,
+          IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_SUGGESTION_CONTENTS)) {
+  // TODO(orinj): Use better scoping for existing setting, or link to a new UI.
+  url_ = GURL("chrome://settings/?search=show+home+button");
+  const auto triggers = {
+      "how to change home page",
+      "how to change your home page",
+      "how do i change my home page",
+      "change home page google",
+      "home page chrome",
+      "change home chrome",
+      "change chrome home page",
+      "how to change home page on chrome",
+      "how to change home page in chrome",
+      "change chrome home",
+  };
+
+  for (const auto* trigger : triggers) {
+    triggers_.insert(base::ASCIIToUTF16(trigger));
+  }
+}
+
+// =============================================================================
+
+OmniboxPedalUpdateCreditCard::OmniboxPedalUpdateCreditCard()
+    : OmniboxPedal(OmniboxPedal::LabelStrings(
+          IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_HINT,
+          IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_HINT_SHORT,
+          IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_SUGGESTION_CONTENTS)) {
+  url_ = GURL("chrome://settings/autofill");
+  const auto triggers = {
+      "how to save credit card info on chrome",
+      "how to remove credit card from google chrome",
+      "remove google chrome credit cards",
+      "access google chrome credit cards",
+      "google chrome credit cards",
+      "chrome credit cards",
+      "get to chrome credit cards",
+      "chrome credit saved",
+  };
+
+  for (const auto* trigger : triggers) {
+    triggers_.insert(base::ASCIIToUTF16(trigger));
+  }
 }
diff --git a/components/omnibox/browser/omnibox_pedal.h b/components/omnibox/browser/omnibox_pedal.h
index d5842ff..af0e590 100644
--- a/components/omnibox/browser/omnibox_pedal.h
+++ b/components/omnibox/browser/omnibox_pedal.h
@@ -58,6 +58,12 @@
   // Provides read access to labels associated with this Pedal.
   const LabelStrings& GetLabelStrings() const;
 
+  // Returns true if this is purely a navigation Pedal with URL.
+  bool IsNavigation() const;
+
+  // For navigation Pedals, returns the destination URL.
+  const GURL& GetNavigationUrl() const;
+
   // These Should* methods can likely be eliminated when Pedal
   // suggestion mode is firmly established.
 
@@ -70,8 +76,9 @@
   // button; this method returns true if this Pedal presents a button.
   virtual bool ShouldPresentButton() const;
 
-  // Takes the action associated with this Pedal.
-  virtual void Execute(ExecutionContext& context) const = 0;
+  // Takes the action associated with this Pedal.  Non-navigation
+  // Pedals must override the default, but Navigation Pedals don't need to.
+  virtual void Execute(ExecutionContext& context) const;
 
   // Returns true if the preprocessed match suggestion text triggers
   // presentation of this Pedal.  This is not intended for general use,
@@ -84,12 +91,35 @@
 
   std::unordered_set<base::string16> triggers_;
   LabelStrings strings_;
+
+  // For navigation Pedals, this holds the destination URL; for action Pedals,
+  // this remains empty.
+  GURL url_;
 };
 
 class OmniboxPedalClearBrowsingData : public OmniboxPedal {
  public:
   OmniboxPedalClearBrowsingData();
-  void Execute(ExecutionContext& context) const override;
+};
+
+class OmniboxPedalChangeSearchEngine : public OmniboxPedal {
+ public:
+  OmniboxPedalChangeSearchEngine();
+};
+
+class OmniboxPedalManagePasswords : public OmniboxPedal {
+ public:
+  OmniboxPedalManagePasswords();
+};
+
+class OmniboxPedalChangeHomePage : public OmniboxPedal {
+ public:
+  OmniboxPedalChangeHomePage();
+};
+
+class OmniboxPedalUpdateCreditCard : public OmniboxPedal {
+ public:
+  OmniboxPedalUpdateCreditCard();
 };
 
 #endif  // COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_PEDAL_H_
diff --git a/components/omnibox/browser/omnibox_pedal_provider.cc b/components/omnibox/browser/omnibox_pedal_provider.cc
index edda980d..916855a1 100644
--- a/components/omnibox/browser/omnibox_pedal_provider.cc
+++ b/components/omnibox/browser/omnibox_pedal_provider.cc
@@ -31,4 +31,8 @@
 
 void OmniboxPedalProvider::RegisterPedals() {
   Add(new OmniboxPedalClearBrowsingData());
+  Add(new OmniboxPedalChangeSearchEngine());
+  Add(new OmniboxPedalManagePasswords());
+  Add(new OmniboxPedalChangeHomePage());
+  Add(new OmniboxPedalUpdateCreditCard());
 }
diff --git a/components/omnibox/browser/omnibox_popup_model.cc b/components/omnibox/browser/omnibox_popup_model.cc
index a84ce9b..bd35b43a1 100644
--- a/components/omnibox/browser/omnibox_popup_model.cc
+++ b/components/omnibox/browser/omnibox_popup_model.cc
@@ -199,7 +199,9 @@
   }
 
   if (state == TAB_SWITCH) {
-    DCHECK(match.has_tab_match);
+    // TODO(orinj): If in-suggestion Pedals are kept, refactor a bit
+    // so that button presence doesn't always assume tab switching use case.
+    DCHECK(match.has_tab_match || match.pedal);
     old_focused_url_ = current_destination;
   }
 
diff --git a/components/omnibox/browser/omnibox_view_unittest.cc b/components/omnibox/browser/omnibox_view_unittest.cc
index f251de4..cc24f10 100644
--- a/components/omnibox/browser/omnibox_view_unittest.cc
+++ b/components/omnibox/browser/omnibox_view_unittest.cc
@@ -164,7 +164,7 @@
 
   AutocompleteMatch match;
   match.destination_url = kUrl;
-  model()->SetCurrentMatch(match);
+  model()->SetCurrentMatchForTest(match);
 
   bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), 0,
                            base::ASCIIToUTF16("a bookmark"), kUrl);
@@ -190,7 +190,7 @@
   AutocompleteMatch match;
   match.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
   match.destination_url = kUrl;
-  model()->SetCurrentMatch(match);
+  model()->SetCurrentMatchForTest(match);
 
   view()->GetIcon(gfx::kFaviconSize, gfx::kPlaceholderColor, base::DoNothing());
 
diff --git a/components/omnibox/browser/test_omnibox_edit_model.cc b/components/omnibox/browser/test_omnibox_edit_model.cc
index a754c8bc..f228a58 100644
--- a/components/omnibox/browser/test_omnibox_edit_model.cc
+++ b/components/omnibox/browser/test_omnibox_edit_model.cc
@@ -12,18 +12,25 @@
     : OmniboxEditModel(view, controller, std::make_unique<TestOmniboxClient>()),
       popup_is_open_(false) {}
 
+TestOmniboxEditModel::~TestOmniboxEditModel() {}
+
 bool TestOmniboxEditModel::PopupIsOpen() const {
   return popup_is_open_;
 }
 
-AutocompleteMatch TestOmniboxEditModel::CurrentMatch(GURL*) const {
-  return current_match_;
+AutocompleteMatch TestOmniboxEditModel::CurrentMatch(
+    GURL* alternate_nav_url) const {
+  if (override_current_match_)
+    return *override_current_match_;
+
+  return OmniboxEditModel::CurrentMatch(alternate_nav_url);
 }
 
 void TestOmniboxEditModel::SetPopupIsOpen(bool open) {
   popup_is_open_ = open;
 }
 
-void TestOmniboxEditModel::SetCurrentMatch(const AutocompleteMatch& match) {
-  current_match_ = match;
+void TestOmniboxEditModel::SetCurrentMatchForTest(
+    const AutocompleteMatch& match) {
+  override_current_match_ = std::make_unique<AutocompleteMatch>(match);
 }
diff --git a/components/omnibox/browser/test_omnibox_edit_model.h b/components/omnibox/browser/test_omnibox_edit_model.h
index 98efbcc..ed252c9 100644
--- a/components/omnibox/browser/test_omnibox_edit_model.h
+++ b/components/omnibox/browser/test_omnibox_edit_model.h
@@ -5,23 +5,26 @@
 #ifndef COMPONENTS_OMNIBOX_BROWSER_TEST_OMNIBOX_EDIT_MODEL_H_
 #define COMPONENTS_OMNIBOX_BROWSER_TEST_OMNIBOX_EDIT_MODEL_H_
 
+#include <memory>
+
 #include "components/omnibox/browser/omnibox_edit_model.h"
 
 class TestOmniboxEditModel : public OmniboxEditModel {
  public:
   TestOmniboxEditModel(OmniboxView* view, OmniboxEditController* controller);
+  ~TestOmniboxEditModel() override;
 
   // OmniboxEditModel:
   bool PopupIsOpen() const override;
-  AutocompleteMatch CurrentMatch(GURL*) const override;
+  AutocompleteMatch CurrentMatch(GURL* alternate_nav_url) const override;
 
   void SetPopupIsOpen(bool open);
 
-  void SetCurrentMatch(const AutocompleteMatch& match);
+  void SetCurrentMatchForTest(const AutocompleteMatch& match);
 
  private:
   bool popup_is_open_;
-  AutocompleteMatch current_match_;
+  std::unique_ptr<AutocompleteMatch> override_current_match_;
 
   DISALLOW_COPY_AND_ASSIGN(TestOmniboxEditModel);
 };
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp
index ced57c39..50c6ac4b 100644
--- a/components/omnibox_strings.grdp
+++ b/components/omnibox_strings.grdp
@@ -85,6 +85,46 @@
     Clear Chrome's browsing history data
   </message>
 
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_HINT" desc="The button text contents to suggest pedal action, change search engine.">
+    Change Search Engine
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_HINT_SHORT" desc="The short one-word button text contents to suggest pedal action, change search engine.">
+    Change
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_SEARCH_ENGINE_SUGGESTION_CONTENTS" desc="The suggestion content text to suggest pedal action, change search engine.">
+    Change Search Engine settings in Chrome
+  </message>
+
+  <message name="IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_HINT" desc="The button text contents to suggest pedal action, manage passwords.">
+    Manage Passwords
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_HINT_SHORT" desc="The short one-word button text contents to suggest pedal action, change language.">
+    Manage
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_MANAGE_PASSWORDS_SUGGESTION_CONTENTS" desc="The suggestion content text to suggest pedal action, change language.">
+    Manage passwords in Chrome settings
+  </message>
+
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_HINT" desc="The button text contents to suggest pedal action, change home page.">
+    Change Home Page
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_HINT_SHORT" desc="The short one-word button text contents to suggest pedal action, change home page.">
+    Change
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_CHANGE_HOME_PAGE_SUGGESTION_CONTENTS" desc="The suggestion content text to suggest pedal action, change home page.">
+    Change home page in Chrome settings
+  </message>
+
+  <message name="IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_HINT" desc="The button text contents to suggest pedal action, update credit card.">
+    Update Credit Card
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_HINT_SHORT" desc="The short one-word button text contents to suggest pedal action, update credit card.">
+    Update
+  </message>
+  <message name="IDS_OMNIBOX_PEDAL_UPDATE_CREDIT_CARD_SUGGESTION_CONTENTS" desc="The suggestion content text to suggest pedal action, update credit card.">
+    Update credit card autofill info in Chrome settings
+  </message>
+
   <!-- Accessibility labels for autocomplete match types.
        These are parameterized on the text being completed into the omnibox.
   -->
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 8571f957..e9f2281 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -10473,13 +10473,13 @@
       'desc': '''Enables <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s restricted log in feature in G Suite and prevents users from changing this setting.
 
       If you define this setting, the user will only be able to access Google
-      Apps using accounts from the specified domains (note that this does not
-      work for gmail.com/googlemail.com).
+      Apps using accounts from the specified domains (note that to allow
+      gmail.com/googlemail.com accounts, you should add "consumer_accounts"
+      (without quotes) to the list of domains).
 
-      This setting will NOT prevent the user from loging in on a managed device
-      that requires Google authentication. The user will still be allowed to
-      sign in to accounts from other domains, but they will receive an error
-      when trying to use G Suite with those accounts.
+      This setting will prevent the user from logging in, and adding a Secondary
+      Account, on a managed device that requires Google authentication, if that
+      account does not belong to the aforementioned list of allowed domains.
 
       If you leave this setting empty/not-configured, the user will be able to
       access G Suite with any account.
diff --git a/components/search/BUILD.gn b/components/search/BUILD.gn
index b3896ba..d6d3db31 100644
--- a/components/search/BUILD.gn
+++ b/components/search/BUILD.gn
@@ -14,6 +14,19 @@
     "//components/search_engines",
     "//url",
   ]
+
+  if (!is_ios && !is_android) {
+    sources += [
+      "url_validity_checker.h",
+      "url_validity_checker_impl.cc",
+      "url_validity_checker_impl.h",
+    ]
+
+    deps += [
+      "//net",
+      "//services/network/public/cpp",
+    ]
+  }
 }
 
 source_set("unit_tests") {
@@ -28,4 +41,15 @@
     "//components/variations",
     "//testing/gtest",
   ]
+
+  if (!is_ios && !is_android) {
+    sources += [ "url_validity_checker_impl_unittest.cc" ]
+
+    deps += [
+      "//net",
+      "//net:test_support",
+      "//services/network:test_support",
+      "//testing/gmock",
+    ]
+  }
 }
diff --git a/components/search/DEPS b/components/search/DEPS
index d9bc813..8402eab 100644
--- a/components/search/DEPS
+++ b/components/search/DEPS
@@ -2,4 +2,7 @@
   "+components/google/core",
   "+components/search_engines",
   "+components/variations",
+  "+net",
+  "+services/network/public/cpp",
+  "+services/network/test",
 ]
diff --git a/components/search/url_validity_checker.h b/components/search/url_validity_checker.h
new file mode 100644
index 0000000..8ac60fa
--- /dev/null
+++ b/components/search/url_validity_checker.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 COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_H_
+#define COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_H_
+
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "url/gurl.h"
+
+// A standalone service that validates if the provided URL is able to resolve to
+// a valid page.
+class UrlValidityChecker {
+ public:
+  // The callback invoked when the request completes. Returns true if the
+  // response was |valid| and the request |duration|.
+  using UrlValidityCheckerCallback =
+      base::OnceCallback<void(bool valid, const base::TimeDelta& duration)>;
+
+  virtual ~UrlValidityChecker() = default;
+
+  // Creates a HEAD request to check if |url| resolves to an existing page.
+  // Returns true if the URL resolves and the request duration. Redirects (3xx)
+  // and 2xx response codes are considered as resolving.
+  virtual void DoesUrlResolve(
+      const GURL& url,
+      net::NetworkTrafficAnnotationTag traffic_annotation,
+      UrlValidityCheckerCallback callback) = 0;
+};
+
+#endif  // COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_H_
diff --git a/components/search/url_validity_checker_impl.cc b/components/search/url_validity_checker_impl.cc
new file mode 100644
index 0000000..accddbfa
--- /dev/null
+++ b/components/search/url_validity_checker_impl.cc
@@ -0,0 +1,86 @@
+// 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/search/url_validity_checker_impl.h"
+
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+
+// Stores the pending request and associated metadata. Deleted once the request
+// finishes.
+struct UrlValidityCheckerImpl::PendingRequest {
+  PendingRequest() = default;
+
+  GURL url;
+  base::TimeTicks time_created;
+  UrlValidityCheckerCallback callback;
+  std::unique_ptr<network::SimpleURLLoader> loader;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingRequest);
+};
+
+UrlValidityCheckerImpl::UrlValidityCheckerImpl(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    : url_loader_factory_(url_loader_factory) {}
+
+UrlValidityCheckerImpl::~UrlValidityCheckerImpl() = default;
+
+void UrlValidityCheckerImpl::DoesUrlResolve(
+    const GURL& url,
+    net::NetworkTrafficAnnotationTag traffic_annotation,
+    UrlValidityCheckerCallback callback) {
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  resource_request->method = "HEAD";
+  resource_request->allow_credentials = false;
+
+  auto request_iter = pending_requests_.emplace(pending_requests_.begin());
+  request_iter->url = url;
+  request_iter->time_created = NowTicks();
+  request_iter->callback = std::move(callback);
+  request_iter->loader = network::SimpleURLLoader::Create(
+      std::move(resource_request), traffic_annotation);
+  // Don't follow redirects to prevent leaking URL data to HTTP sites.
+  request_iter->loader->SetOnRedirectCallback(
+      base::BindRepeating(&UrlValidityCheckerImpl::OnSimpleLoaderRedirect,
+                          weak_ptr_factory_.GetWeakPtr(), request_iter));
+  request_iter->loader->DownloadToString(
+      url_loader_factory_.get(),
+      base::BindOnce(&UrlValidityCheckerImpl::OnSimpleLoaderComplete,
+                     weak_ptr_factory_.GetWeakPtr(), request_iter),
+      /*max_body_size=*/1);
+}
+
+void UrlValidityCheckerImpl::OnSimpleLoaderRedirect(
+    std::list<PendingRequest>::iterator request_iter,
+    const net::RedirectInfo& redirect_info,
+    const network::ResourceResponseHead& response_head,
+    std::vector<std::string>* to_be_removed_headers) {
+  // Assume the URL is valid if a redirect is returned.
+  OnSimpleLoaderHandler(request_iter, true);
+}
+
+void UrlValidityCheckerImpl::OnSimpleLoaderComplete(
+    std::list<PendingRequest>::iterator request_iter,
+    std::unique_ptr<std::string> response_body) {
+  // |response_body| is null for non-2xx responses.
+  OnSimpleLoaderHandler(request_iter, response_body.get() != nullptr);
+}
+
+void UrlValidityCheckerImpl::OnSimpleLoaderHandler(
+    std::list<PendingRequest>::iterator request_iter,
+    bool valid) {
+  base::TimeDelta elapsed_time = NowTicks() - request_iter->time_created;
+  std::move(request_iter->callback).Run(valid, elapsed_time);
+  pending_requests_.erase(request_iter);
+}
+
+base::TimeTicks UrlValidityCheckerImpl::NowTicks() const {
+  if (!time_ticks_for_testing_.is_null())
+    return time_ticks_for_testing_;
+  return base::TimeTicks::Now();
+}
diff --git a/components/search/url_validity_checker_impl.h b/components/search/url_validity_checker_impl.h
new file mode 100644
index 0000000..fea4b02
--- /dev/null
+++ b/components/search/url_validity_checker_impl.h
@@ -0,0 +1,73 @@
+// 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 COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_IMPL_H_
+#define COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_IMPL_H_
+
+#include <list>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "build/build_config.h"
+#include "components/search/url_validity_checker.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+namespace net {
+struct RedirectInfo;
+}  // namespace net
+
+namespace network {
+struct ResourceResponseHead;
+class SharedURLLoaderFactory;
+}  // namespace network
+
+class UrlValidityCheckerImpl : public UrlValidityChecker {
+ public:
+  explicit UrlValidityCheckerImpl(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+  ~UrlValidityCheckerImpl() override;
+
+  void DoesUrlResolve(const GURL& url,
+                      net::NetworkTrafficAnnotationTag traffic_annotation,
+                      UrlValidityCheckerCallback callback) override;
+
+  // Used for testing.
+  void SetTimeTicksForTesting(const base::TimeTicks& time_ticks) {
+    time_ticks_for_testing_ = time_ticks;
+  }
+
+ private:
+  struct PendingRequest;
+
+  void OnSimpleLoaderRedirect(
+      std::list<PendingRequest>::iterator request_iter,
+      const net::RedirectInfo& redirect_info,
+      const network::ResourceResponseHead& response_head,
+      std::vector<std::string>* to_be_removed_headers);
+  void OnSimpleLoaderComplete(std::list<PendingRequest>::iterator request_iter,
+                              std::unique_ptr<std::string> response_body);
+  // Called when the request from |DoesUrlResolve| finishes. Invokes the
+  // associated callback with the request status and duration.
+  void OnSimpleLoaderHandler(std::list<PendingRequest>::iterator request_iter,
+                             bool valid);
+
+  // Returns base::TimeTicks::Now() or the test TimeTicks if not null.
+  base::TimeTicks NowTicks() const;
+
+  // Stores any ongoing network requests. Once a request is completed, it is
+  // deleted from the list.
+  std::list<PendingRequest> pending_requests_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+  // Test time ticks used for testing.
+  base::TimeTicks time_ticks_for_testing_;
+
+  base::WeakPtrFactory<UrlValidityCheckerImpl> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(UrlValidityCheckerImpl);
+};
+
+#endif  // COMPONENTS_SEARCH_URL_VALIDITY_CHECKER_IMPL_H_
diff --git a/components/search/url_validity_checker_impl_unittest.cc b/components/search/url_validity_checker_impl_unittest.cc
new file mode 100644
index 0000000..4c154bf
--- /dev/null
+++ b/components/search/url_validity_checker_impl_unittest.cc
@@ -0,0 +1,146 @@
+// 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/search/url_validity_checker_impl.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+
+class UrlValidityCheckerImplTest : public testing::Test {
+ protected:
+  UrlValidityCheckerImplTest()
+      : test_shared_loader_factory_(
+            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+                &test_url_loader_factory_)),
+        url_checker_(test_shared_loader_factory_) {
+    // Start |clock_| at non-zero.
+    clock_.Advance(base::TimeDelta::FromSeconds(1));
+  }
+
+  ~UrlValidityCheckerImplTest() override {}
+
+  void SetUp() override {
+    test_shared_loader_factory_ =
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            url_loader_factory());
+    url_checker()->SetTimeTicksForTesting(clock_.NowTicks());
+  }
+
+  UrlValidityCheckerImpl* url_checker() { return &url_checker_; }
+
+  network::TestURLLoaderFactory* url_loader_factory() {
+    return &test_url_loader_factory_;
+  }
+
+  void AdvanceClock(const base::TimeDelta& delta) {
+    clock_.Advance(delta);
+    url_checker()->SetTimeTicksForTesting(clock_.NowTicks());
+  }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ private:
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+
+  base::SimpleTestTickClock clock_;
+  UrlValidityCheckerImpl url_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(UrlValidityCheckerImplTest);
+};
+
+TEST_F(UrlValidityCheckerImplTest, DoesUrlResolve_OnSuccess) {
+  const GURL kUrl("https://www.foo.com");
+  const int kTimeAdvance = 10;
+  base::TimeDelta expected_duration =
+      base::TimeDelta::FromSeconds(kTimeAdvance);
+
+  network::ResourceResponseHead response;
+  response.headers = new net::HttpResponseHeaders(
+      "HTTP/1.1 200 OK\nContent-type: text/html\n\n");
+  url_loader_factory()->SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        AdvanceClock(expected_duration);
+        url_loader_factory()->AddResponse(
+            request.url, response, std::string(),
+            network::URLLoaderCompletionStatus(net::OK));
+      }));
+  base::MockCallback<UrlValidityChecker::UrlValidityCheckerCallback>
+      callback_ok;
+  EXPECT_CALL(callback_ok, Run(true, expected_duration));
+
+  url_checker()->DoesUrlResolve(kUrl, TRAFFIC_ANNOTATION_FOR_TESTS,
+                                callback_ok.Get());
+  scoped_task_environment_.RunUntilIdle();
+
+  response.headers =
+      new net::HttpResponseHeaders("HTTP/1.1 204 No Content\r\n\r\n");
+  base::MockCallback<UrlValidityChecker::UrlValidityCheckerCallback>
+      callback_no_content;
+  EXPECT_CALL(callback_no_content, Run(true, expected_duration));
+
+  url_checker()->DoesUrlResolve(kUrl, TRAFFIC_ANNOTATION_FOR_TESTS,
+                                callback_no_content.Get());
+  scoped_task_environment_.RunUntilIdle();
+}
+
+TEST_F(UrlValidityCheckerImplTest, DoesUrlResolve_OnFailure) {
+  const GURL kUrl("https://www.foo.com");
+  const int kTimeAdvance = 20;
+  base::TimeDelta expected_duration =
+      base::TimeDelta::FromSeconds(kTimeAdvance);
+
+  url_loader_factory()->SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        AdvanceClock(expected_duration);
+        url_loader_factory()->AddResponse(
+            request.url, network::ResourceResponseHead(), std::string(),
+            network::URLLoaderCompletionStatus(net::ERR_FAILED));
+      }));
+  base::MockCallback<UrlValidityChecker::UrlValidityCheckerCallback> callback;
+  EXPECT_CALL(callback, Run(false, expected_duration));
+
+  url_checker()->DoesUrlResolve(kUrl, TRAFFIC_ANNOTATION_FOR_TESTS,
+                                callback.Get());
+  scoped_task_environment_.RunUntilIdle();
+}
+
+TEST_F(UrlValidityCheckerImplTest, DoesUrlResolve_OnRedirect) {
+  const GURL kUrl("https://www.foo.com");
+  const GURL kRedirectUrl("https://www.foo2.com");
+  const int kTimeAdvance = 30;
+  base::TimeDelta expected_duration =
+      base::TimeDelta::FromSeconds(kTimeAdvance);
+
+  net::RedirectInfo redirect_info;
+  redirect_info.status_code = 301;
+  redirect_info.new_url = kRedirectUrl;
+  network::TestURLLoaderFactory::Redirects redirects{
+      {redirect_info, network::ResourceResponseHead()}};
+  url_loader_factory()->SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        AdvanceClock(expected_duration);
+        url_loader_factory()->AddResponse(
+            request.url, network::ResourceResponseHead(), std::string(),
+            network::URLLoaderCompletionStatus(), redirects);
+      }));
+  base::MockCallback<UrlValidityChecker::UrlValidityCheckerCallback> callback;
+  EXPECT_CALL(callback, Run(true, expected_duration));
+
+  url_checker()->DoesUrlResolve(kUrl, TRAFFIC_ANNOTATION_FOR_TESTS,
+                                callback.Get());
+  scoped_task_environment_.RunUntilIdle();
+}
diff --git a/components/viz/host/gpu_client.cc b/components/viz/host/gpu_client.cc
index d526482..2dd5619f 100644
--- a/components/viz/host/gpu_client.cc
+++ b/components/viz/host/gpu_client.cc
@@ -76,6 +76,10 @@
   connection_error_handler_ = std::move(connection_error_handler);
 }
 
+base::WeakPtr<GpuClient> GpuClient::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 void GpuClient::OnEstablishGpuChannel(
     mojo::ScopedMessagePipeHandle channel_handle,
     const gpu::GPUInfo& gpu_info,
diff --git a/components/viz/host/gpu_client.h b/components/viz/host/gpu_client.h
index 53006ac..fdfcb9fd 100644
--- a/components/viz/host/gpu_client.h
+++ b/components/viz/host/gpu_client.h
@@ -37,6 +37,8 @@
   void SetConnectionErrorHandler(
       ConnectionErrorHandlerClosure connection_error_handler);
 
+  base::WeakPtr<GpuClient> GetWeakPtr();
+
   // ws::mojom::GpuMemoryBufferFactory overrides:
   void CreateGpuMemoryBuffer(
       gfx::GpuMemoryBufferId id,
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 6328c62..7750b5a3 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2093,8 +2093,6 @@
       "renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h",
       "renderer_host/pepper/quota_reservation.cc",
       "renderer_host/pepper/quota_reservation.h",
-      "renderer_host/pepper/ssl_context_helper.cc",
-      "renderer_host/pepper/ssl_context_helper.h",
       "renderer_host/plugin_registry_impl.cc",
       "renderer_host/plugin_registry_impl.h",
     ]
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 5635ed5..ec60dd6b 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -2243,6 +2243,7 @@
     return @"";
   } else if (owner_->HasIntAttribute(ax::mojom::IntAttribute::kCheckedState) ||
              [role isEqualToString:NSAccessibilityRadioButtonRole]) {
+    // On Mac, tabs are exposed as radio buttons, and are treated as checkable.
     int value;
     const auto checkedState = static_cast<ax::mojom::CheckedState>(
         owner_->GetIntAttribute(ax::mojom::IntAttribute::kCheckedState));
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index e324470..1ba2c301 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -776,7 +776,7 @@
   std::string protocol = info.alpn_negotiated_protocol;
   if (protocol.empty() || protocol == "unknown") {
     if (info.was_fetched_via_spdy) {
-      protocol = "spdy";
+      protocol = "h2";
     } else if (url.SchemeIsHTTPOrHTTPS()) {
       protocol = "http";
       if (info.headers->GetHttpVersion() == net::HttpVersion(0, 9))
diff --git a/content/browser/frame_host/render_widget_host_view_guest.cc b/content/browser/frame_host/render_widget_host_view_guest.cc
index 66f1270..96c9cd1 100644
--- a/content/browser/frame_host/render_widget_host_view_guest.cc
+++ b/content/browser/frame_host/render_widget_host_view_guest.cc
@@ -338,6 +338,18 @@
   return platform_view_->GetSelectedText();
 }
 
+base::string16 RenderWidgetHostViewGuest::GetSurroundingText() {
+  return platform_view_->GetSurroundingText();
+}
+
+gfx::Range RenderWidgetHostViewGuest::GetSelectedRange() {
+  return platform_view_->GetSelectedRange();
+}
+
+size_t RenderWidgetHostViewGuest::GetOffsetForSurroundingText() {
+  return platform_view_->GetOffsetForSurroundingText();
+}
+
 void RenderWidgetHostViewGuest::SetNeedsBeginFrames(bool needs_begin_frames) {
   if (platform_view_)
     platform_view_->SetNeedsBeginFrames(needs_begin_frames);
diff --git a/content/browser/frame_host/render_widget_host_view_guest.h b/content/browser/frame_host/render_widget_host_view_guest.h
index b3353f56..dae838f 100644
--- a/content/browser/frame_host/render_widget_host_view_guest.h
+++ b/content/browser/frame_host/render_widget_host_view_guest.h
@@ -80,6 +80,9 @@
   gfx::Rect GetBoundsInRootWindow() override;
   gfx::Size GetCompositorViewportPixelSize() const override;
   base::string16 GetSelectedText() override;
+  base::string16 GetSurroundingText() override;
+  gfx::Range GetSelectedRange() override;
+  size_t GetOffsetForSurroundingText() override;
   void SetNeedsBeginFrames(bool needs_begin_frames) override;
   TouchSelectionControllerClientManager*
   GetTouchSelectionControllerClientManager() override;
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index cb77aba..d38c982 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -826,7 +826,8 @@
   // This is the |fallback_callback| passed to
   // NavigationLoaderInterceptor::MaybeCreateLoader. It allows an interceptor
   // to initially elect to handle a request, and later decide to fallback to
-  // the default behavior. This is needed for service worker network fallback.
+  // the default behavior. This is needed for service worker network fallback
+  // and signed exchange (SXG) fallback redirect.
   void FallbackToNonInterceptedRequest(bool reset_subresource_loader_params) {
     if (reset_subresource_loader_params)
       subresource_loader_params_.reset();
@@ -835,21 +836,35 @@
     // Cancel state on ResourceDispatcherHostImpl so it doesn't complain about
     // reusing the request_id after redirects. Otherwise the following sequence
     // can happen:
-    // RDHI Start(request_id) -> Redirect -> SW interception -> SW fallback to
-    // network -> RDHI Start(request_id).
+    // case 1. RDHI Start(request_id) -> Redirect -> SW interception -> SW
+    //         fallback to network -> RDHI Start(request_id).
+    // case 2. RDHI Start(request_id) -> SXG interception -> SXG fallback to
+    //         network -> RDHI Start(request_id).
     if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
       DCHECK(ResourceDispatcherHostImpl::Get());
       ResourceDispatcherHostImpl::Get()->CancelRequest(
           global_request_id_.child_id, global_request_id_.request_id);
     }
 
-    // |url_loader_| is using the factory for the interceptor that decided to
-    // fallback, so restart it with the non-interceptor factory.
-    DCHECK(url_loader_);
     uint32_t options = network::mojom::kURLLoadOptionNone;
     scoped_refptr<network::SharedURLLoaderFactory> factory =
         PrepareForNonInterceptedRequest(&options);
-    url_loader_->RestartWithFactory(std::move(factory), options);
+    if (url_loader_) {
+      // |url_loader_| is using the factory for the interceptor that decided to
+      // fallback, so restart it with the non-interceptor factory.
+      url_loader_->RestartWithFactory(std::move(factory), options);
+    } else {
+      // In SXG cases we don't have |url_loader_| because it was reset when the
+      // SXG interceptor intercepted the response in
+      // MaybeCreateLoaderForResponse.
+      DCHECK(response_loader_binding_);
+      response_loader_binding_.Close();
+      url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
+          std::move(factory), CreateURLLoaderThrottles(), frame_tree_node_id_,
+          global_request_id_.request_id, options, resource_request_.get(),
+          this /* client */, kNavigationUrlLoaderTrafficAnnotation,
+          base::ThreadTaskRunnerHandle::Get());
+    }
   }
 
   scoped_refptr<network::SharedURLLoaderFactory>
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index 3553320c..7c6394853 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -9,6 +9,7 @@
 #include <tuple>
 #include <utility>
 
+#include "base/containers/adapters.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -856,15 +857,19 @@
   // We should go backwards through the stack so the top of the stack is
   // always shown first in the list.
   base::ListValue stack_data;
-  for (auto iter = stack.rbegin(); iter != stack.rend(); ++iter) {
-    int request_id = (*iter)->request_id;
+  for (const auto& session : base::Reversed(stack)) {
+    if (!session->request_id.has_value())
+      continue;
+
+    std::string id_string = session->request_id.value().ToString();
     base::DictionaryValue media_session_data;
-    media_session_data.SetKey(kAudioFocusIdKey, base::Value(request_id));
+    media_session_data.SetKey(kAudioFocusIdKey, base::Value(id_string));
     stack_data.GetList().push_back(std::move(media_session_data));
 
     audio_focus_debug_ptr_->GetDebugInfoForRequest(
-        request_id, base::BindOnce(&MediaInternals::DidGetAudioFocusDebugInfo,
-                                   base::Unretained(this), request_id));
+        session->request_id.value(),
+        base::BindOnce(&MediaInternals::DidGetAudioFocusDebugInfo,
+                       base::Unretained(this), id_string));
   }
 
   audio_focus_data_.SetKey(kAudioFocusSessionsKey, std::move(stack_data));
@@ -874,7 +879,7 @@
 }
 
 void MediaInternals::DidGetAudioFocusDebugInfo(
-    int id,
+    const std::string& id,
     media_session::mojom::MediaSessionDebugInfoPtr info) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -887,7 +892,7 @@
 
   bool updated = false;
   for (auto& session : sessions_list->GetList()) {
-    if (session.FindKey(kAudioFocusIdKey)->GetInt() != id)
+    if (session.FindKey(kAudioFocusIdKey)->GetString() != id)
       continue;
 
     session.SetKey("name", base::Value(info->name));
diff --git a/content/browser/media/media_internals.h b/content/browser/media/media_internals.h
index 3e42a724..0ad0fc2a 100644
--- a/content/browser/media/media_internals.h
+++ b/content/browser/media/media_internals.h
@@ -133,7 +133,7 @@
   // Called when we receive audio focus debug info to display for a single
   // audio focus request.
   void DidGetAudioFocusDebugInfo(
-      int id,
+      const std::string& id,
       media_session::mojom::MediaSessionDebugInfoPtr info);
 
   // Sends |update| to each registered UpdateCallback.  Safe to call from any
diff --git a/content/browser/media/media_internals_unittest.cc b/content/browser/media/media_internals_unittest.cc
index cd3159c..32a85ae 100644
--- a/content/browser/media/media_internals_unittest.cc
+++ b/content/browser/media/media_internals_unittest.cc
@@ -28,6 +28,7 @@
 #include "media/base/media_switches.h"
 #include "services/media_session/public/cpp/switches.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
+#include "services/media_session/public/mojom/constants.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/size.h"
@@ -36,6 +37,8 @@
 const int kTestComponentID = 0;
 const char kTestDeviceID[] = "test-device-id";
 
+using media_session::mojom::AudioFocusRequestStatePtr;
+
 // This class encapsulates a MediaInternals reference. It also has some useful
 // methods to receive a callback, deserialize its associated data and expect
 // integer/string values.
@@ -327,7 +330,9 @@
     browser_context_.reset(new TestBrowserContext());
     run_loop_ = std::make_unique<base::RunLoop>();
 
-    run_loop_ = std::make_unique<base::RunLoop>();
+    content::ServiceManagerConnection::GetForProcess()
+        ->GetConnector()
+        ->BindInterface(media_session::mojom::kServiceName, &audio_focus_ptr_);
 
     content::MediaInternals::GetInstance()->AddUpdateCallback(update_cb_);
   }
@@ -394,6 +399,20 @@
     run_loop_->Run();
   }
 
+  std::string GetRequestIdForTopFocusRequest() {
+    std::string result;
+
+    audio_focus_ptr_->GetFocusRequests(base::BindOnce(
+        [](std::string* out, std::vector<AudioFocusRequestStatePtr> requests) {
+          DCHECK(!requests.empty());
+          *out = requests.back()->request_id.value().ToString();
+        },
+        &result));
+
+    audio_focus_ptr_.FlushForTesting();
+    return result;
+  }
+
   MediaInternals::UpdateCallback update_cb_;
 
  private:
@@ -403,6 +422,8 @@
   base::Lock lock_;
   std::unique_ptr<base::RunLoop> run_loop_;
   std::unique_ptr<TestBrowserContext> browser_context_;
+
+  media_session::mojom::AudioFocusManagerPtr audio_focus_ptr_;
 };
 
 TEST_F(MediaInternalsAudioFocusTest, AudioFocusStateIsUpdated) {
@@ -413,10 +434,13 @@
   media_session1->RequestSystemAudioFocus(AudioFocusType::kGain);
   WaitForCallbackCount(1);
 
+  // Get the |request_id| for the top session.
+  std::string request_id1 = GetRequestIdForTopFocusRequest();
+
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session;
-    expected_session.SetKey("id", base::Value(0));
+    expected_session.SetKey("id", base::Value(request_id1));
     expected_session.SetKey("name", GetAddressAsValue(media_session1));
     expected_session.SetKey("owner", base::Value(kTestTitle1));
     expected_session.SetKey("state", base::Value("Active"));
@@ -434,16 +458,20 @@
       AudioFocusType::kGainTransientMayDuck);
   WaitForCallbackCount(2);
 
+  // Get the |request_id| for the top session.
+  std::string request_id2 = GetRequestIdForTopFocusRequest();
+  DCHECK_NE(request_id1, request_id2);
+
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session1;
-    expected_session1.SetKey("id", base::Value(1));
+    expected_session1.SetKey("id", base::Value(request_id2));
     expected_session1.SetKey("name", GetAddressAsValue(media_session2));
     expected_session1.SetKey("owner", base::Value(kTestTitle2));
     expected_session1.SetKey("state", base::Value("Active"));
 
     base::DictionaryValue expected_session2;
-    expected_session2.SetKey("id", base::Value(0));
+    expected_session2.SetKey("id", base::Value(request_id1));
     expected_session2.SetKey("name", GetAddressAsValue(media_session1));
     expected_session2.SetKey("owner", base::Value(kTestTitle1));
     expected_session2.SetKey("state", base::Value("Active Ducked"));
@@ -461,7 +489,7 @@
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session;
-    expected_session.SetKey("id", base::Value(0));
+    expected_session.SetKey("id", base::Value(request_id1));
     expected_session.SetKey("name", GetAddressAsValue(media_session1));
     expected_session.SetKey("owner", base::Value(kTestTitle1));
     expected_session.SetKey("state", base::Value("Active"));
diff --git a/content/browser/renderer_host/clipboard_host_impl.cc b/content/browser/renderer_host/clipboard_host_impl.cc
index 60928166..0b8dc15 100644
--- a/content/browser/renderer_host/clipboard_host_impl.cc
+++ b/content/browser/renderer_host/clipboard_host_impl.cc
@@ -6,11 +6,13 @@
 
 #include <utility>
 
+#include "base/location.h"
 #include "base/macros.h"
 #include "base/pickle.h"
+#include "base/sequenced_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/clipboard/clipboard.h"
@@ -20,15 +22,23 @@
 
 namespace content {
 
-ClipboardHostImpl::ClipboardHostImpl()
-    : clipboard_(ui::Clipboard::GetForCurrentThread()),
+ClipboardHostImpl::ClipboardHostImpl(blink::mojom::ClipboardHostRequest request)
+    : binding_(this, std::move(request)),
+      clipboard_(ui::Clipboard::GetForCurrentThread()),
       clipboard_writer_(
           new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)) {}
 
 void ClipboardHostImpl::Create(blink::mojom::ClipboardHostRequest request) {
-  mojo::MakeStrongBinding(
-      base::WrapUnique<ClipboardHostImpl>(new ClipboardHostImpl()),
-      std::move(request));
+  // Clipboard implementations do interesting things, like run nested message
+  // loops. Since StrongBinding<T> synchronously destroys on failure, that can
+  // result in some unfortunate use-after-frees after the nested message loops
+  // exit.
+  auto* host = new ClipboardHostImpl(std::move(request));
+  host->binding_.set_connection_error_handler(base::BindOnce(
+      [](ClipboardHostImpl* host) {
+        base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, host);
+      },
+      host));
 }
 
 ClipboardHostImpl::~ClipboardHostImpl() {
diff --git a/content/browser/renderer_host/clipboard_host_impl.h b/content/browser/renderer_host/clipboard_host_impl.h
index f9c6a3d..82a0d6b 100644
--- a/content/browser/renderer_host/clipboard_host_impl.h
+++ b/content/browser/renderer_host/clipboard_host_impl.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "third_party/blink/public/mojom/clipboard/clipboard.mojom.h"
 #include "ui/base/clipboard/clipboard.h"
 
@@ -33,7 +34,7 @@
  private:
   friend class ClipboardHostImplTest;
 
-  ClipboardHostImpl();
+  explicit ClipboardHostImpl(blink::mojom::ClipboardHostRequest request);
 
   // content::mojom::ClipboardHost
   void GetSequenceNumber(ui::ClipboardType clipboard_type,
@@ -73,7 +74,8 @@
   void WriteStringToFindPboard(const base::string16& text) override;
 #endif
 
-  ui::Clipboard* clipboard_;  // Not owned
+  mojo::Binding<blink::mojom::ClipboardHost> binding_;
+  ui::Clipboard* const clipboard_;  // Not owned
   std::unique_ptr<ui::ScopedClipboardWriter> clipboard_writer_;
 };
 
diff --git a/content/browser/renderer_host/clipboard_host_impl_unittest.cc b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
index a3a0e92..50b866d 100644
--- a/content/browser/renderer_host/clipboard_host_impl_unittest.cc
+++ b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
@@ -7,8 +7,12 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "base/callback_helpers.h"
 #include "base/run_loop.h"
+#include "base/strings/string16.h"
+#include "base/test/bind_test_util.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/test/test_clipboard.h"
@@ -19,26 +23,21 @@
 class ClipboardHostImplTest : public ::testing::Test {
  protected:
   ClipboardHostImplTest()
-      : clipboard_(ui::TestClipboard::CreateForCurrentThread()) {}
+      : clipboard_(ui::TestClipboard::CreateForCurrentThread()) {
+    ClipboardHostImpl::Create(mojo::MakeRequest(&ptr_));
+  }
 
   ~ClipboardHostImplTest() override {
     ui::Clipboard::DestroyClipboardForCurrentThread();
   }
 
-  void CallWriteImage(const SkBitmap& bitmap) {
-    host_.WriteImage(ui::CLIPBOARD_TYPE_COPY_PASTE, bitmap);
-  }
+  blink::mojom::ClipboardHostPtr& mojo_clipboard() { return ptr_; }
 
-  void CallCommitWrite() {
-    host_.CommitWrite(ui::CLIPBOARD_TYPE_COPY_PASTE);
-    base::RunLoop().RunUntilIdle();
-  }
-
-  ui::Clipboard* clipboard() { return clipboard_; }
+  ui::Clipboard* system_clipboard() { return clipboard_; }
 
  private:
   const TestBrowserThreadBundle thread_bundle_;
-  ClipboardHostImpl host_;
+  blink::mojom::ClipboardHostPtr ptr_;
   ui::Clipboard* const clipboard_;
 };
 
@@ -47,20 +46,48 @@
   SkBitmap bitmap;
   bitmap.allocN32Pixels(3, 2);
   bitmap.eraseARGB(255, 0, 255, 0);
-  CallWriteImage(bitmap);
+  mojo_clipboard()->WriteImage(ui::CLIPBOARD_TYPE_COPY_PASTE, bitmap);
   uint64_t sequence_number =
-      clipboard()->GetSequenceNumber(ui::CLIPBOARD_TYPE_COPY_PASTE);
-  CallCommitWrite();
+      system_clipboard()->GetSequenceNumber(ui::CLIPBOARD_TYPE_COPY_PASTE);
+  mojo_clipboard()->CommitWrite(ui::CLIPBOARD_TYPE_COPY_PASTE);
+  base::RunLoop().RunUntilIdle();
 
-  EXPECT_NE(sequence_number,
-            clipboard()->GetSequenceNumber(ui::CLIPBOARD_TYPE_COPY_PASTE));
-  EXPECT_FALSE(clipboard()->IsFormatAvailable(
+  EXPECT_NE(sequence_number, system_clipboard()->GetSequenceNumber(
+                                 ui::CLIPBOARD_TYPE_COPY_PASTE));
+  EXPECT_FALSE(system_clipboard()->IsFormatAvailable(
       ui::Clipboard::GetPlainTextFormatType(), ui::CLIPBOARD_TYPE_COPY_PASTE));
-  EXPECT_TRUE(clipboard()->IsFormatAvailable(
+  EXPECT_TRUE(system_clipboard()->IsFormatAvailable(
       ui::Clipboard::GetBitmapFormatType(), ui::CLIPBOARD_TYPE_COPY_PASTE));
 
-  SkBitmap actual = clipboard()->ReadImage(ui::CLIPBOARD_TYPE_COPY_PASTE);
+  SkBitmap actual =
+      system_clipboard()->ReadImage(ui::CLIPBOARD_TYPE_COPY_PASTE);
   EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, actual));
 }
 
+TEST_F(ClipboardHostImplTest, ReentrancyInSyncCall) {
+  // Due to the nature of this test, it's somewhat racy. On some platforms
+  // (currently Linux), reading the clipboard requires running a nested message
+  // loop. During that time, it's possible to send a bad message that causes the
+  // message pipe to be closed. Make sure ClipboardHostImpl doesn't UaF |this|
+  // after exiting the nested message loop.
+
+  // ReadText() is a sync method, so normally, one wouldn't call this method
+  // directly. These are not normal times though...
+  base::RunLoop run_loop;
+  mojo_clipboard()->ReadText(
+      ui::CLIPBOARD_TYPE_COPY_PASTE,
+      base::BindLambdaForTesting(
+          [&run_loop](const base::string16& ignored) { run_loop.Quit(); }));
+
+  // Now purposely write a raw message which (hopefully) won't deserialize to
+  // anything valid. The receiver side should still be in the midst of
+  // dispatching ReadText() when Mojo attempts to deserialize this message,
+  // which should cause a validation failure that signals a connection error.
+  mojo::WriteMessageRaw(mojo_clipboard().internal_state()->handle(), "moo", 3,
+                        nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+  run_loop.Run();
+
+  EXPECT_TRUE(mojo_clipboard().encountered_error());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index f761c11a..7190d3c 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -112,11 +112,8 @@
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override;
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override {}
+  void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
+  }
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override {}
   void RequestNewLayerTreeFrameSink() override;
diff --git a/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc b/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc
index 09c612c..f4bef21 100644
--- a/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc
+++ b/content/browser/renderer_host/pepper/browser_ppapi_host_impl.cc
@@ -57,8 +57,7 @@
       plugin_path_(plugin_path),
       profile_data_directory_(profile_data_directory),
       in_process_(in_process),
-      external_plugin_(external_plugin),
-      ssl_context_helper_(new SSLContextHelper()) {
+      external_plugin_(external_plugin) {
   message_filter_ = new HostMessageFilter(ppapi_host_.get(), this);
   ppapi_host_->AddHostFactoryFilter(std::unique_ptr<ppapi::host::HostFactory>(
       new ContentBrowserPepperHostFactory(this)));
diff --git a/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h b/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h
index 8aeaed4..e892474 100644
--- a/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h
+++ b/content/browser/renderer_host/pepper/browser_ppapi_host_impl.h
@@ -18,7 +18,6 @@
 #include "base/observer_list.h"
 #include "base/process/process.h"
 #include "content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h"
-#include "content/browser/renderer_host/pepper/ssl_context_helper.h"
 #include "content/common/content_export.h"
 #include "content/common/pepper_renderer_instance_data.h"
 #include "content/public/browser/browser_ppapi_host.h"
@@ -100,10 +99,6 @@
     return message_filter_;
   }
 
-  const scoped_refptr<SSLContextHelper>& ssl_context_helper() const {
-    return ssl_context_helper_;
-  }
-
  private:
   friend class BrowserPpapiHostTest;
 
@@ -153,8 +148,6 @@
   // BrowserPpapiHost::CreateExternalPluginProcess.
   bool external_plugin_;
 
-  scoped_refptr<SSLContextHelper> ssl_context_helper_;
-
   // Tracks all PP_Instances in this plugin and associated data.
   std::unordered_map<PP_Instance, std::unique_ptr<InstanceData>> instance_map_;
 
diff --git a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
index 782cd8b7..69eb47a8 100644
--- a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
+++ b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
@@ -246,12 +246,18 @@
 ContentBrowserPepperHostFactory::CreateAcceptedTCPSocket(
     PP_Instance instance,
     ppapi::TCPSocketVersion version,
-    std::unique_ptr<net::TCPSocket> socket) {
+    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+    network::mojom::SocketObserverRequest socket_observer_request,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
   if (!CanCreateSocket())
     return std::unique_ptr<ppapi::host::ResourceHost>();
-  scoped_refptr<ppapi::host::ResourceMessageFilter> tcp_socket(
-      new PepperTCPSocketMessageFilter(host_, instance, version,
-                                       std::move(socket)));
+  scoped_refptr<PepperTCPSocketMessageFilter> tcp_socket(
+      base::MakeRefCounted<PepperTCPSocketMessageFilter>(
+          nullptr /* factory */, host_, instance, version));
+  tcp_socket->SetConnectedSocket(
+      std::move(connected_socket), std::move(socket_observer_request),
+      std::move(receive_stream), std::move(send_stream));
   return std::unique_ptr<ppapi::host::ResourceHost>(
       new ppapi::host::MessageFilterHost(host_->GetPpapiHost(), instance, 0,
                                          tcp_socket));
diff --git a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h
index d9b3a55..4c75513 100644
--- a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h
+++ b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h
@@ -10,10 +10,11 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "net/socket/tcp_socket.h"
+#include "mojo/public/cpp/system/data_pipe.h"
 #include "ppapi/c/pp_resource.h"
 #include "ppapi/host/host_factory.h"
 #include "ppapi/shared_impl/ppb_tcp_socket_shared.h"
+#include "services/network/public/mojom/tcp_socket.mojom.h"
 
 namespace ppapi {
 class PpapiPermissions;
@@ -41,7 +42,10 @@
   std::unique_ptr<ppapi::host::ResourceHost> CreateAcceptedTCPSocket(
       PP_Instance instance,
       ppapi::TCPSocketVersion version,
-      std::unique_ptr<net::TCPSocket> socket);
+      network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream);
 
  private:
   std::unique_ptr<ppapi::host::ResourceHost> CreateNewTCPSocket(
diff --git a/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc b/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc
index 2373f97..b74462f 100644
--- a/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc
+++ b/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.cc
@@ -17,13 +17,16 @@
 #include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/common/socket_permission_request.h"
+#include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
-#include "net/log/net_log_source.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "ppapi/c/pp_errors.h"
-#include "ppapi/c/private/ppb_net_address_private.h"
 #include "ppapi/host/dispatch_host_message.h"
 #include "ppapi/host/error_conversion.h"
 #include "ppapi/host/ppapi_host.h"
@@ -32,6 +35,7 @@
 #include "ppapi/shared_impl/api_id.h"
 #include "ppapi/shared_impl/ppb_tcp_socket_shared.h"
 #include "ppapi/shared_impl/private/net_address_private_impl.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/network/firewall_hole.h"
@@ -42,12 +46,15 @@
 
 namespace {
 
-size_t g_num_instances = 0;
+static size_t g_num_instances = 0;
 
 }  // namespace
 
 namespace content {
 
+network::mojom::NetworkContext*
+    PepperTCPServerSocketMessageFilter::network_context_for_testing = nullptr;
+
 PepperTCPServerSocketMessageFilter::PepperTCPServerSocketMessageFilter(
     ContentBrowserPepperHostFactory* factory,
     BrowserPpapiHostImpl* host,
@@ -57,6 +64,7 @@
       factory_(factory),
       instance_(instance),
       state_(STATE_BEFORE_LISTENING),
+      bound_addr_(NetAddressPrivateImpl::kInvalidNetAddress),
       external_plugin_(host->external_plugin()),
       private_api_(private_api),
       render_process_id_(0),
@@ -75,19 +83,35 @@
 }
 
 // static
+void PepperTCPServerSocketMessageFilter::SetNetworkContextForTesting(
+    network::mojom::NetworkContext* network_context) {
+  network_context_for_testing = network_context;
+}
+
+// static
 size_t PepperTCPServerSocketMessageFilter::GetNumInstances() {
   return g_num_instances;
 }
 
+void PepperTCPServerSocketMessageFilter::OnFilterDestroyed() {
+  ResourceMessageFilter::OnFilterDestroyed();
+  // Need to close all mojo pipes the socket on the UI thread. Calling Close()
+  // also ensures that future messages will be ignored, so the mojo pipes won't
+  // be re-created, so after Close() runs, |this| can be safely deleted on the
+  // IO thread.
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&PepperTCPServerSocketMessageFilter::Close, this));
+}
+
 scoped_refptr<base::TaskRunner>
 PepperTCPServerSocketMessageFilter::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
   switch (message.type()) {
     case PpapiHostMsg_TCPServerSocket_Listen::ID:
-      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
     case PpapiHostMsg_TCPServerSocket_Accept::ID:
     case PpapiHostMsg_TCPServerSocket_StopListening::ID:
-      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
+      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
   }
   return nullptr;
 }
@@ -124,16 +148,45 @@
     return PP_ERROR_NOACCESS;
   }
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPServerSocketMessageFilter::DoListen, this,
-                     context->MakeReplyMessageContext(), addr, backlog));
+  net::IPAddressBytes address;
+  uint16_t port;
+
+  if (state_ != STATE_BEFORE_LISTENING ||
+      !NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
+    Close();
+    return PP_ERROR_FAILED;
+  }
+
+  network::mojom::NetworkContext* network_context = network_context_for_testing;
+  if (!network_context) {
+    RenderProcessHost* render_process_host =
+        RenderProcessHost::FromID(render_process_id_);
+    network_context =
+        render_process_host->GetStoragePartition()->GetNetworkContext();
+    if (!network_context)
+      return PP_ERROR_FAILED;
+  }
+
+  state_ = STATE_LISTEN_IN_PROGRESS;
+
+  ppapi::host::ReplyMessageContext reply_context =
+      context->MakeReplyMessageContext();
+
+  network_context->CreateTCPServerSocket(
+      net::IPEndPoint(net::IPAddress(address), port), backlog,
+      pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
+      mojo::MakeRequest(&socket_),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPServerSocketMessageFilter::OnListenCompleted,
+                         base::Unretained(this), reply_context),
+          net::ERR_FAILED, base::nullopt /* local_addr_out */));
+
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPServerSocketMessageFilter::OnMsgAccept(
     const ppapi::host::HostMessageContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(context);
 
   if (state_ != STATE_LISTENING)
@@ -142,190 +195,192 @@
   state_ = STATE_ACCEPT_IN_PROGRESS;
   ppapi::host::ReplyMessageContext reply_context(
       context->MakeReplyMessageContext());
-  int net_result = socket_->Accept(
-      &accepted_socket_, &accepted_address_,
-      base::Bind(&PepperTCPServerSocketMessageFilter::OnAcceptCompleted,
-                 base::Unretained(this), reply_context));
-  if (net_result != net::ERR_IO_PENDING)
-    OnAcceptCompleted(reply_context, net_result);
+
+  network::mojom::SocketObserverPtr socket_observer;
+  network::mojom::SocketObserverRequest socket_observer_request =
+      mojo::MakeRequest(&socket_observer);
+  socket_->Accept(
+      std::move(socket_observer),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPServerSocketMessageFilter::OnAcceptCompleted,
+                         base::Unretained(this), reply_context,
+                         std::move(socket_observer_request)),
+          net::ERR_FAILED, base::nullopt /* remote_addr */,
+          network::mojom::TCPConnectedSocketPtr(),
+          mojo::ScopedDataPipeConsumerHandle(),
+          mojo::ScopedDataPipeProducerHandle()));
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPServerSocketMessageFilter::OnMsgStopListening(
     const ppapi::host::HostMessageContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(context);
 
-  state_ = STATE_CLOSED;
-  socket_.reset();
-#if defined(OS_CHROMEOS)
-  firewall_hole_.reset();
-#endif  // defined(OS_CHROMEOS)
+  Close();
   return PP_OK;
 }
 
-void PepperTCPServerSocketMessageFilter::DoListen(
-    const ppapi::host::ReplyMessageContext& context,
-    const PP_NetAddress_Private& addr,
-    int32_t backlog) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  net::IPAddressBytes address;
-  uint16_t port;
-  if (state_ != STATE_BEFORE_LISTENING ||
-      !NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
-    SendListenError(context, PP_ERROR_FAILED);
-    state_ = STATE_CLOSED;
-    return;
-  }
-
-  state_ = STATE_LISTEN_IN_PROGRESS;
-
-  socket_.reset(new net::TCPSocket(nullptr, nullptr, net::NetLogSource()));
-  int net_result = net::OK;
-  do {
-    net::IPEndPoint ip_end_point(net::IPAddress(address), port);
-    net_result = socket_->Open(ip_end_point.GetFamily());
-    if (net_result != net::OK)
-      break;
-    net_result = socket_->SetDefaultOptionsForServer();
-    if (net_result != net::OK)
-      break;
-    net_result = socket_->Bind(ip_end_point);
-    if (net_result != net::OK)
-      break;
-    net_result = socket_->Listen(backlog);
-  } while (false);
-
-  if (net_result != net::ERR_IO_PENDING) {
-#if defined(OS_CHROMEOS)
-    OpenFirewallHole(context, net_result);
-#else
-    OnListenCompleted(context, net_result);
-#endif
-  }
-}
-
 void PepperTCPServerSocketMessageFilter::OnListenCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  if (state_ != STATE_LISTEN_IN_PROGRESS) {
+    int net_result,
+    const base::Optional<net::IPEndPoint>& local_addr) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // Exit early if this is called during Close().
+  if (state_ == STATE_CLOSED) {
+    DCHECK_EQ(net::ERR_FAILED, net_result);
     SendListenError(context, PP_ERROR_FAILED);
-    state_ = STATE_CLOSED;
     return;
   }
+
+  DCHECK(socket_.is_bound());
+  DCHECK_EQ(state_, STATE_LISTEN_IN_PROGRESS);
+
   if (net_result != net::OK) {
     SendListenError(context, NetErrorToPepperError(net_result));
+    socket_.reset();
     state_ = STATE_BEFORE_LISTENING;
     return;
   }
 
-  DCHECK(socket_.get());
-
-  net::IPEndPoint end_point;
-  PP_NetAddress_Private addr;
-
-  int32_t pp_result =
-      NetErrorToPepperError(socket_->GetLocalAddress(&end_point));
-  if (pp_result != PP_OK) {
-    SendListenError(context, pp_result);
-    state_ = STATE_BEFORE_LISTENING;
-    return;
-  }
-  if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
-          end_point.address().bytes(), end_point.port(), &addr)) {
+  if (!local_addr ||
+      !NetAddressPrivateImpl::IPEndPointToNetAddress(
+          local_addr->address().bytes(), local_addr->port(), &bound_addr_)) {
     SendListenError(context, PP_ERROR_FAILED);
+    socket_.reset();
     state_ = STATE_BEFORE_LISTENING;
     return;
   }
 
-  SendListenReply(context, PP_OK, addr);
+#if defined(OS_CHROMEOS)
+  OpenFirewallHole(context, *local_addr);
+#else
+  SendListenReply(context, PP_OK, bound_addr_);
   state_ = STATE_LISTENING;
+#endif
 }
 
 #if defined(OS_CHROMEOS)
 void PepperTCPServerSocketMessageFilter::OpenFirewallHole(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  if (net_result != net::OK) {
-    return;
-  }
-  net::IPEndPoint local_addr;
-  socket_->GetLocalAddress(&local_addr);
-  pepper_socket_utils::FirewallHoleOpenCallback callback =
-      base::Bind(&PepperTCPServerSocketMessageFilter::OnFirewallHoleOpened,
-                 this, context, net_result);
+    const net::IPEndPoint& local_addr) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  pepper_socket_utils::FirewallHoleOpenCallback callback = base::BindRepeating(
+      &PepperTCPServerSocketMessageFilter::OnFirewallHoleOpened, this, context);
   pepper_socket_utils::OpenTCPFirewallHole(local_addr, callback);
 }
 
 void PepperTCPServerSocketMessageFilter::OnFirewallHoleOpened(
     const ppapi::host::ReplyMessageContext& context,
-    int32_t net_result,
     std::unique_ptr<chromeos::FirewallHole> hole) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   LOG_IF(WARNING, !hole.get()) << "Firewall hole could not be opened.";
   firewall_hole_.reset(hole.release());
-  OnListenCompleted(context, net_result);
+
+  SendListenReply(context, PP_OK, bound_addr_);
+  state_ = STATE_LISTENING;
 }
 #endif  // defined(OS_CHROMEOS)
 
 void PepperTCPServerSocketMessageFilter::OnAcceptCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  if (state_ != STATE_ACCEPT_IN_PROGRESS) {
-    SendAcceptError(context, PP_ERROR_FAILED);
-    state_ = STATE_CLOSED;
+    network::mojom::SocketObserverRequest socket_observer_request,
+    int net_result,
+    const base::Optional<net::IPEndPoint>& remote_addr,
+    network::mojom::TCPConnectedSocketPtr connected_socket,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // Exit early if this is called during Close().
+  if (state_ == STATE_CLOSED) {
+    DCHECK_EQ(net::ERR_FAILED, net_result);
+    SendListenError(context, PP_ERROR_FAILED);
     return;
   }
 
-  state_ = STATE_LISTENING;
+  DCHECK_EQ(state_, STATE_ACCEPT_IN_PROGRESS);
 
+  state_ = STATE_LISTENING;
   if (net_result != net::OK) {
     SendAcceptError(context, NetErrorToPepperError(net_result));
     return;
   }
 
-  DCHECK(accepted_socket_.get());
-
-  net::IPEndPoint ip_end_point_local;
-  PP_NetAddress_Private local_addr = NetAddressPrivateImpl::kInvalidNetAddress;
-  PP_NetAddress_Private remote_addr = NetAddressPrivateImpl::kInvalidNetAddress;
-
-  int32_t pp_result = NetErrorToPepperError(
-      accepted_socket_->GetLocalAddress(&ip_end_point_local));
-  if (pp_result != PP_OK) {
-    SendAcceptError(context, pp_result);
+  if (!remote_addr || !connected_socket.is_bound()) {
+    SendAcceptError(context, NetErrorToPepperError(net_result));
     return;
   }
+
+  DCHECK(socket_observer_request.is_pending());
+
+  PP_NetAddress_Private pp_remote_addr =
+      NetAddressPrivateImpl::kInvalidNetAddress;
+
   if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
-          ip_end_point_local.address().bytes(), ip_end_point_local.port(),
-          &local_addr) ||
-      !NetAddressPrivateImpl::IPEndPointToNetAddress(
-          accepted_address_.address().bytes(), accepted_address_.port(),
-          &remote_addr)) {
-    SendAcceptError(context, PP_ERROR_FAILED);
+          remote_addr->address().bytes(), remote_addr->port(),
+          &pp_remote_addr)) {
+    SendAcceptError(context, PP_ERROR_ADDRESS_INVALID);
     return;
   }
 
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO},
+      base::BindOnce(
+          &PepperTCPServerSocketMessageFilter::OnAcceptCompletedOnIOThread,
+          this, context, connected_socket.PassInterface(),
+          std::move(socket_observer_request), std::move(receive_stream),
+          std::move(send_stream), bound_addr_, pp_remote_addr));
+}
+
+void PepperTCPServerSocketMessageFilter::OnAcceptCompletedOnIOThread(
+    const ppapi::host::ReplyMessageContext& context,
+    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+    network::mojom::SocketObserverRequest socket_observer_request,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream,
+    PP_NetAddress_Private pp_local_addr,
+    PP_NetAddress_Private pp_remote_addr) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  // |factory_| is guaranteed to be non-NULL here. Only those instances created
+  // in CONNECTED state have a NULL |factory_|, while getting here requires
+  // LISTENING state.
   std::unique_ptr<ppapi::host::ResourceHost> host =
-      factory_->CreateAcceptedTCPSocket(instance_,
-                                        ppapi::TCP_SOCKET_VERSION_PRIVATE,
-                                        std::move(accepted_socket_));
+      factory_->CreateAcceptedTCPSocket(
+          instance_, ppapi::TCP_SOCKET_VERSION_PRIVATE,
+          std::move(connected_socket), std::move(socket_observer_request),
+          std::move(receive_stream), std::move(send_stream));
   if (!host) {
     SendAcceptError(context, PP_ERROR_NOSPACE);
     return;
   }
-  int pending_resource_id =
-      ppapi_host_->AddPendingResourceHost(std::move(host));
-  if (pending_resource_id) {
-    SendAcceptReply(
-        context, PP_OK, pending_resource_id, local_addr, remote_addr);
+
+  int pending_host_id = ppapi_host_->AddPendingResourceHost(std::move(host));
+  if (pending_host_id) {
+    SendAcceptReply(context, PP_OK, pending_host_id, pp_local_addr,
+                    pp_remote_addr);
   } else {
     SendAcceptError(context, PP_ERROR_NOSPACE);
   }
 }
 
+void PepperTCPServerSocketMessageFilter::Close() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // This needs to be the first line, so if closing Mojo pipes results in
+  // invoking any callbacks, they can exit early.
+  state_ = STATE_CLOSED;
+
+  socket_.reset();
+#if defined(OS_CHROMEOS)
+  firewall_hole_.reset();
+#endif  // defined(OS_CHROMEOS)
+}
+
 void PepperTCPServerSocketMessageFilter::SendListenReply(
     const ppapi::host::ReplyMessageContext& context,
     int32_t pp_result,
diff --git a/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h b/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h
index 9c7be218..482d7450 100644
--- a/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h
+++ b/content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h
@@ -13,20 +13,27 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
+#include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/ip_endpoint.h"
-#include "net/socket/tcp_socket.h"
 #include "ppapi/c/pp_instance.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
 #include "ppapi/host/resource_message_filter.h"
-
-struct PP_NetAddress_Private;
+#include "services/network/public/mojom/tcp_socket.mojom.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/network/firewall_hole.h"
 #include "content/public/browser/browser_thread.h"
 #endif  // defined(OS_CHROMEOS)
 
+namespace network {
+namespace mojom {
+class NetworkContext;
+}
+}  // namespace network
+
 namespace ppapi {
 namespace host {
 class PpapiHost;
@@ -48,6 +55,11 @@
                                      PP_Instance instance,
                                      bool private_api);
 
+  // Sets a global NetworkContext object to be used instead of the real one for
+  // doing all network operations.
+  static void SetNetworkContextForTesting(
+      network::mojom::NetworkContext* network_context);
+
   static size_t GetNumInstances();
 
  protected:
@@ -63,6 +75,7 @@
   };
 
   // ppapi::host::ResourceMessageFilter overrides.
+  void OnFilterDestroyed() override;
   scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message) override;
   int32_t OnResourceMessageReceived(
@@ -76,13 +89,31 @@
   int32_t OnMsgStopListening(const ppapi::host::HostMessageContext* context);
 
   void DoListen(const ppapi::host::ReplyMessageContext& context,
-                const PP_NetAddress_Private& addr,
                 int32_t backlog);
 
   void OnListenCompleted(const ppapi::host::ReplyMessageContext& context,
-                         int net_result);
-  void OnAcceptCompleted(const ppapi::host::ReplyMessageContext& context,
-                         int net_result);
+                         int net_result,
+                         const base::Optional<net::IPEndPoint>& local_addr);
+  void OnAcceptCompleted(
+      const ppapi::host::ReplyMessageContext& context,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      int net_result,
+      const base::Optional<net::IPEndPoint>& remote_addr,
+      network::mojom::TCPConnectedSocketPtr connected_socket,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream);
+  void OnAcceptCompletedOnIOThread(
+      const ppapi::host::ReplyMessageContext& context,
+      network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream,
+      PP_NetAddress_Private pp_local_addr,
+      PP_NetAddress_Private pp_remote_addr);
+
+  // Closes the socket and FirewallHole, if they're open, and prevents
+  // |this| from being used further, even with a new socket.
+  void Close();
 
   void SendListenReply(const ppapi::host::ReplyMessageContext& context,
                        int32_t pp_result,
@@ -99,9 +130,8 @@
 
 #if defined(OS_CHROMEOS)
   void OpenFirewallHole(const ppapi::host::ReplyMessageContext& context,
-                        int net_result);
+                        const net::IPEndPoint& local_addr);
   void OnFirewallHoleOpened(const ppapi::host::ReplyMessageContext& context,
-                            int32_t net_result,
                             std::unique_ptr<chromeos::FirewallHole> hole);
 #endif  // defined(OS_CHROMEOS)
 
@@ -113,9 +143,9 @@
   PP_Instance instance_;
 
   State state_;
-  std::unique_ptr<net::TCPSocket> socket_;
-  std::unique_ptr<net::TCPSocket> accepted_socket_;
-  net::IPEndPoint accepted_address_;
+  network::mojom::TCPServerSocketPtr socket_;
+
+  PP_NetAddress_Private bound_addr_;
 
 #if defined(OS_CHROMEOS)
   std::unique_ptr<chromeos::FirewallHole,
@@ -130,6 +160,9 @@
   int render_process_id_;
   int render_frame_id_;
 
+  // Used in place of the StoragePartition's NetworkContext when non-null.
+  static network::mojom::NetworkContext* network_context_for_testing;
+
   DISALLOW_COPY_AND_ASSIGN(PepperTCPServerSocketMessageFilter);
 };
 
diff --git a/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.cc b/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.cc
index 070667a..044e303 100644
--- a/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.cc
+++ b/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.cc
@@ -6,7 +6,6 @@
 
 #include <cstring>
 #include <utility>
-#include <vector>
 
 #include "base/bind.h"
 #include "base/location.h"
@@ -23,6 +22,8 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/socket_permission_request.h"
+#include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "net/base/address_family.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/io_buffer.h"
@@ -30,10 +31,7 @@
 #include "net/base/net_errors.h"
 #include "net/log/net_log_source.h"
 #include "net/log/net_log_with_source.h"
-#include "net/socket/client_socket_factory.h"
-#include "net/socket/client_socket_handle.h"
-#include "net/socket/ssl_client_socket.h"
-#include "net/socket/tcp_client_socket.h"
+#include "net/ssl/ssl_info.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "ppapi/host/dispatch_host_message.h"
 #include "ppapi/host/error_conversion.h"
@@ -48,10 +46,10 @@
 #endif  // defined(OS_CHROMEOS)
 
 using ppapi::NetAddressPrivateImpl;
-using ppapi::host::NetErrorToPepperError;
-using ppapi::proxy::TCPSocketResourceConstants;
 using ppapi::TCPSocketState;
 using ppapi::TCPSocketVersion;
+using ppapi::host::NetErrorToPepperError;
+using ppapi::proxy::TCPSocketResourceConstants;
 
 namespace {
 
@@ -61,31 +59,34 @@
 
 namespace content {
 
+network::mojom::NetworkContext*
+    PepperTCPSocketMessageFilter::network_context_for_testing = nullptr;
+
 PepperTCPSocketMessageFilter::PepperTCPSocketMessageFilter(
     ContentBrowserPepperHostFactory* factory,
     BrowserPpapiHostImpl* host,
     PP_Instance instance,
     TCPSocketVersion version)
     : version_(version),
+      host_(host),
+      factory_(factory),
+      instance_(instance),
       external_plugin_(host->external_plugin()),
       render_process_id_(0),
       render_frame_id_(0),
       binding_(this),
-      host_(host),
-      factory_(factory),
-      instance_(instance),
+      socket_observer_binding_(this),
       state_(TCPSocketState::INITIAL),
-      end_of_file_reached_(false),
       bind_input_addr_(NetAddressPrivateImpl::kInvalidNetAddress),
       socket_options_(SOCKET_OPTION_NODELAY),
       rcvbuf_size_(0),
       sndbuf_size_(0),
-      address_index_(0),
-      socket_(new net::TCPSocket(nullptr, nullptr, net::NetLogSource())),
-      ssl_context_helper_(host->ssl_context_helper()),
       pending_accept_(false),
+      pending_read_size_(0),
+      pending_read_pp_error_(PP_OK_COMPLETIONPENDING),
       pending_read_on_unthrottle_(false),
-      pending_read_net_result_(0),
+      pending_write_bytes_written_(0),
+      pending_write_pp_error_(PP_OK_COMPLETIONPENDING),
       is_potentially_secure_plugin_context_(
           host->IsPotentiallySecurePluginContext(instance)) {
   DCHECK(host);
@@ -93,67 +94,79 @@
 
   ++g_num_tcp_filter_instances;
   host_->AddInstanceObserver(instance_, this);
-  if (!host->GetRenderFrameIDsForInstance(
-          instance, &render_process_id_, &render_frame_id_)) {
+  is_throttled_ = host_->IsThrottled(instance_);
+  if (!host->GetRenderFrameIDsForInstance(instance, &render_process_id_,
+                                          &render_frame_id_)) {
     NOTREACHED();
   }
 }
 
-PepperTCPSocketMessageFilter::PepperTCPSocketMessageFilter(
-    BrowserPpapiHostImpl* host,
-    PP_Instance instance,
-    TCPSocketVersion version,
-    std::unique_ptr<net::TCPSocket> socket)
-    : version_(version),
-      external_plugin_(host->external_plugin()),
-      render_process_id_(0),
-      render_frame_id_(0),
-      binding_(this),
-      host_(host),
-      factory_(nullptr),
-      instance_(instance),
-      state_(TCPSocketState::CONNECTED),
-      end_of_file_reached_(false),
-      bind_input_addr_(NetAddressPrivateImpl::kInvalidNetAddress),
-      socket_options_(SOCKET_OPTION_NODELAY),
-      rcvbuf_size_(0),
-      sndbuf_size_(0),
-      address_index_(0),
-      socket_(std::move(socket)),
-      ssl_context_helper_(host->ssl_context_helper()),
-      pending_accept_(false),
-      pending_read_on_unthrottle_(false),
-      pending_read_net_result_(0),
-      is_potentially_secure_plugin_context_(
-          host->IsPotentiallySecurePluginContext(instance)) {
-  DCHECK(host);
-  DCHECK_NE(version, ppapi::TCP_SOCKET_VERSION_1_0);
+void PepperTCPSocketMessageFilter::SetConnectedSocket(
+    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+    network::mojom::SocketObserverRequest socket_observer_request,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  // This method grabs a reference to |this|, and releases a reference on the UI
+  // thread, so something should be holding on to a reference on the current
+  // thread to prevent the object from being deleted before this method returns.
+  DCHECK(HasOneRef());
 
-  ++g_num_tcp_filter_instances;
-  host_->AddInstanceObserver(instance_, this);
-  if (!host->GetRenderFrameIDsForInstance(
-          instance, &render_process_id_, &render_frame_id_)) {
-    NOTREACHED();
-  }
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(
+          &PepperTCPSocketMessageFilter::SetConnectedSocketOnUIThread, this,
+          std::move(connected_socket), std::move(socket_observer_request),
+          std::move(receive_stream), std::move(send_stream)));
 }
 
 PepperTCPSocketMessageFilter::~PepperTCPSocketMessageFilter() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (host_)
     host_->RemoveInstanceObserver(instance_, this);
-  if (socket_)
-    socket_->Close();
-  if (ssl_socket_)
-    ssl_socket_->Disconnect();
   --g_num_tcp_filter_instances;
 }
 
+void PepperTCPSocketMessageFilter::SetConnectedSocketOnUIThread(
+    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+    network::mojom::SocketObserverRequest socket_observer_request,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_EQ(state_.state(), TCPSocketState::INITIAL);
+
+  state_ = TCPSocketState(TCPSocketState::CONNECTED);
+  connected_socket_.Bind(std::move(connected_socket));
+  socket_observer_binding_.Bind(std::move(socket_observer_request));
+  socket_observer_binding_.set_connection_error_handler(
+      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
+                     base::Unretained(this)));
+
+  SetStreams(std::move(receive_stream), std::move(send_stream));
+}
+
+// static
+void PepperTCPSocketMessageFilter::SetNetworkContextForTesting(
+    network::mojom::NetworkContext* network_context) {
+  network_context_for_testing = network_context;
+}
+
 // static
 size_t PepperTCPSocketMessageFilter::GetNumInstances() {
   return g_num_tcp_filter_instances;
 }
 
+void PepperTCPSocketMessageFilter::OnFilterDestroyed() {
+  ResourceMessageFilter::OnFilterDestroyed();
+  // Need to close all mojo pipes the socket on the UI thread. Calling Close()
+  // also ensures that future messages will be ignored, so the mojo pipes won't
+  // be re-created, so after Close() runs, |this| can be safely deleted on the
+  // IO thread.
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&PepperTCPSocketMessageFilter::Close, this));
+}
+
 scoped_refptr<base::TaskRunner>
 PepperTCPSocketMessageFilter::OverrideTaskRunnerForMessage(
     const IPC::Message& message) {
@@ -162,14 +175,13 @@
     case PpapiHostMsg_TCPSocket_Connect::ID:
     case PpapiHostMsg_TCPSocket_ConnectWithNetAddress::ID:
     case PpapiHostMsg_TCPSocket_Listen::ID:
-      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
     case PpapiHostMsg_TCPSocket_SSLHandshake::ID:
     case PpapiHostMsg_TCPSocket_Read::ID:
     case PpapiHostMsg_TCPSocket_Write::ID:
     case PpapiHostMsg_TCPSocket_Accept::ID:
     case PpapiHostMsg_TCPSocket_Close::ID:
     case PpapiHostMsg_TCPSocket_SetOption::ID:
-      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
+      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
   }
   return nullptr;
 }
@@ -201,13 +213,11 @@
 }
 
 void PepperTCPSocketMessageFilter::OnThrottleStateChanged(bool is_throttled) {
-  if (pending_read_on_unthrottle_ && !is_throttled) {
-    DCHECK(read_buffer_);
-    OnReadCompleted(pending_read_reply_message_context_,
-                    pending_read_net_result_);
-    DCHECK(!read_buffer_);
-    pending_read_on_unthrottle_ = false;
-  }
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(
+          &PepperTCPSocketMessageFilter::ThrottleStateChangedOnUIThread, this,
+          is_throttled));
 }
 
 void PepperTCPSocketMessageFilter::OnHostDestroyed() {
@@ -216,18 +226,86 @@
   host_ = nullptr;
 }
 
+void PepperTCPSocketMessageFilter::ThrottleStateChangedOnUIThread(
+    bool is_throttled) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  is_throttled_ = is_throttled;
+
+  if (pending_read_on_unthrottle_ && !is_throttled) {
+    pending_read_on_unthrottle_ = false;
+    TryRead();
+  }
+}
+
 void PepperTCPSocketMessageFilter::OnComplete(
     int result,
     const base::Optional<net::AddressList>& resolved_addresses) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   binding_.Close();
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::OnResolveCompleted, this,
-                     result, std::move(resolved_addresses)));
+  if (!host_resolve_context_.is_valid())
+    return;
 
-  Release();  // Balances AddRef in OnMsgConnect.
+  ppapi::host::ReplyMessageContext context = host_resolve_context_;
+  host_resolve_context_ = ppapi::host::ReplyMessageContext();
+
+  if (!state_.IsPending(TCPSocketState::CONNECT)) {
+    DCHECK(state_.state() == TCPSocketState::CLOSED);
+    SendConnectError(context, PP_ERROR_FAILED);
+    return;
+  }
+
+  if (result != net::OK) {
+    SendConnectError(context, NetErrorToPepperError(result));
+    state_.CompletePendingTransition(false);
+    return;
+  }
+
+  StartConnect(context, resolved_addresses.value());
+}
+
+void PepperTCPSocketMessageFilter::OnReadError(int net_error) {
+  // If this method is called more than once, or |net_error| isn't an allowed
+  // value, just ignore the message.
+  if (pending_read_pp_error_ != PP_OK_COMPLETIONPENDING || net_error > 0 ||
+      net_error == net::ERR_IO_PENDING) {
+    return;
+  }
+
+  pending_read_pp_error_ = NetErrorToPepperError(net_error);
+  // Complete pending read with the error message if there's a pending read, and
+  // the read data pipe has already been closed. If the pipe is still open, need
+  // to wait until all data has been read before can start failing reads.
+  if (pending_read_context_.is_valid() && !receive_stream_ &&
+      !pending_read_on_unthrottle_) {
+    TryRead();
+  }
+}
+
+void PepperTCPSocketMessageFilter::OnWriteError(int net_error) {
+  // If this method is called more than once, or |net_error| isn't an allowed
+  // value, just ignore the message.
+  if (pending_write_pp_error_ != PP_OK_COMPLETIONPENDING || net_error > 0 ||
+      net_error == net::ERR_IO_PENDING) {
+    return;
+  }
+
+  pending_write_pp_error_ = NetErrorToPepperError(net_error);
+  // Complete pending write with the error message if there's a pending write,
+  // and the write data pipe has already been closed.
+  if (pending_write_context_.is_valid() && !send_stream_)
+    TryWrite();
+}
+
+void PepperTCPSocketMessageFilter::OnSocketObserverError() {
+  // Note that this method may be called while a connection is still being made.
+  socket_observer_binding_.Close();
+
+  // Treat this as a read and write error. If read and write errors have already
+  // been received, these calls will do nothing.
+  OnReadError(PP_ERROR_FAILED);
+  OnWriteError(PP_ERROR_FAILED);
 }
 
 int32_t PepperTCPSocketMessageFilter::OnMsgBind(
@@ -247,12 +325,45 @@
     return PP_ERROR_NOACCESS;
   }
 
+  if (state_.IsPending(TCPSocketState::BIND))
+    return PP_ERROR_INPROGRESS;
+
+  if (!state_.IsValidTransition(TCPSocketState::BIND))
+    return PP_ERROR_FAILED;
+
+  DCHECK(!bound_socket_);
+  DCHECK(!connected_socket_);
+  DCHECK(!server_socket_);
+
+  // Validate address.
+  net::IPAddressBytes address;
+  uint16_t port;
+  if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr, &address,
+                                                     &port)) {
+    state_.DoTransition(TCPSocketState::BIND, false);
+    return PP_ERROR_ADDRESS_INVALID;
+  }
+
+  network::mojom::NetworkContext* network_context = GetNetworkContext();
+  if (!network_context)
+    return PP_ERROR_FAILED;
+
+  // The network service doesn't allow binding a socket without first
+  // specifying if it's going to be used as a read or write socket,
+  // so just hold onto the address for now, without actually binding anything.
   bind_input_addr_ = net_addr;
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::DoBind, this,
-                     context->MakeReplyMessageContext(), net_addr));
+  state_.SetPendingTransition(TCPSocketState::BIND);
+  network_context->CreateTCPBoundSocket(
+      net::IPEndPoint(net::IPAddress(address), port),
+      pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
+      mojo::MakeRequest(&bound_socket_),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPSocketMessageFilter::OnBindCompleted,
+                         base::Unretained(this),
+                         context->MakeReplyMessageContext()),
+          net::ERR_FAILED, base::nullopt /* local_addr */));
+
   return PP_OK_COMPLETIONPENDING;
 }
 
@@ -268,39 +379,34 @@
     return PP_ERROR_NOACCESS;
   }
 
-  SocketPermissionRequest request(
-      SocketPermissionRequest::TCP_CONNECT, host, port);
-  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
-                                             true /* private_api */,
-                                             &request,
-                                             render_process_id_,
-                                             render_frame_id_)) {
+  SocketPermissionRequest request(SocketPermissionRequest::TCP_CONNECT, host,
+                                  port);
+  if (!pepper_socket_utils::CanUseSocketAPIs(
+          external_plugin_, true /* private_api */, &request,
+          render_process_id_, render_frame_id_)) {
     return PP_ERROR_NOACCESS;
   }
 
-  RenderProcessHost* render_process_host =
-      RenderProcessHost::FromID(render_process_id_);
-  if (!render_process_host)
+  if (!state_.IsValidTransition(TCPSocketState::CONNECT)) {
+    NOTREACHED() << "This shouldn't be reached since the renderer only tries "
+                 << "to connect once.";
     return PP_ERROR_FAILED;
-  auto* storage_partition = render_process_host->GetStoragePartition();
-  // Grab a reference to this class to ensure that it's fully alive if a
-  // connection error occurs (i.e. ref count is higher than 0 and there's no
-  // task from ResourceMessageFilterDeleteTraits to delete this object on the IO
-  // thread pending). Balanced in OnComplete();
-  AddRef();
+  }
+
+  network::mojom::NetworkContext* network_context = GetNetworkContext();
+  if (!network_context)
+    return PP_ERROR_FAILED;
 
   network::mojom::ResolveHostClientPtr client_ptr;
   binding_.Bind(mojo::MakeRequest(&client_ptr));
   binding_.set_connection_error_handler(
       base::BindOnce(&PepperTCPSocketMessageFilter::OnComplete,
                      base::Unretained(this), net::ERR_FAILED, base::nullopt));
-  storage_partition->GetNetworkContext()->ResolveHost(
-      net::HostPortPair(host, port), nullptr, std::move(client_ptr));
+  network_context->ResolveHost(net::HostPortPair(host, port), nullptr,
+                               std::move(client_ptr));
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::HostResolvingStarted, this,
-                     context->MakeReplyMessageContext()));
+  state_.SetPendingTransition(TCPSocketState::CONNECT);
+  host_resolve_context_ = context->MakeReplyMessageContext();
   return PP_OK_COMPLETIONPENDING;
 }
 
@@ -312,18 +418,28 @@
   content::SocketPermissionRequest request =
       pepper_socket_utils::CreateSocketPermissionRequest(
           content::SocketPermissionRequest::TCP_CONNECT, net_addr);
-  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
-                                             IsPrivateAPI(),
-                                             &request,
-                                             render_process_id_,
+  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_, IsPrivateAPI(),
+                                             &request, render_process_id_,
                                              render_frame_id_)) {
     return PP_ERROR_NOACCESS;
   }
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::DoConnectWithNetAddress,
-                     this, context->MakeReplyMessageContext(), net_addr));
+  if (!state_.IsValidTransition(TCPSocketState::CONNECT))
+    return PP_ERROR_FAILED;
+
+  state_.SetPendingTransition(TCPSocketState::CONNECT);
+
+  net::IPAddressBytes address;
+  uint16_t port;
+  if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr, &address,
+                                                     &port)) {
+    state_.CompletePendingTransition(false);
+    return PP_ERROR_ADDRESS_INVALID;
+  }
+
+  StartConnect(
+      context->MakeReplyMessageContext(),
+      net::AddressList(net::IPEndPoint(net::IPAddress(address), port)));
   return PP_OK_COMPLETIONPENDING;
 }
 
@@ -331,103 +447,101 @@
     const ppapi::host::HostMessageContext* context,
     const std::string& server_name,
     uint16_t server_port,
-    const std::vector<std::vector<char> >& trusted_certs,
-    const std::vector<std::vector<char> >& untrusted_certs) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    const std::vector<std::vector<char>>& trusted_certs,
+    const std::vector<std::vector<char>>& untrusted_certs) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // Allow to do SSL handshake only if currently the socket has been connected
   // and there isn't pending read or write.
   if (!state_.IsValidTransition(TCPSocketState::SSL_CONNECT) ||
-      read_buffer_.get() || write_buffer_base_.get() || write_buffer_.get()) {
+      pending_read_context_.is_valid() || pending_write_context_.is_valid()) {
     return PP_ERROR_FAILED;
   }
 
+  // If there's a pending read or write error, fail the request with that.
+  if (pending_read_pp_error_ != PP_OK_COMPLETIONPENDING) {
+    if (pending_read_pp_error_ == PP_OK)
+      pending_read_pp_error_ = PP_ERROR_FAILED;
+    return pending_read_pp_error_;
+  }
+
+  if (pending_write_pp_error_ != PP_OK_COMPLETIONPENDING) {
+    if (pending_write_pp_error_ == PP_OK)
+      pending_write_pp_error_ = PP_ERROR_FAILED;
+    return pending_write_pp_error_;
+  }
+
   // TODO(raymes,rsleevi): Use trusted/untrusted certificates when connecting.
-  net::IPEndPoint peer_address;
-  if (socket_->GetPeerAddress(&peer_address) != net::OK)
-    return PP_ERROR_FAILED;
 
-  std::unique_ptr<net::ClientSocketHandle> handle(
-      new net::ClientSocketHandle());
-  handle->SetSocket(base::WrapUnique<net::StreamSocket>(
-      new net::TCPClientSocket(std::move(socket_), peer_address)));
-  net::ClientSocketFactory* factory =
-      net::ClientSocketFactory::GetDefaultFactory();
-  net::HostPortPair host_port_pair(server_name, server_port);
-  net::SSLClientSocketContext ssl_context;
-  ssl_context.cert_verifier = ssl_context_helper_->GetCertVerifier();
-  ssl_context.transport_security_state =
-      ssl_context_helper_->GetTransportSecurityState();
-  ssl_context.cert_transparency_verifier =
-      ssl_context_helper_->GetCertTransparencyVerifier();
-  ssl_context.ct_policy_enforcer = ssl_context_helper_->GetCTPolicyEnforcer();
-  ssl_socket_ = factory->CreateSSLClientSocket(
-      std::move(handle), host_port_pair, ssl_context_helper_->ssl_config(),
-      ssl_context);
-  if (!ssl_socket_) {
-    LOG(WARNING) << "Failed to create an SSL client socket.";
-    state_.CompletePendingTransition(false);
-    return PP_ERROR_FAILED;
-  }
+  // Close all Mojo pipes except |connected_socket_|.
+  receive_stream_.reset();
+  read_watcher_.reset();
+  send_stream_.reset();
+  write_watcher_.reset();
+  socket_observer_binding_.Close();
+
+  network::mojom::SocketObserverPtr socket_observer;
+  socket_observer_binding_.Bind(mojo::MakeRequest(&socket_observer));
+  socket_observer_binding_.set_connection_error_handler(
+      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
+                     base::Unretained(this)));
 
   state_.SetPendingTransition(TCPSocketState::SSL_CONNECT);
 
-  const ppapi::host::ReplyMessageContext reply_context(
-      context->MakeReplyMessageContext());
-  int net_result = ssl_socket_->Connect(
-      base::BindOnce(&PepperTCPSocketMessageFilter::OnSSLHandshakeCompleted,
-                     base::Unretained(this), reply_context));
-  if (net_result != net::ERR_IO_PENDING)
-    OnSSLHandshakeCompleted(reply_context, net_result);
+  network::mojom::TLSClientSocketOptionsPtr tls_client_socket_options =
+      network::mojom::TLSClientSocketOptions::New();
+  tls_client_socket_options->send_ssl_info = true;
+  net::HostPortPair host_port_pair(server_name, server_port);
+  connected_socket_->UpgradeToTLS(
+      host_port_pair, std::move(tls_client_socket_options),
+      pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
+      mojo::MakeRequest(&tls_client_socket_), std::move(socket_observer),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPSocketMessageFilter::OnSSLHandshakeCompleted,
+                         base::Unretained(this),
+                         context->MakeReplyMessageContext()),
+          net::ERR_FAILED, mojo::ScopedDataPipeConsumerHandle(),
+          mojo::ScopedDataPipeProducerHandle(), base::nullopt /* ssl_info */));
+
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPSocketMessageFilter::OnMsgRead(
     const ppapi::host::HostMessageContext* context,
     int32_t bytes_to_read) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!state_.IsConnected() || end_of_file_reached_)
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  // This only covers the case where the socket was explicitly closed from the
+  // caller, or the filter is being destroyed. Read errors and Mojo errors are
+  // handled in TryRead().
+  if (!state_.IsConnected())
     return PP_ERROR_FAILED;
-  if (read_buffer_.get())
+
+  if (pending_read_context_.is_valid())
     return PP_ERROR_INPROGRESS;
   if (bytes_to_read <= 0 ||
       bytes_to_read > TCPSocketResourceConstants::kMaxReadSize) {
     return PP_ERROR_BADARGUMENT;
   }
 
-  ppapi::host::ReplyMessageContext reply_context(
-      context->MakeReplyMessageContext());
-  read_buffer_ = base::MakeRefCounted<net::IOBuffer>(bytes_to_read);
-
-  int net_result = net::ERR_FAILED;
-  if (socket_) {
-    DCHECK_EQ(state_.state(), TCPSocketState::CONNECTED);
-    net_result = socket_->Read(
-        read_buffer_.get(), bytes_to_read,
-        base::BindOnce(&PepperTCPSocketMessageFilter::OnReadCompleted,
-                       base::Unretained(this), reply_context));
-  } else if (ssl_socket_) {
-    DCHECK_EQ(state_.state(), TCPSocketState::SSL_CONNECTED);
-    net_result = ssl_socket_->Read(
-        read_buffer_.get(), bytes_to_read,
-        base::BindOnce(&PepperTCPSocketMessageFilter::OnReadCompleted,
-                       base::Unretained(this), reply_context));
-  }
-  if (net_result != net::ERR_IO_PENDING)
-    OnReadCompleted(reply_context, net_result);
+  pending_read_context_ = context->MakeReplyMessageContext();
+  pending_read_size_ = static_cast<uint32_t>(bytes_to_read);
+  TryRead();
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPSocketMessageFilter::OnMsgWrite(
     const ppapi::host::HostMessageContext* context,
     const std::string& data) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!state_.IsConnected())
     return PP_ERROR_FAILED;
-  if (write_buffer_base_.get() || write_buffer_.get())
+  if (pending_write_context_.is_valid())
     return PP_ERROR_INPROGRESS;
 
+  DCHECK(pending_write_data_.empty());
+  DCHECK_EQ(0u, pending_write_bytes_written_);
+
   size_t data_size = data.size();
   if (data_size == 0 ||
       data_size >
@@ -435,11 +549,9 @@
     return PP_ERROR_BADARGUMENT;
   }
 
-  write_buffer_base_ = base::MakeRefCounted<net::IOBuffer>(data_size);
-  memcpy(write_buffer_base_->data(), data.data(), data_size);
-  write_buffer_ = base::MakeRefCounted<net::DrainableIOBuffer>(
-      write_buffer_base_, data_size);
-  DoWrite(context->MakeReplyMessageContext());
+  pending_write_data_ = data;
+  pending_write_context_ = context->MakeReplyMessageContext();
+  TryWrite();
   return PP_OK_COMPLETIONPENDING;
 }
 
@@ -448,6 +560,12 @@
     int32_t backlog) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  if (state_.IsPending(TCPSocketState::LISTEN))
+    return PP_ERROR_INPROGRESS;
+
+  if (!state_.IsValidTransition(TCPSocketState::LISTEN))
+    return PP_ERROR_FAILED;
+
   // This is only supported by PPB_TCPSocket v1.1 or above.
   if (version_ != ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
     NOTREACHED();
@@ -457,59 +575,64 @@
   content::SocketPermissionRequest request =
       pepper_socket_utils::CreateSocketPermissionRequest(
           content::SocketPermissionRequest::TCP_LISTEN, bind_input_addr_);
-  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
-                                             false /* private_api */,
-                                             &request,
-                                             render_process_id_,
-                                             render_frame_id_)) {
+  if (!pepper_socket_utils::CanUseSocketAPIs(
+          external_plugin_, false /* private_api */, &request,
+          render_process_id_, render_frame_id_)) {
     return PP_ERROR_NOACCESS;
   }
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::DoListen, this,
-                     context->MakeReplyMessageContext(), backlog));
+  DCHECK(bound_socket_);
+  DCHECK(!server_socket_);
+
+  state_.SetPendingTransition(TCPSocketState::LISTEN);
+
+  bound_socket_->Listen(
+      backlog, mojo::MakeRequest(&server_socket_),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPSocketMessageFilter::OnListenCompleted,
+                         base::Unretained(this),
+                         context->MakeReplyMessageContext()),
+          net::ERR_FAILED));
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPSocketMessageFilter::OnMsgAccept(
     const ppapi::host::HostMessageContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (pending_accept_)
     return PP_ERROR_INPROGRESS;
   if (state_.state() != TCPSocketState::LISTENING)
     return PP_ERROR_FAILED;
 
+  DCHECK(server_socket_);
+
   pending_accept_ = true;
-  ppapi::host::ReplyMessageContext reply_context(
-      context->MakeReplyMessageContext());
-  int net_result = socket_->Accept(
-      &accepted_socket_, &accepted_address_,
-      base::Bind(&PepperTCPSocketMessageFilter::OnAcceptCompleted,
-                 base::Unretained(this), reply_context));
-  if (net_result != net::ERR_IO_PENDING)
-    OnAcceptCompleted(reply_context, net_result);
+
+  network::mojom::SocketObserverPtr socket_observer;
+  network::mojom::SocketObserverRequest socket_observer_request =
+      mojo::MakeRequest(&socket_observer);
+  server_socket_->Accept(
+      std::move(socket_observer),
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPSocketMessageFilter::OnAcceptCompleted,
+                         base::Unretained(this),
+                         context->MakeReplyMessageContext(),
+                         std::move(socket_observer_request)),
+          net::ERR_FAILED, base::nullopt /* remote_addr */,
+          network::mojom::TCPConnectedSocketPtr(),
+          mojo::ScopedDataPipeConsumerHandle(),
+          mojo::ScopedDataPipeProducerHandle()));
   return PP_OK_COMPLETIONPENDING;
 }
 
 int32_t PepperTCPSocketMessageFilter::OnMsgClose(
     const ppapi::host::HostMessageContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (state_.state() == TCPSocketState::CLOSED)
     return PP_OK;
 
-  state_.DoTransition(TCPSocketState::CLOSE, true);
-#if defined(OS_CHROMEOS)
-  // Close the firewall hole, it is no longer needed.
-  firewall_hole_.reset();
-#endif  // defined(OS_CHROMEOS)
-  // Make sure we get no further callbacks from |socket_| or |ssl_socket_|.
-  if (socket_) {
-    socket_->Close();
-  } else if (ssl_socket_) {
-    ssl_socket_->Disconnect();
-  }
+  Close();
   return PP_OK;
 }
 
@@ -517,19 +640,37 @@
     const ppapi::host::HostMessageContext* context,
     PP_TCPSocket_Option name,
     const ppapi::SocketOptionData& value) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  // Options are only applied if |this| is currently being used as a client
+  // socket, or is going to be used as one - they are ignored for server
+  // sockets.
   switch (name) {
     case PP_TCPSOCKET_OPTION_NO_DELAY: {
       bool boolean_value = false;
       if (!value.GetBool(&boolean_value))
         return PP_ERROR_BADARGUMENT;
 
-      // If the socket is already connected, proxy the value to TCPSocket.
-      if (state_.state() == TCPSocketState::CONNECTED)
-        return socket_->SetNoDelay(boolean_value) ? PP_OK : PP_ERROR_FAILED;
+      // If |connected_socket_| is connecting or has connected, pass the setting
+      // along.
+      if (connected_socket_.is_bound()) {
+        connected_socket_->SetNoDelay(
+            boolean_value,
+            // Callback that converts a bool to a net error code which it then
+            // passes to the common completion callback routine.
+            base::BindOnce(
+                [](base::OnceCallback<void(int)> completion_callback,
+                   bool success) {
+                  std::move(completion_callback)
+                      .Run(success ? net::OK : net::ERR_FAILED);
+                },
+                CreateCompletionCallback<
+                    PpapiPluginMsg_TCPSocket_SetOptionReply>(context)));
+        return PP_OK_COMPLETIONPENDING;
+      }
 
-      // TCPSocket instance is not yet created. So remember the value here.
+      // TCPConnectedSocket instance is not yet created. So remember the value
+      // here.
       if (boolean_value) {
         socket_options_ |= SOCKET_OPTION_NODELAY;
       } else {
@@ -543,10 +684,14 @@
           integer_value > TCPSocketResourceConstants::kMaxSendBufferSize)
         return PP_ERROR_BADARGUMENT;
 
-      // If the socket is already connected, proxy the value to TCPSocket.
-      if (state_.state() == TCPSocketState::CONNECTED) {
-        return NetErrorToPepperError(
-            socket_->SetSendBufferSize(integer_value));
+      // If |connected_socket_| is connecting or has connected, pass the setting
+      // along.
+      if (connected_socket_.is_bound()) {
+        connected_socket_->SetSendBufferSize(
+            integer_value,
+            CreateCompletionCallback<PpapiPluginMsg_TCPSocket_SetOptionReply>(
+                context));
+        return PP_OK_COMPLETIONPENDING;
       }
 
       // TCPSocket instance is not yet created. So remember the value here.
@@ -560,13 +705,18 @@
           integer_value > TCPSocketResourceConstants::kMaxReceiveBufferSize)
         return PP_ERROR_BADARGUMENT;
 
-      // If the socket is already connected, proxy the value to TCPSocket.
-      if (state_.state() == TCPSocketState::CONNECTED) {
-        return NetErrorToPepperError(
-            socket_->SetReceiveBufferSize(integer_value));
+      // If |connected_socket_| is connecting or has connected, pass the setting
+      // along.
+      if (connected_socket_.is_bound()) {
+        connected_socket_->SetReceiveBufferSize(
+            integer_value,
+            CreateCompletionCallback<PpapiPluginMsg_TCPSocket_SetOptionReply>(
+                context));
+        return PP_OK_COMPLETIONPENDING;
       }
 
-      // TCPSocket instance is not yet created. So remember the value here.
+      // TCPConnectedSocket instance is not yet created. So remember the value
+      // here.
       socket_options_ |= SOCKET_OPTION_RCVBUF_SIZE;
       rcvbuf_size_ = integer_value;
       return PP_OK;
@@ -576,469 +726,498 @@
       return PP_ERROR_BADARGUMENT;
     }
   }
+  return PP_OK;
 }
 
-void PepperTCPSocketMessageFilter::DoBind(
-    const ppapi::host::ReplyMessageContext& context,
-    const PP_NetAddress_Private& net_addr) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  if (state_.IsPending(TCPSocketState::BIND)) {
-    SendBindError(context, PP_ERROR_INPROGRESS);
-    return;
-  }
-  if (!state_.IsValidTransition(TCPSocketState::BIND)) {
-    SendBindError(context, PP_ERROR_FAILED);
-    return;
-  }
-
-  int pp_result = PP_OK;
-  do {
-    net::IPAddressBytes address;
-    uint16_t port;
-    if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(
-            net_addr, &address, &port)) {
-      pp_result = PP_ERROR_ADDRESS_INVALID;
-      break;
-    }
-    net::IPEndPoint bind_addr(net::IPAddress(address), port);
-
-    DCHECK(!socket_->IsValid());
-    pp_result = NetErrorToPepperError(socket_->Open(bind_addr.GetFamily()));
-    if (pp_result != PP_OK)
-      break;
-
-    pp_result = NetErrorToPepperError(socket_->SetDefaultOptionsForServer());
-    if (pp_result != PP_OK)
-      break;
-
-    pp_result = NetErrorToPepperError(socket_->Bind(bind_addr));
-    if (pp_result != PP_OK)
-      break;
-
-    net::IPEndPoint ip_end_point_local;
-    pp_result =
-        NetErrorToPepperError(socket_->GetLocalAddress(&ip_end_point_local));
-    if (pp_result != PP_OK)
-      break;
-
-    PP_NetAddress_Private local_addr =
-        NetAddressPrivateImpl::kInvalidNetAddress;
-    if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
-            ip_end_point_local.address().bytes(), ip_end_point_local.port(),
-            &local_addr)) {
-      pp_result = PP_ERROR_ADDRESS_INVALID;
-      break;
-    }
-
-    SendBindReply(context, PP_OK, local_addr);
-    state_.DoTransition(TCPSocketState::BIND, true);
-    return;
-  } while (false);
-  if (socket_->IsValid())
-    socket_->Close();
-  SendBindError(context, pp_result);
-  state_.DoTransition(TCPSocketState::BIND, false);
-}
-
-void PepperTCPSocketMessageFilter::HostResolvingStarted(
-    const ppapi::host::ReplyMessageContext& context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!state_.IsValidTransition(TCPSocketState::CONNECT)) {
-    NOTREACHED() << "This shouldn't be reached since the renderer only tries "
-                 << "to connect once.";
-    SendConnectError(context, PP_ERROR_FAILED);
-    return;
-  }
-
-  state_.SetPendingTransition(TCPSocketState::CONNECT);
-  address_index_ = 0;
-  address_list_.clear();
-  host_resolve_context_ = context;
-}
-
-void PepperTCPSocketMessageFilter::DoConnectWithNetAddress(
-    const ppapi::host::ReplyMessageContext& context,
-    const PP_NetAddress_Private& net_addr) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  if (!state_.IsValidTransition(TCPSocketState::CONNECT)) {
-    SendConnectError(context, PP_ERROR_FAILED);
-    return;
-  }
-
-  state_.SetPendingTransition(TCPSocketState::CONNECT);
-
-  net::IPAddressBytes address;
-  uint16_t port;
-  if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(
-          net_addr, &address, &port)) {
-    state_.CompletePendingTransition(false);
-    SendConnectError(context, PP_ERROR_ADDRESS_INVALID);
-    return;
-  }
-
-  // Copy the single IPEndPoint to address_list_.
-  address_index_ = 0;
-  address_list_.clear();
-  address_list_.push_back(net::IPEndPoint(net::IPAddress(address), port));
-  StartConnect(context);
-}
-
-void PepperTCPSocketMessageFilter::DoWrite(
-    const ppapi::host::ReplyMessageContext& context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(write_buffer_base_.get());
-  DCHECK(write_buffer_.get());
-  DCHECK_GT(write_buffer_->BytesRemaining(), 0);
+void PepperTCPSocketMessageFilter::TryRead() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(state_.IsConnected());
+  DCHECK(pending_read_context_.is_valid());
+  DCHECK_GT(pending_read_size_, 0u);
+  DCHECK(!pending_read_on_unthrottle_);
 
-  int net_result = net::ERR_FAILED;
-  if (socket_) {
-    DCHECK_EQ(state_.state(), TCPSocketState::CONNECTED);
-    net_result = socket_->Write(
-        write_buffer_.get(), write_buffer_->BytesRemaining(),
-        base::BindOnce(&PepperTCPSocketMessageFilter::OnWriteCompleted,
-                       base::Unretained(this), context),
-        static_cast<net::NetworkTrafficAnnotationTag>(
-            pepper_socket_utils::PepperTCPNetworkAnnotationTag()));
-  } else if (ssl_socket_) {
-    DCHECK_EQ(state_.state(), TCPSocketState::SSL_CONNECTED);
-    net_result = ssl_socket_->Write(
-        write_buffer_.get(), write_buffer_->BytesRemaining(),
-        base::BindOnce(&PepperTCPSocketMessageFilter::OnWriteCompleted,
-                       base::Unretained(this), context),
-        static_cast<net::NetworkTrafficAnnotationTag>(
-            pepper_socket_utils::PepperTCPNetworkAnnotationTag()));
+  if (is_throttled_) {
+    pending_read_on_unthrottle_ = true;
+    return;
   }
-  if (net_result != net::ERR_IO_PENDING)
-    OnWriteCompleted(context, net_result);
+
+  // This loop's body will generally run only once, unless there's a read error,
+  // in which case, it will start over, to re-apply the initial logic.
+  while (true) {
+    // As long as the read stream is still open, try to read data, even if a
+    // read error has been received from the SocketObserver, as there may still
+    // be data on the pipe.
+    if (!receive_stream_.is_valid()) {
+      // If no read error has been received yet, wait to receive one through
+      // the SocketObserver interface.
+      if (pending_read_pp_error_ == PP_OK_COMPLETIONPENDING) {
+        DCHECK(socket_observer_binding_.is_bound());
+        break;
+      }
+
+      // Otherwise, pass along the read error.
+      SendReadError(pending_read_pp_error_);
+      // If the socket was closed gracefully, only return OK for a single
+      // read.
+      if (pending_read_pp_error_ == PP_OK)
+        pending_read_pp_error_ = PP_ERROR_FAILED;
+      break;
+    }
+
+    DCHECK(read_watcher_);
+    const void* buffer = nullptr;
+    uint32_t num_bytes = 0;
+    int mojo_result = receive_stream_->BeginReadData(&buffer, &num_bytes,
+                                                     MOJO_READ_DATA_FLAG_NONE);
+    if (mojo_result == MOJO_RESULT_SHOULD_WAIT) {
+      read_watcher_->ArmOrNotify();
+      break;
+    }
+
+    // On a Mojo pipe error (which may indicate a graceful close, network error,
+    // or network service crash), close read pipe and restart the loop.
+    if (mojo_result != MOJO_RESULT_OK) {
+      read_watcher_.reset();
+      receive_stream_.reset();
+      continue;
+    }
+
+    // This is guaranteed by Mojo.
+    DCHECK_GT(num_bytes, 0u);
+
+    uint32_t bytes_to_copy = std::min(num_bytes, pending_read_size_);
+    SendReadReply(PP_OK, std::string(reinterpret_cast<const char*>(buffer),
+                                     bytes_to_copy));
+    receive_stream_->EndReadData(bytes_to_copy);
+    break;
+  }
 }
 
-void PepperTCPSocketMessageFilter::DoListen(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t backlog) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+void PepperTCPSocketMessageFilter::TryWrite() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(state_.IsConnected());
+  DCHECK(pending_write_context_.is_valid());
+  DCHECK(!pending_write_data_.empty());
+  DCHECK_LT(pending_write_bytes_written_, pending_write_data_.size());
 
-  if (state_.IsPending(TCPSocketState::LISTEN)) {
-    SendListenReply(context, PP_ERROR_INPROGRESS);
-    return;
+  // The structure of this code largely mirrors TryRead() above, with a couple
+  // differences. The loop will repeat until all bytes are written, there's an
+  // error, or no more buffer space is available. Also, it's possible for a
+  // Mojo write to succeed, but a write to the underlying socket to fail. In
+  // that case, the failure might not be returned to the caller until it tries
+  // to write again. Since the socket APIs themselves don't guarantee that
+  // data has been successfully received by the remote server on success, this
+  // should not cause unexpected problems for consumers.
+  while (true) {
+    if (!send_stream_.is_valid()) {
+      if (pending_write_pp_error_ == PP_OK_COMPLETIONPENDING) {
+        DCHECK(socket_observer_binding_.is_bound());
+        break;
+      }
+      SendWriteReply(pending_write_pp_error_);
+      // Mirror handling of "OK" read errors, only sending "OK" for a single
+      // write, though getting "OK" from a write is probably nonsense, anyways.
+      if (pending_write_pp_error_ == PP_OK)
+        pending_write_pp_error_ = PP_ERROR_FAILED;
+      break;
+    }
+
+    DCHECK(write_watcher_);
+
+    uint32_t num_bytes =
+        pending_write_data_.size() - pending_write_bytes_written_;
+    DCHECK_GT(num_bytes, 0u);
+    int mojo_result = send_stream_->WriteData(
+        pending_write_data_.data() + pending_write_bytes_written_, &num_bytes,
+        MOJO_WRITE_DATA_FLAG_NONE);
+    if (mojo_result == MOJO_RESULT_SHOULD_WAIT) {
+      write_watcher_->ArmOrNotify();
+      break;
+    }
+
+    // On a Mojo pipe error (which may indicate a graceful close, network error,
+    // or network service crash), close write pipe and restart the loop.
+    if (mojo_result != MOJO_RESULT_OK) {
+      write_watcher_.reset();
+      send_stream_.reset();
+      continue;
+    }
+
+    // This is guaranteed by Mojo.
+    DCHECK_GT(num_bytes, 0u);
+
+    pending_write_bytes_written_ += num_bytes;
+    // If all bytes were written, nothing left to do.
+    if (pending_write_bytes_written_ == pending_write_data_.size()) {
+      SendWriteReply(pending_write_bytes_written_);
+      break;
+    }
   }
-  if (!state_.IsValidTransition(TCPSocketState::LISTEN)) {
-    SendListenReply(context, PP_ERROR_FAILED);
-    return;
-  }
-
-  int32_t pp_result = NetErrorToPepperError(socket_->Listen(backlog));
-#if defined(OS_CHROMEOS)
-  OpenFirewallHole(context, pp_result);
-#else
-  OnListenCompleted(context, pp_result);
-#endif
-}
-
-void PepperTCPSocketMessageFilter::OnResolveCompleted(
-    int net_result,
-    const base::Optional<net::AddressList>& resolved_addresses) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!host_resolve_context_.is_valid())
-    return;
-
-  ppapi::host::ReplyMessageContext context = host_resolve_context_;
-  host_resolve_context_ = ppapi::host::ReplyMessageContext();
-
-  if (!state_.IsPending(TCPSocketState::CONNECT)) {
-    DCHECK(state_.state() == TCPSocketState::CLOSED);
-    SendConnectError(context, PP_ERROR_FAILED);
-    return;
-  }
-
-  if (net_result != net::OK) {
-    SendConnectError(context, NetErrorToPepperError(net_result));
-    state_.CompletePendingTransition(false);
-    return;
-  }
-
-  address_list_ = resolved_addresses.value();
-  StartConnect(context);
 }
 
 void PepperTCPSocketMessageFilter::StartConnect(
-    const ppapi::host::ReplyMessageContext& context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    const ppapi::host::ReplyMessageContext& context,
+    const net::AddressList& address_list) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(state_.IsPending(TCPSocketState::CONNECT));
-  DCHECK_LT(address_index_, address_list_.size());
+  DCHECK(!address_list.empty());
 
-  if (!socket_->IsValid()) {
-    int net_result = socket_->Open(address_list_[address_index_].GetFamily());
-    if (net_result != net::OK) {
-      OnConnectCompleted(context, net_result);
+  network::mojom::SocketObserverPtr socket_observer;
+  socket_observer_binding_.Bind(mojo::MakeRequest(&socket_observer));
+
+  socket_observer_binding_.set_connection_error_handler(
+      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
+                     base::Unretained(this)));
+
+  network::mojom::TCPConnectedSocketOptionsPtr socket_options =
+      network::mojom::TCPConnectedSocketOptions::New();
+  socket_options->no_delay = !!(socket_options_ & SOCKET_OPTION_NODELAY);
+  if (socket_options_ & SOCKET_OPTION_RCVBUF_SIZE)
+    socket_options->receive_buffer_size = rcvbuf_size_;
+  if (socket_options_ & SOCKET_OPTION_SNDBUF_SIZE)
+    socket_options->send_buffer_size = sndbuf_size_;
+
+  network::mojom::NetworkContext::CreateTCPConnectedSocketCallback callback =
+      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+          base::BindOnce(&PepperTCPSocketMessageFilter::OnConnectCompleted,
+                         base::Unretained(this), context),
+          net::ERR_FAILED, base::nullopt, base::nullopt,
+          mojo::ScopedDataPipeConsumerHandle(),
+          mojo::ScopedDataPipeProducerHandle());
+  if (bound_socket_) {
+    bound_socket_->Connect(address_list, std::move(socket_options),
+                           mojo::MakeRequest(&connected_socket_),
+                           std::move(socket_observer), std::move(callback));
+  } else {
+    network::mojom::NetworkContext* network_context = GetNetworkContext();
+    if (!network_context) {
+      // This will delete |callback|, which will invoke OnConnectCompleted()
+      // with an error.
       return;
     }
+    network_context->CreateTCPConnectedSocket(
+        base::nullopt /* local_addr */, address_list, std::move(socket_options),
+        pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
+        mojo::MakeRequest(&connected_socket_), std::move(socket_observer),
+        std::move(callback));
   }
-
-  socket_->SetDefaultOptionsForClient();
-
-  if (!(socket_options_ & SOCKET_OPTION_NODELAY)) {
-    if (!socket_->SetNoDelay(false)) {
-      OnConnectCompleted(context, net::ERR_FAILED);
-      return;
-    }
-  }
-  if (socket_options_ & SOCKET_OPTION_RCVBUF_SIZE) {
-    int net_result = socket_->SetReceiveBufferSize(rcvbuf_size_);
-    if (net_result != net::OK) {
-      OnConnectCompleted(context, net_result);
-      return;
-    }
-  }
-  if (socket_options_ & SOCKET_OPTION_SNDBUF_SIZE) {
-    int net_result = socket_->SetSendBufferSize(sndbuf_size_);
-    if (net_result != net::OK) {
-      OnConnectCompleted(context, net_result);
-      return;
-    }
-  }
-
-  int net_result = socket_->Connect(
-      address_list_[address_index_],
-      base::BindOnce(&PepperTCPSocketMessageFilter::OnConnectCompleted,
-                     base::Unretained(this), context));
-  if (net_result != net::ERR_IO_PENDING)
-    OnConnectCompleted(context, net_result);
 }
 
 void PepperTCPSocketMessageFilter::OnConnectCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    int net_result,
+    const base::Optional<net::IPEndPoint>& local_addr,
+    const base::Optional<net::IPEndPoint>& peer_addr,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (!state_.IsPending(TCPSocketState::CONNECT)) {
-    DCHECK(state_.state() == TCPSocketState::CLOSED);
-    SendConnectError(context, PP_ERROR_FAILED);
+  int32_t pp_result = NetErrorToPepperError(net_result);
+
+  if (state_.state() == TCPSocketState::CLOSED) {
+    // If this is called as a result of Close() being invoked and closing the
+    // pipe, fail the request without doing anything.
+    DCHECK_EQ(net_result, net::ERR_FAILED);
+    SendConnectError(context, pp_result);
     return;
   }
 
-  int32_t pp_result = NetErrorToPepperError(net_result);
+  DCHECK(state_.IsPending(TCPSocketState::CONNECT));
+
   do {
     if (pp_result != PP_OK)
       break;
 
-    net::IPEndPoint ip_end_point_local;
-    net::IPEndPoint ip_end_point_remote;
-    pp_result =
-        NetErrorToPepperError(socket_->GetLocalAddress(&ip_end_point_local));
-    if (pp_result != PP_OK)
-      break;
-    pp_result =
-        NetErrorToPepperError(socket_->GetPeerAddress(&ip_end_point_remote));
-    if (pp_result != PP_OK)
-      break;
-
-    PP_NetAddress_Private local_addr =
+    PP_NetAddress_Private pp_local_addr =
         NetAddressPrivateImpl::kInvalidNetAddress;
-    PP_NetAddress_Private remote_addr =
+    PP_NetAddress_Private pp_remote_addr =
         NetAddressPrivateImpl::kInvalidNetAddress;
-    if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
-            ip_end_point_local.address().bytes(), ip_end_point_local.port(),
-            &local_addr) ||
+    if (!local_addr || !peer_addr ||
         !NetAddressPrivateImpl::IPEndPointToNetAddress(
-            ip_end_point_remote.address().bytes(), ip_end_point_remote.port(),
-            &remote_addr)) {
+            local_addr->address().bytes(), local_addr->port(),
+            &pp_local_addr) ||
+        !NetAddressPrivateImpl::IPEndPointToNetAddress(
+            peer_addr->address().bytes(), peer_addr->port(), &pp_remote_addr)) {
       pp_result = PP_ERROR_ADDRESS_INVALID;
       break;
     }
 
-    SendConnectReply(context, PP_OK, local_addr, remote_addr);
+    SetStreams(std::move(receive_stream), std::move(send_stream));
+
+    bound_socket_.reset();
+
+    SendConnectReply(context, PP_OK, pp_local_addr, pp_remote_addr);
     state_.CompletePendingTransition(true);
     return;
   } while (false);
 
-  if (version_ == ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
-    DCHECK_EQ(1u, address_list_.size());
+  // Handle errors.
 
-    SendConnectError(context, pp_result);
-    state_.CompletePendingTransition(false);
-  } else {
-    // We have to recreate |socket_| because it doesn't allow a second connect
-    // attempt. We won't lose any state such as bound address or set options,
-    // because in the private or v1.0 API, connect must be the first operation.
-    socket_.reset(new net::TCPSocket(nullptr, nullptr, net::NetLogSource()));
+  // This can happen even when the network service is behaving correctly, as
+  // we may see the |socket_observer_binding_| closed before receiving an
+  // error.
+  pending_read_pp_error_ = PP_OK_COMPLETIONPENDING;
+  pending_write_pp_error_ = PP_OK_COMPLETIONPENDING;
 
-    if (address_index_ + 1 < address_list_.size()) {
-      DCHECK_EQ(version_, ppapi::TCP_SOCKET_VERSION_PRIVATE);
-      address_index_++;
-      StartConnect(context);
-    } else {
-      SendConnectError(context, pp_result);
-      // In order to maintain backward compatibility, allow further attempts to
-      // connect the socket.
-      state_ = TCPSocketState(TCPSocketState::INITIAL);
-    }
+  Close();
+  SendConnectError(context, pp_result);
+
+  if (version_ != ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
+    // In order to maintain backward compatibility, allow further attempts
+    // to connect the socket.
+    state_ = TCPSocketState(TCPSocketState::INITIAL);
   }
 }
 
 void PepperTCPSocketMessageFilter::OnSSLHandshakeCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    int net_result,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream,
+    const base::Optional<net::SSLInfo>& ssl_info) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (!state_.IsPending(TCPSocketState::SSL_CONNECT)) {
-    DCHECK(state_.state() == TCPSocketState::CLOSED);
-    SendSSLHandshakeReply(context, PP_ERROR_FAILED);
+  int pp_result = NetErrorToPepperError(net_result);
+  if (state_.state() == TCPSocketState::CLOSED) {
+    // If this is called as a result of Close() being invoked and closing the
+    // pipe, fail the request without doing anything.
+    DCHECK_EQ(net_result, net::ERR_FAILED);
+    SendSSLHandshakeReply(context, pp_result, base::nullopt /* ssl_info */);
     return;
   }
 
-  SendSSLHandshakeReply(context, NetErrorToPepperError(net_result));
-  state_.CompletePendingTransition(net_result == net::OK);
-}
+  DCHECK(state_.IsPending(TCPSocketState::SSL_CONNECT));
 
-void PepperTCPSocketMessageFilter::OnReadCompleted(
-    const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(read_buffer_.get());
+  if (pp_result == PP_OK && !ssl_info)
+    pp_result = PP_ERROR_FAILED;
 
-  if (host_ && host_->IsThrottled(instance_)) {
-    pending_read_on_unthrottle_ = true;
-    pending_read_reply_message_context_ = context;
-    pending_read_net_result_ = net_result;
-    return;
-  }
+  state_.CompletePendingTransition(pp_result == PP_OK);
 
-  if (net_result > 0) {
-    SendReadReply(
-        context, PP_OK, std::string(read_buffer_->data(), net_result));
-  } else if (net_result == 0) {
-    end_of_file_reached_ = true;
-    SendReadReply(context, PP_OK, std::string());
+  if (pp_result != PP_OK) {
+    Close();
   } else {
-    SendReadError(context, NetErrorToPepperError(net_result));
+    SetStreams(std::move(receive_stream), std::move(send_stream));
   }
-  read_buffer_ = nullptr;
+
+  SendSSLHandshakeReply(context, pp_result, ssl_info);
 }
 
-void PepperTCPSocketMessageFilter::OnWriteCompleted(
+void PepperTCPSocketMessageFilter::OnBindCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(write_buffer_base_.get());
-  DCHECK(write_buffer_.get());
+    int net_result,
+    const base::Optional<net::IPEndPoint>& local_addr) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  // Note: For partial writes of 0 bytes, don't continue writing to avoid a
-  // likely infinite loop.
-  if (net_result > 0) {
-    write_buffer_->DidConsume(net_result);
-    if (write_buffer_->BytesRemaining() > 0 && state_.IsConnected()) {
-      DoWrite(context);
-      return;
-    }
+  int pp_result = NetErrorToPepperError(net_result);
+  if (state_.state() == TCPSocketState::CLOSED) {
+    // If this is called as a result of Close() being invoked and closing the
+    // pipe, fail the request without doing anything.
+    DCHECK_EQ(net_result, net::ERR_FAILED);
+    SendBindError(context, pp_result);
+    return;
   }
 
-  if (net_result >= 0)
-    SendWriteReply(context, write_buffer_->BytesConsumed());
-  else
-    SendWriteReply(context, NetErrorToPepperError(net_result));
+  DCHECK(bound_socket_);
+  DCHECK(state_.IsPending(TCPSocketState::BIND));
 
-  write_buffer_ = nullptr;
-  write_buffer_base_ = nullptr;
+  PP_NetAddress_Private bound_address =
+      NetAddressPrivateImpl::kInvalidNetAddress;
+  if (pp_result == PP_OK &&
+      (!local_addr || !NetAddressPrivateImpl::IPEndPointToNetAddress(
+                          local_addr->address().bytes(), local_addr->port(),
+                          &bound_address))) {
+    pp_result = PP_ERROR_ADDRESS_INVALID;
+  }
+
+  if (pp_result != PP_OK) {
+    bound_socket_.reset();
+  } else {
+    bind_output_ip_endpoint_ = *local_addr;
+  }
+
+  SendBindReply(context, pp_result, bound_address);
+  state_.CompletePendingTransition(pp_result == PP_OK);
 }
 
 void PepperTCPSocketMessageFilter::OnListenCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  SendListenReply(context, pp_result);
-  state_.DoTransition(TCPSocketState::LISTEN, pp_result == PP_OK);
-}
+    int net_result) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-#if defined(OS_CHROMEOS)
-void PepperTCPSocketMessageFilter::OpenFirewallHole(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_result) {
-  if (pp_result != PP_OK) {
+  int pp_result = NetErrorToPepperError(net_result);
+  if (state_.state() == TCPSocketState::CLOSED) {
+    // If this is called as a result of Close() being invoked and closing the
+    // pipe, fail the request without doing anything.
+    DCHECK_EQ(net_result, net::ERR_FAILED);
+    SendListenReply(context, pp_result);
     return;
   }
 
-  net::IPEndPoint local_addr;
-  // Has already been called successfully in DoBind().
-  socket_->GetLocalAddress(&local_addr);
-  pepper_socket_utils::FirewallHoleOpenCallback callback =
-      base::Bind(&PepperTCPSocketMessageFilter::OnFirewallHoleOpened, this,
-                 context, pp_result);
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::UI},
-      base::BindOnce(&pepper_socket_utils::OpenTCPFirewallHole, local_addr,
-                     callback));
-}
+  DCHECK(state_.IsPending(TCPSocketState::LISTEN));
 
-void PepperTCPSocketMessageFilter::OnFirewallHoleOpened(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t result,
-    std::unique_ptr<chromeos::FirewallHole> hole) {
-  LOG_IF(WARNING, !hole.get()) << "Firewall hole could not be opened.";
-  firewall_hole_.reset(hole.release());
-
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&PepperTCPSocketMessageFilter::OnListenCompleted, this,
-                     context, result));
-}
+#if defined(OS_CHROMEOS)
+  if (pp_result == PP_OK) {
+    OpenFirewallHole(context);
+    return;
+  }
 #endif  // defined(OS_CHROMEOS)
 
+  SendListenReply(context, pp_result);
+  state_.CompletePendingTransition(pp_result == PP_OK);
+  if (pp_result != PP_OK)
+    Close();
+}
+
 void PepperTCPSocketMessageFilter::OnAcceptCompleted(
     const ppapi::host::ReplyMessageContext& context,
-    int net_result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    network::mojom::SocketObserverRequest socket_observer_request,
+    int net_result,
+    const base::Optional<net::IPEndPoint>& remote_addr,
+    network::mojom::TCPConnectedSocketPtr connected_socket,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(pending_accept_);
 
   pending_accept_ = false;
-
   if (net_result != net::OK) {
     SendAcceptError(context, NetErrorToPepperError(net_result));
     return;
   }
 
-  DCHECK(accepted_socket_.get());
-
-  net::IPEndPoint ip_end_point_local;
-  PP_NetAddress_Private local_addr = NetAddressPrivateImpl::kInvalidNetAddress;
-  PP_NetAddress_Private remote_addr = NetAddressPrivateImpl::kInvalidNetAddress;
-
-  int32_t pp_result = NetErrorToPepperError(
-      accepted_socket_->GetLocalAddress(&ip_end_point_local));
-  if (pp_result != PP_OK) {
-    SendAcceptError(context, pp_result);
+  if (!remote_addr || !connected_socket.is_bound()) {
+    SendAcceptError(context, NetErrorToPepperError(net_result));
     return;
   }
+
+  DCHECK(socket_observer_request.is_pending());
+
+  PP_NetAddress_Private pp_remote_addr =
+      NetAddressPrivateImpl::kInvalidNetAddress;
+
   if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
-          ip_end_point_local.address().bytes(), ip_end_point_local.port(),
-          &local_addr) ||
-      !NetAddressPrivateImpl::IPEndPointToNetAddress(
-          accepted_address_.address().bytes(), accepted_address_.port(),
-          &remote_addr)) {
+          remote_addr->address().bytes(), remote_addr->port(),
+          &pp_remote_addr)) {
     SendAcceptError(context, PP_ERROR_ADDRESS_INVALID);
     return;
   }
 
+  PP_NetAddress_Private bound_address =
+      NetAddressPrivateImpl::kInvalidNetAddress;
+  bool success = NetAddressPrivateImpl::IPEndPointToNetAddress(
+      bind_output_ip_endpoint_.address().bytes(),
+      bind_output_ip_endpoint_.port(), &bound_address);
+  // This conversion should succeed, since it succeeded in OnBindComplete()
+  // already.
+  DCHECK(success);
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO},
+      base::BindOnce(&PepperTCPSocketMessageFilter::OnAcceptCompletedOnIOThread,
+                     this, context, connected_socket.PassInterface(),
+                     std::move(socket_observer_request),
+                     std::move(receive_stream), std::move(send_stream),
+                     bound_address, pp_remote_addr));
+}
+
+void PepperTCPSocketMessageFilter::OnAcceptCompletedOnIOThread(
+    const ppapi::host::ReplyMessageContext& context,
+    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+    network::mojom::SocketObserverRequest socket_observer_request,
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream,
+    PP_NetAddress_Private pp_local_addr,
+    PP_NetAddress_Private pp_remote_addr) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
   // |factory_| is guaranteed to be non-NULL here. Only those instances created
   // in CONNECTED state have a NULL |factory_|, while getting here requires
   // LISTENING state.
+  DCHECK(factory_);
+
   std::unique_ptr<ppapi::host::ResourceHost> host =
-      factory_->CreateAcceptedTCPSocket(instance_, version_,
-                                        std::move(accepted_socket_));
+      factory_->CreateAcceptedTCPSocket(
+          instance_, version_, std::move(connected_socket),
+          std::move(socket_observer_request), std::move(receive_stream),
+          std::move(send_stream));
   if (!host) {
     SendAcceptError(context, PP_ERROR_NOSPACE);
     return;
   }
+
   int pending_host_id =
       host_->GetPpapiHost()->AddPendingResourceHost(std::move(host));
-  if (pending_host_id)
-    SendAcceptReply(context, PP_OK, pending_host_id, local_addr, remote_addr);
-  else
+  if (pending_host_id) {
+    SendAcceptReply(context, PP_OK, pending_host_id, pp_local_addr,
+                    pp_remote_addr);
+  } else {
     SendAcceptError(context, PP_ERROR_NOSPACE);
+  }
 }
 
+void PepperTCPSocketMessageFilter::SetStreams(
+    mojo::ScopedDataPipeConsumerHandle receive_stream,
+    mojo::ScopedDataPipeProducerHandle send_stream) {
+  DCHECK(!read_watcher_);
+  DCHECK(!write_watcher_);
+
+  receive_stream_ = std::move(receive_stream);
+  read_watcher_ = std::make_unique<mojo::SimpleWatcher>(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
+  read_watcher_->Watch(
+      receive_stream_.get(),
+      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+      base::BindRepeating(
+          [](PepperTCPSocketMessageFilter* message_filter,
+             MojoResult /* result */,
+             const mojo::HandleSignalsState& /* state */) {
+            // TryRead will correctly handle both cases (data ready to be
+            // read, and the pipe was closed).
+            message_filter->TryRead();
+          },
+          base::Unretained(this)));
+
+  send_stream_ = std::move(send_stream);
+  write_watcher_ = std::make_unique<mojo::SimpleWatcher>(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
+  write_watcher_->Watch(
+      send_stream_.get(),
+      MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+      base::BindRepeating(
+          [](PepperTCPSocketMessageFilter* message_filter,
+             MojoResult /* result */,
+             const mojo::HandleSignalsState& /* state */) {
+            // TryRead will correctly handle both cases (data ready to be
+            // read, and the pipe was closed).
+            message_filter->TryWrite();
+          },
+          base::Unretained(this)));
+}
+
+#if defined(OS_CHROMEOS)
+void PepperTCPSocketMessageFilter::OpenFirewallHole(
+    const ppapi::host::ReplyMessageContext& context) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  pepper_socket_utils::FirewallHoleOpenCallback callback = base::BindRepeating(
+      &PepperTCPSocketMessageFilter::OnFirewallHoleOpened, this, context);
+  pepper_socket_utils::OpenTCPFirewallHole(bind_output_ip_endpoint_, callback);
+}
+
+void PepperTCPSocketMessageFilter::OnFirewallHoleOpened(
+    const ppapi::host::ReplyMessageContext& context,
+    std::unique_ptr<chromeos::FirewallHole> hole) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(state_.IsPending(TCPSocketState::LISTEN));
+  LOG_IF(WARNING, !hole.get()) << "Firewall hole could not be opened.";
+  firewall_hole_.reset(hole.release());
+
+  SendListenReply(context, PP_OK);
+  state_.CompletePendingTransition(true);
+}
+#endif  // defined(OS_CHROMEOS)
+
 void PepperTCPSocketMessageFilter::SendBindReply(
     const ppapi::host::ReplyMessageContext& context,
     int32_t pp_result,
@@ -1071,24 +1250,21 @@
 void PepperTCPSocketMessageFilter::SendConnectError(
     const ppapi::host::ReplyMessageContext& context,
     int32_t pp_error) {
-  SendConnectReply(context,
-                   pp_error,
-                   NetAddressPrivateImpl::kInvalidNetAddress,
+  SendConnectReply(context, pp_error, NetAddressPrivateImpl::kInvalidNetAddress,
                    NetAddressPrivateImpl::kInvalidNetAddress);
 }
 
 void PepperTCPSocketMessageFilter::SendSSLHandshakeReply(
     const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_result) {
+    int32_t pp_result,
+    const base::Optional<net::SSLInfo>& ssl_info) {
   ppapi::host::ReplyMessageContext reply_context(context);
   reply_context.params.set_result(pp_result);
   ppapi::PPB_X509Certificate_Fields certificate_fields;
   if (pp_result == PP_OK) {
-    // Our socket is guaranteed to be an SSL socket if we get here.
-    net::SSLInfo ssl_info;
-    ssl_socket_->GetSSLInfo(&ssl_info);
-    if (ssl_info.cert.get()) {
-      pepper_socket_utils::GetCertificateFields(*ssl_info.cert.get(),
+    DCHECK(ssl_info);
+    if (ssl_info->cert.get()) {
+      pepper_socket_utils::GetCertificateFields(*ssl_info->cert,
                                                 &certificate_fields);
     }
   }
@@ -1096,27 +1272,36 @@
             PpapiPluginMsg_TCPSocket_SSLHandshakeReply(certificate_fields));
 }
 
-void PepperTCPSocketMessageFilter::SendReadReply(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_result,
-    const std::string& data) {
-  ppapi::host::ReplyMessageContext reply_context(context);
-  reply_context.params.set_result(pp_result);
-  SendReply(reply_context, PpapiPluginMsg_TCPSocket_ReadReply(data));
+void PepperTCPSocketMessageFilter::SendReadReply(int32_t pp_result,
+                                                 const std::string& data) {
+  DCHECK(pending_read_context_.is_valid());
+  DCHECK_GT(pending_read_size_, 0u);
+
+  pending_read_context_.params.set_result(pp_result);
+  SendReply(pending_read_context_, PpapiPluginMsg_TCPSocket_ReadReply(data));
+
+  pending_read_context_ = ppapi::host::ReplyMessageContext();
+  pending_read_size_ = 0;
 }
 
-void PepperTCPSocketMessageFilter::SendReadError(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_error) {
-  SendReadReply(context, pp_error, std::string());
+void PepperTCPSocketMessageFilter::SendReadError(int32_t pp_error) {
+  SendReadReply(pp_error, std::string());
 }
 
-void PepperTCPSocketMessageFilter::SendWriteReply(
-    const ppapi::host::ReplyMessageContext& context,
-    int32_t pp_result) {
-  ppapi::host::ReplyMessageContext reply_context(context);
-  reply_context.params.set_result(pp_result);
-  SendReply(reply_context, PpapiPluginMsg_TCPSocket_WriteReply());
+void PepperTCPSocketMessageFilter::SendWriteReply(int32_t pp_result) {
+  DCHECK(pending_write_context_.is_valid());
+  DCHECK(!pending_write_data_.empty());
+  DCHECK(pp_result <= 0 ||
+         static_cast<uint32_t>(pp_result) == pending_write_data_.size());
+  DCHECK(pp_result <= 0 ||
+         static_cast<uint32_t>(pp_result) == pending_write_bytes_written_);
+
+  pending_write_context_.params.set_result(pp_result);
+  SendReply(pending_write_context_, PpapiPluginMsg_TCPSocket_WriteReply());
+
+  pending_write_context_ = ppapi::host::ReplyMessageContext();
+  pending_write_data_.clear();
+  pending_write_bytes_written_ = 0;
 }
 
 void PepperTCPSocketMessageFilter::SendListenReply(
@@ -1135,19 +1320,76 @@
     const PP_NetAddress_Private& remote_addr) {
   ppapi::host::ReplyMessageContext reply_context(context);
   reply_context.params.set_result(pp_result);
-  SendReply(reply_context,
-            PpapiPluginMsg_TCPSocket_AcceptReply(
-                pending_host_id, local_addr, remote_addr));
+  SendReply(reply_context, PpapiPluginMsg_TCPSocket_AcceptReply(
+                               pending_host_id, local_addr, remote_addr));
 }
 
 void PepperTCPSocketMessageFilter::SendAcceptError(
     const ppapi::host::ReplyMessageContext& context,
     int32_t pp_error) {
-  SendAcceptReply(context,
-                  pp_error,
-                  0,
+  SendAcceptReply(context, pp_error, 0,
                   NetAddressPrivateImpl::kInvalidNetAddress,
                   NetAddressPrivateImpl::kInvalidNetAddress);
 }
 
+void PepperTCPSocketMessageFilter::Close() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // Need to do this first, as destroying Mojo pipes may invoke some of the
+  // callbacks with failure messages.
+  state_.DoTransition(TCPSocketState::CLOSE, true);
+
+#if defined(OS_CHROMEOS)
+  // Close the firewall hole, it is no longer needed.
+  firewall_hole_.reset();
+#endif  // defined(OS_CHROMEOS)
+
+  // Make sure there are no further callbacks from Mojo, which could end up in a
+  // double free (Add ref on the UI thread, while a deletion is pending on the
+  // IO thread), and that they're closed on the correct thread.
+  bound_socket_.reset();
+  connected_socket_.reset();
+  tls_client_socket_.reset();
+  server_socket_.reset();
+  binding_.Close();
+  socket_observer_binding_.Close();
+
+  read_watcher_.reset();
+  receive_stream_.reset();
+  write_watcher_.reset();
+  send_stream_.reset();
+}
+
+network::mojom::NetworkContext*
+PepperTCPSocketMessageFilter::GetNetworkContext() const {
+  if (network_context_for_testing)
+    return network_context_for_testing;
+
+  RenderProcessHost* render_process_host =
+      RenderProcessHost::FromID(render_process_id_);
+  if (!render_process_host)
+    return nullptr;
+
+  return render_process_host->GetStoragePartition()->GetNetworkContext();
+}
+
+template <class ReturnMessage>
+base::OnceCallback<void(int result)>
+PepperTCPSocketMessageFilter::CreateCompletionCallback(
+    const ppapi::host::HostMessageContext* context) {
+  return mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+      base::BindOnce(&PepperTCPSocketMessageFilter::ReturnResult<ReturnMessage>,
+                     base::Unretained(this),
+                     context->MakeReplyMessageContext()),
+      net::ERR_FAILED);
+}
+
+template <class ReturnMessage>
+void PepperTCPSocketMessageFilter::ReturnResult(
+    ppapi::host::ReplyMessageContext context,
+    int net_result) {
+  context.params.set_result(NetErrorToPepperError(net_result));
+  SendReply(context, ReturnMessage());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.h b/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.h
index 03648b6..ecd25e9 100644
--- a/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.h
+++ b/content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.h
@@ -15,32 +15,27 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "build/build_config.h"
 #include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
-#include "content/browser/renderer_host/pepper/ssl_context_helper.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/address_list.h"
 #include "net/base/ip_endpoint.h"
-#include "net/socket/tcp_socket.h"
 #include "ppapi/c/pp_instance.h"
 #include "ppapi/c/ppb_tcp_socket.h"
 #include "ppapi/c/private/ppb_net_address_private.h"
 #include "ppapi/host/resource_message_filter.h"
 #include "ppapi/shared_impl/ppb_tcp_socket_shared.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/tcp_socket.mojom.h"
+#include "services/network/public/mojom/tls_socket.mojom.h"
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/network/firewall_hole.h"
-#include "content/public/browser/browser_thread.h"
 #endif  // defined(OS_CHROMEOS)
 
-namespace net {
-class DrainableIOBuffer;
-class IOBuffer;
-class SSLClientSocket;
-}
-
 namespace ppapi {
 class SocketOptionData;
 
@@ -54,21 +49,38 @@
 class BrowserPpapiHostImpl;
 class ContentBrowserPepperHostFactory;
 
+// Handles communication between Pepper and TCP socket Mojo interfaces. The Mojo
+// interfaces and all class variables live on the UI thread, while the class is
+// created on and receives IPCs on the IO thread (The IPCs are then passed to
+// the UI thread).
 class CONTENT_EXPORT PepperTCPSocketMessageFilter
     : public ppapi::host::ResourceMessageFilter,
       public BrowserPpapiHostImpl::InstanceObserver,
-      public network::mojom::ResolveHostClient {
+      public network::mojom::ResolveHostClient,
+      public network::mojom::SocketObserver {
  public:
+  // |factory| must be non-nullptr unless the consumer immediately calls
+  // SetConnectedSocket(). SetConnectedSocket() must be a separate method,
+  // because something must already be holding onto a reference to |this| when a
+  // task is posted to the UI thread (Which requires grabbing another reference,
+  // which could potentially be released before the constructor returns).
   PepperTCPSocketMessageFilter(ContentBrowserPepperHostFactory* factory,
                                BrowserPpapiHostImpl* host,
                                PP_Instance instance,
                                ppapi::TCPSocketVersion version);
 
-  // Used for creating already connected sockets.
-  PepperTCPSocketMessageFilter(BrowserPpapiHostImpl* host,
-                               PP_Instance instance,
-                               ppapi::TCPSocketVersion version,
-                               std::unique_ptr<net::TCPSocket> socket);
+  // Switches state to CONNECTED using the provided pipes. May only be called
+  // before any messages are received,
+  void SetConnectedSocket(
+      network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream);
+
+  // Sets a global NetworkContext object to be used instead of the real one for
+  // doing all network operations.
+  static void SetNetworkContextForTesting(
+      network::mojom::NetworkContext* network_context);
 
   static size_t GetNumInstances();
 
@@ -81,7 +93,14 @@
 
   ~PepperTCPSocketMessageFilter() override;
 
+  void SetConnectedSocketOnUIThread(
+      network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream);
+
   // ppapi::host::ResourceMessageFilter overrides.
+  void OnFilterDestroyed() override;
   scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
       const IPC::Message& message) override;
   int32_t OnResourceMessageReceived(
@@ -92,11 +111,21 @@
   void OnThrottleStateChanged(bool is_throttled) override;
   void OnHostDestroyed() override;
 
+  void ThrottleStateChangedOnUIThread(bool is_throttled);
+
   // network::mojom::ResolveHostClient overrides.
   void OnComplete(
       int result,
       const base::Optional<net::AddressList>& resolved_addresses) override;
 
+  // network::mojom::SocketObserver overrides.
+  void OnReadError(int net_error) override;
+  void OnWriteError(int net_error) override;
+
+  // Called either when the SocketObserver Mojo pipe has an error, or bad data
+  // is received from it.
+  void OnSocketObserverError();
+
   int32_t OnMsgBind(const ppapi::host::HostMessageContext* context,
                     const PP_NetAddress_Private& net_addr);
   int32_t OnMsgConnect(const ppapi::host::HostMessageContext* context,
@@ -123,38 +152,73 @@
                          PP_TCPSocket_Option name,
                          const ppapi::SocketOptionData& value);
 
-  void DoBind(const ppapi::host::ReplyMessageContext& context,
-              const PP_NetAddress_Private& net_addr);
-  void HostResolvingStarted(const ppapi::host::ReplyMessageContext& context);
-  void DoConnectWithNetAddress(const ppapi::host::ReplyMessageContext& context,
-                               const PP_NetAddress_Private& net_addr);
-  void DoWrite(const ppapi::host::ReplyMessageContext& context);
-  void DoListen(const ppapi::host::ReplyMessageContext& context,
-                int32_t backlog);
+  // Attempts to read up to |pending_read_size_| bytes from |receive_stream_|.
+  // If any bytes are read, or there's an error, returns that information to
+  // |pending_read_context_|.
+  void TryRead();
+
+  // Attempts to write |pending_write_data_| to |send_stream_|.
+  // |pending_write_bytes_written_| reflects how much of the data has been
+  // written to the stream so far. Once all bytes are written, or there's an
+  // error, returns that information to |pending_write_context_|.
+  void TryWrite();
 
   void OnResolveCompleted(
       int net_result,
       const base::Optional<net::AddressList>& resolved_addresses);
-  void StartConnect(const ppapi::host::ReplyMessageContext& context);
+
+  // Attempts to connect to all addresses in |address_list| in order.
+  void StartConnect(const ppapi::host::ReplyMessageContext& context,
+                    const net::AddressList& address_list);
 
   void OnConnectCompleted(const ppapi::host::ReplyMessageContext& context,
-                          int net_result);
-  void OnSSLHandshakeCompleted(const ppapi::host::ReplyMessageContext& context,
-                               int net_result);
-  void OnReadCompleted(const ppapi::host::ReplyMessageContext& context,
-                       int net_result);
-  void OnWriteCompleted(const ppapi::host::ReplyMessageContext& context,
-                        int net_result);
-  void OnAcceptCompleted(const ppapi::host::ReplyMessageContext& context,
-                         int net_result);
+                          int net_result,
+                          const base::Optional<net::IPEndPoint>& local_addr,
+                          const base::Optional<net::IPEndPoint>& peer_addr,
+                          mojo::ScopedDataPipeConsumerHandle receive_stream,
+                          mojo::ScopedDataPipeProducerHandle send_stream);
+
+  void OnSSLHandshakeCompleted(
+      const ppapi::host::ReplyMessageContext& context,
+      int net_result,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream,
+      const base::Optional<net::SSLInfo>& ssl_info);
 
   void OnListenCompleted(const ppapi::host::ReplyMessageContext& context,
-                         int32_t pp_result);
+                         int net_result);
+
+  void OnBindCompleted(const ppapi::host::ReplyMessageContext& context,
+                       int net_result,
+                       const base::Optional<net::IPEndPoint>& local_addr);
+
+  void OnAcceptCompleted(
+      const ppapi::host::ReplyMessageContext& context,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      int net_result,
+      const base::Optional<net::IPEndPoint>& remote_addr,
+      network::mojom::TCPConnectedSocketPtr connected_socket,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream);
+
+  void OnAcceptCompletedOnIOThread(
+      const ppapi::host::ReplyMessageContext& context,
+      network::mojom::TCPConnectedSocketPtrInfo connected_socket,
+      network::mojom::SocketObserverRequest socket_observer_request,
+      mojo::ScopedDataPipeConsumerHandle receive_stream,
+      mojo::ScopedDataPipeProducerHandle send_stream,
+      PP_NetAddress_Private pp_local_addr,
+      PP_NetAddress_Private pp_remote_addr);
+
+  // Sets the read/write streams and constructs watchers for them, which are not
+  // armed until there's an attempt to use them that can't complete
+  // synchronously.
+  void SetStreams(mojo::ScopedDataPipeConsumerHandle receive_stream,
+                  mojo::ScopedDataPipeProducerHandle send_stream);
+
 #if defined(OS_CHROMEOS)
-  void OpenFirewallHole(const ppapi::host::ReplyMessageContext& context,
-                        int32_t pp_result);
+  void OpenFirewallHole(const ppapi::host::ReplyMessageContext& context);
   void OnFirewallHoleOpened(const ppapi::host::ReplyMessageContext& context,
-                            int32_t result,
                             std::unique_ptr<chromeos::FirewallHole> hole);
 #endif  // defined(OS_CHROMEOS)
 
@@ -170,14 +234,13 @@
   void SendConnectError(const ppapi::host::ReplyMessageContext& context,
                         int32_t pp_error);
   void SendSSLHandshakeReply(const ppapi::host::ReplyMessageContext& context,
-                             int32_t pp_result);
-  void SendReadReply(const ppapi::host::ReplyMessageContext& context,
-                     int32_t pp_result,
-                     const std::string& data);
-  void SendReadError(const ppapi::host::ReplyMessageContext& context,
-                     int32_t pp_error);
-  void SendWriteReply(const ppapi::host::ReplyMessageContext& context,
-                      int32_t pp_result);
+                             int32_t pp_result,
+                             const base::Optional<net::SSLInfo>& ssl_info);
+  // The read and write reply messages use the |pending_*_context_| fields, and
+  // clear fields related to the pending read / write request as needed.
+  void SendReadReply(int32_t pp_result, const std::string& data);
+  void SendReadError(int32_t pp_error);
+  void SendWriteReply(int32_t pp_result);
   void SendListenReply(const ppapi::host::ReplyMessageContext& context,
                        int32_t pp_result);
   void SendAcceptReply(const ppapi::host::ReplyMessageContext& context,
@@ -188,23 +251,29 @@
   void SendAcceptError(const ppapi::host::ReplyMessageContext& context,
                        int32_t pp_error);
 
+  // Closes any open Mojo pipe, and prevents new ones from being opened.
+  void Close();
+
+  network::mojom::NetworkContext* GetNetworkContext() const;
+
   bool IsPrivateAPI() const {
     return version_ == ppapi::TCP_SOCKET_VERSION_PRIVATE;
   }
 
+  // These are used to create a callback that:
+  // 1) if invoked with a network error code, will pass a message of the
+  // requested type to |context| with the corresponding Pepper error.
+  // 2) If destroyed without being invoked, will pass a message of the requested
+  // type to |context| with PP_ERROR_FAILED.
+  template <class ReturnMessage>
+  base::OnceCallback<void(int net_result)> CreateCompletionCallback(
+      const ppapi::host::HostMessageContext* context);
+  template <class ReturnMessage>
+  void ReturnResult(ppapi::host::ReplyMessageContext context, int net_result);
+
   // The following fields are used on both the UI and IO thread.
   const ppapi::TCPSocketVersion version_;
 
-  // The following fields are used only on the UI thread.
-  const bool external_plugin_;
-
-  int render_process_id_;
-  int render_frame_id_;
-
-  // A reference to |this| must always be taken while |binding_| is bound to
-  // ensure that if the error callback is called the object is alive.
-  mojo::Binding<network::mojom::ResolveHostClient> binding_;
-
   // The following fields are used only on the IO thread.
   // Non-owning ptr.
   BrowserPpapiHostImpl* host_;
@@ -212,22 +281,33 @@
   ContentBrowserPepperHostFactory* factory_;
   PP_Instance instance_;
 
+  // The following fields are used only on the UI thread.
+  const bool external_plugin_;
+
+  // Mirrors state of host_->IsThrottled(), but is on UI thread.
+  bool is_throttled_;
+
+  int render_process_id_;
+  int render_frame_id_;
+
+  // A reference to |this| must always be taken while |binding_| is bound to
+  // ensure that if the error callback is called the object is alive.
+  mojo::Binding<network::mojom::ResolveHostClient> binding_;
+  mojo::Binding<network::mojom::SocketObserver> socket_observer_binding_;
+
   ppapi::TCPSocketState state_;
-  bool end_of_file_reached_;
 
   // This is the address requested to bind. Please note that this is not the
   // bound address. For example, |bind_input_addr_| may have port set to 0.
   // It is used to check permission for listening.
   PP_NetAddress_Private bind_input_addr_;
 
-#if defined(OS_CHROMEOS)
-  std::unique_ptr<chromeos::FirewallHole,
-                  content::BrowserThread::DeleteOnUIThread>
-      firewall_hole_;
-#endif  // defined(OS_CHROMEOS)
+  // The bound address.
+  net::IPEndPoint bind_output_ip_endpoint_;
 
-  // Used for DNS request.
-  std::unique_ptr<net::HostResolver::Request> request_;
+#if defined(OS_CHROMEOS)
+  std::unique_ptr<chromeos::FirewallHole> firewall_hole_;
+#endif  // defined(OS_CHROMEOS)
 
   // Bitwise-or of SocketOption flags. This stores the state about whether
   // each option is set before Connect() is called.
@@ -237,42 +317,52 @@
   int32_t rcvbuf_size_;
   int32_t sndbuf_size_;
 
-  // |address_list_| may store multiple addresses when
-  // PPB_TCPSocket_Private.Connect() is used, which involves name resolution.
-  // In that case, we will try each address in the list until a connection is
-  // successfully established.
-  net::AddressList address_list_;
-  // Where we are in the above list.
-  size_t address_index_;
   ppapi::host::ReplyMessageContext host_resolve_context_;
 
-  // Non-null unless an SSL connection is requested.
-  std::unique_ptr<net::TCPSocket> socket_;
-  // Non-null if an SSL connection is requested.
-  std::unique_ptr<net::SSLClientSocket> ssl_socket_;
+  // Holds socket if Bind() is called. Will be used to create a connected or
+  // server socket, depending on the next call.
+  network::mojom::TCPBoundSocketPtr bound_socket_;
+  // Holds socket if Connect() is called.
+  network::mojom::TCPConnectedSocketPtr connected_socket_;
+  // Holds socket if socket was upgraded to SSL.
+  network::mojom::TLSClientSocketPtr tls_client_socket_;
+  // Holds socket if Listen() is called.
+  network::mojom::TCPServerSocketPtr server_socket_;
 
-  scoped_refptr<net::IOBuffer> read_buffer_;
-
-  // TCPSocket::Write() may not always write the full buffer, but we would
-  // rather have our DoWrite() do so whenever possible. To do this, we may have
-  // to call the former multiple times for each of the latter. This entails
-  // using a DrainableIOBuffer, which requires an underlying base IOBuffer.
-  scoped_refptr<net::IOBuffer> write_buffer_base_;
-  scoped_refptr<net::DrainableIOBuffer> write_buffer_;
-  scoped_refptr<SSLContextHelper> ssl_context_helper_;
+  // Read/write pipes and their watchers. Both the watchers are configured so
+  // that they must be armed to receive a notification.
+  mojo::ScopedDataPipeConsumerHandle receive_stream_;
+  std::unique_ptr<mojo::SimpleWatcher> read_watcher_;
+  mojo::ScopedDataPipeProducerHandle send_stream_;
+  std::unique_ptr<mojo::SimpleWatcher> write_watcher_;
 
   bool pending_accept_;
-  std::unique_ptr<net::TCPSocket> accepted_socket_;
-  net::IPEndPoint accepted_address_;
 
+  uint32_t pending_read_size_;
+  ppapi::host::ReplyMessageContext pending_read_context_;
+  // This is set to an error other than PP_OK_COMPLETIONPENDING when a read
+  // error is received through the SocketObserver interface. If the
+  // SocketObserver interface is destroyed and this still hasn't been changed
+  // from its initial value of PP_OK_COMPLETIONPENDING, it's set to
+  // PP_ERROR_FAILED.
+  int pending_read_pp_error_;
   // If the plugin is throttled, we defer completing socket reads until
   // the plugin is unthrottled.
   bool pending_read_on_unthrottle_;
-  ppapi::host::ReplyMessageContext pending_read_reply_message_context_;
-  int pending_read_net_result_;
+
+  std::string pending_write_data_;
+  // Number of bytes from |pending_write_data_| that have already been written.
+  // Always less than the size of |pending_write_data_|.
+  size_t pending_write_bytes_written_;
+  ppapi::host::ReplyMessageContext pending_write_context_;
+  // This mirrors |pending_read_pp_error_|.
+  int pending_write_pp_error_;
 
   const bool is_potentially_secure_plugin_context_;
 
+  // Used in place of the StoragePartition's NetworkContext when non-null.
+  static network::mojom::NetworkContext* network_context_for_testing;
+
   DISALLOW_COPY_AND_ASSIGN(PepperTCPSocketMessageFilter);
 };
 
diff --git a/content/browser/renderer_host/pepper/ssl_context_helper.cc b/content/browser/renderer_host/pepper/ssl_context_helper.cc
deleted file mode 100644
index 1d6fb4c9..0000000
--- a/content/browser/renderer_host/pepper/ssl_context_helper.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/renderer_host/pepper/ssl_context_helper.h"
-
-#include "net/cert/cert_verifier.h"
-#include "net/cert/ct_policy_enforcer.h"
-#include "net/cert/multi_log_ct_verifier.h"
-#include "net/http/transport_security_state.h"
-
-namespace content {
-
-SSLContextHelper::SSLContextHelper() {}
-
-SSLContextHelper::~SSLContextHelper() {}
-
-net::CertVerifier* SSLContextHelper::GetCertVerifier() {
-  if (!cert_verifier_)
-    cert_verifier_ = net::CertVerifier::CreateDefault();
-  return cert_verifier_.get();
-}
-
-net::TransportSecurityState* SSLContextHelper::GetTransportSecurityState() {
-  if (!transport_security_state_)
-    transport_security_state_.reset(new net::TransportSecurityState());
-  return transport_security_state_.get();
-}
-
-net::CTVerifier* SSLContextHelper::GetCertTransparencyVerifier() {
-  if (!cert_transparency_verifier_)
-    cert_transparency_verifier_.reset(new net::MultiLogCTVerifier());
-  return cert_transparency_verifier_.get();
-}
-
-net::CTPolicyEnforcer* SSLContextHelper::GetCTPolicyEnforcer() {
-  if (!ct_policy_enforcer_)
-    ct_policy_enforcer_.reset(new net::DefaultCTPolicyEnforcer());
-  return ct_policy_enforcer_.get();
-}
-
-}  // namespace content
diff --git a/content/browser/renderer_host/pepper/ssl_context_helper.h b/content/browser/renderer_host/pepper/ssl_context_helper.h
deleted file mode 100644
index d2b880ec..0000000
--- a/content/browser/renderer_host/pepper/ssl_context_helper.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_RENDERER_HOST_PEPPER_SSL_CONTEXT_HELPER_H_
-#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_SSL_CONTEXT_HELPER_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "net/ssl/ssl_config_service.h"
-
-namespace net {
-class CertVerifier;
-class CTPolicyEnforcer;
-class CTVerifier;
-class TransportSecurityState;
-}
-
-namespace content {
-
-class SSLContextHelper : public base::RefCounted<SSLContextHelper> {
- public:
-  SSLContextHelper();
-
-  net::CertVerifier* GetCertVerifier();
-  net::TransportSecurityState* GetTransportSecurityState();
-  net::CTVerifier* GetCertTransparencyVerifier();
-  net::CTPolicyEnforcer* GetCTPolicyEnforcer();
-  const net::SSLConfig& ssl_config() { return ssl_config_; }
-
- private:
-  friend class base::RefCounted<SSLContextHelper>;
-
-  ~SSLContextHelper();
-
-  // This is lazily created. Users should use GetCertVerifier to retrieve it.
-  std::unique_ptr<net::CertVerifier> cert_verifier_;
-  // This is lazily created. Users should use GetTransportSecurityState to
-  // retrieve it.
-  std::unique_ptr<net::TransportSecurityState> transport_security_state_;
-  // This is lazily created. Users should use GetCertTransparencyVerifier to
-  // retrieve it.
-  std::unique_ptr<net::CTVerifier> cert_transparency_verifier_;
-  // This is lazily created. Users should use GetCTPolicyEnforcer to
-  // retrieve it.
-  std::unique_ptr<net::CTPolicyEnforcer> ct_policy_enforcer_;
-
-  // The default SSL configuration settings are used, as opposed to Chrome's SSL
-  // settings.
-  net::SSLConfig ssl_config_;
-
-  DISALLOW_COPY_AND_ASSIGN(SSLContextHelper);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_RENDERER_HOST_PEPPER_SSL_CONTEXT_HELPER_H_
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 07ec546a..41588b0 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -4336,6 +4336,14 @@
     return;
   }
 
+  if (!has_recorded_media_stream_frame_depth_metric_ && !visible_clients_ &&
+      media_stream_count_) {
+    UMA_HISTOGRAM_EXACT_LINEAR(
+        "BrowserRenderProcessHost.InvisibleMediaStreamFrameDepth", frame_depth_,
+        50);
+    has_recorded_media_stream_frame_depth_metric_ = true;
+  }
+
   const ChildProcessLauncherPriority priority(
       visible_clients_ > 0 || base::CommandLine::ForCurrentProcess()->HasSwitch(
                                   switches::kDisableRendererBackgrounding),
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 0f102fe3..f11aa82 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -888,6 +888,9 @@
 
   bool cleanup_corb_exception_for_plugin_upon_destruction_ = false;
 
+  // Fields for recording MediaStream UMA.
+  bool has_recorded_media_stream_frame_depth_metric_ = false;
+
   base::WeakPtrFactory<RenderProcessHostImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderProcessHostImpl);
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index e34de9d5..88340958 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -20,6 +20,7 @@
 #include "third_party/blink/public/platform/web_drag_operation.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/range/range.h"
 
 namespace blink {
 class WebMouseEvent;
@@ -155,6 +156,12 @@
   // currently focused frame.
   virtual void SelectRange(const gfx::Point& base, const gfx::Point& extent) {}
 
+#if defined(OS_MACOSX)
+  virtual void DidChangeTextSelection(const base::string16& text,
+                                      const gfx::Range& range,
+                                      size_t offset) {}
+#endif
+
   // Request the renderer to Move the caret to the new position.
   virtual void MoveCaret(const gfx::Point& extent) {}
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 2ed27d80..5c5bcdf 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -1561,7 +1561,16 @@
 // Test that the rendering timeout for newly loaded content fires when enough
 // time passes without receiving a new compositor frame. This test assumes
 // Surface Synchronization is off.
-TEST_F(RenderWidgetHostTest, NewContentRenderingTimeoutWithoutSurfaceSync) {
+// Disabled due to flakiness on Android.  See https://crbug.com/892700.
+#if defined(OS_ANDROID)
+#define MAYBE_NewContentRenderingTimeoutWithoutSurfaceSync \
+  DISABLED_NewContentRenderingTimeoutWithoutSurfaceSync
+#else
+#define MAYBE_NewContentRenderingTimeoutWithoutSurfaceSync \
+  NewContentRenderingTimeoutWithoutSurfaceSync
+#endif
+TEST_F(RenderWidgetHostTest,
+       NewContentRenderingTimeoutWithoutSurfaceSync_MAYBE) {
   // If Surface Synchronization is on, we have a separate code path for
   // cancelling new content rendering timeout that is tested separately.
   if (features::IsSurfaceSynchronizationEnabled())
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 72f5b4178..e155153e 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -299,6 +299,24 @@
   return GetTextInputManager()->GetTextSelection(this)->selected_text();
 }
 
+base::string16 RenderWidgetHostViewBase::GetSurroundingText() {
+  if (!GetTextInputManager())
+    return base::string16();
+  return GetTextInputManager()->GetTextSelection(this)->text();
+}
+
+gfx::Range RenderWidgetHostViewBase::GetSelectedRange() {
+  if (!GetTextInputManager())
+    return gfx::Range();
+  return GetTextInputManager()->GetTextSelection(this)->range();
+}
+
+size_t RenderWidgetHostViewBase::GetOffsetForSurroundingText() {
+  if (!GetTextInputManager())
+    return 0;
+  return GetTextInputManager()->GetTextSelection(this)->offset();
+}
+
 void RenderWidgetHostViewBase::SetBackgroundColor(SkColor color) {
   DCHECK(SkColorGetA(color) == SK_AlphaOPAQUE ||
          SkColorGetA(color) == SK_AlphaTRANSPARENT);
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 404d277..becc63fa 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -116,6 +116,9 @@
   void WasOccluded() override {}
   void SetIsInVR(bool is_in_vr) override;
   base::string16 GetSelectedText() override;
+  base::string16 GetSurroundingText() override;
+  gfx::Range GetSelectedRange() override;
+  size_t GetOffsetForSurroundingText() override;
   bool IsMouseLocked() override;
   bool LockKeyboard(base::Optional<base::flat_set<ui::DomCode>> codes) override;
   void SetBackgroundColor(SkColor color) override;
diff --git a/content/browser/renderer_host/render_widget_host_view_cocoa.h b/content/browser/renderer_host/render_widget_host_view_cocoa.h
index 12e531d..fc0105d 100644
--- a/content/browser/renderer_host/render_widget_host_view_cocoa.h
+++ b/content/browser/renderer_host/render_widget_host_view_cocoa.h
@@ -55,7 +55,6 @@
 @interface RenderWidgetHostViewCocoa
     : ToolTipBaseView<CommandDispatcherTarget,
                       RenderWidgetHostNSViewClientOwner,
-                      NSCandidateListTouchBarItemDelegate,
                       NSTextInputClient> {
  @private
   // The communications channel to the RenderWidgetHostViewMac. This pointer is
@@ -199,8 +198,6 @@
 @property(nonatomic, assign) NSRange markedRange;
 @property(nonatomic, assign) ui::TextInputType textInputType;
 
-@property(nonatomic, assign) NSSpellChecker* spellCheckerForTesting;
-
 // Common code path for handling begin gesture events. This helper method is
 // called via different codepaths based on OS version and SDK:
 // - On 10.11 and later, when linking with the 10.11 SDK, it is called from
diff --git a/content/browser/renderer_host/render_widget_host_view_cocoa.mm b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
index 7730617..ee6cde81 100644
--- a/content/browser/renderer_host/render_widget_host_view_cocoa.mm
+++ b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
@@ -21,12 +21,10 @@
 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
-#include "content/public/common/content_features.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
 #import "ui/base/clipboard/clipboard_util_mac.h"
 #import "ui/base/cocoa/appkit_utils.h"
 #include "ui/base/cocoa/cocoa_base_utils.h"
-#import "ui/base/cocoa/touch_bar_util.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/display/screen.h"
 #include "ui/events/event_utils.h"
@@ -54,9 +52,6 @@
 
 namespace {
 
-// Touch bar identifier.
-NSString* const kWebContentTouchBarId = @"web-content";
-
 // Whether a keyboard event has been reserved by OSX.
 BOOL EventIsReservedBySystem(NSEvent* event) {
   content::SystemHotkeyHelperMac* helper =
@@ -124,10 +119,6 @@
 @interface RenderWidgetHostViewCocoa () {
   bool keyboardLockActive_;
   base::Optional<base::flat_set<ui::DomCode>> lockedKeys_;
-
-  API_AVAILABLE(macos(10.12.2))
-  base::scoped_nsobject<NSCandidateListTouchBarItem> candidateListTouchBarItem_;
-  NSInteger textSuggestionsSequenceNumber_;
 }
 - (void)processedWheelEvent:(const blink::WebMouseWheelEvent&)event
                    consumed:(BOOL)consumed;
@@ -137,21 +128,13 @@
 - (void)windowDidBecomeKey:(NSNotification*)notification;
 - (void)windowDidResignKey:(NSNotification*)notification;
 - (void)sendViewBoundsInWindowToClient;
-- (void)requestTextSuggestions;
 - (void)sendWindowFrameInScreenToClient;
 - (bool)clientIsDisconnected;
-- (void)invalidateTouchBar API_AVAILABLE(macos(10.12.2));
-
-// NSCandidateListTouchBarItemDelegate implementation
-- (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem*)anItem
-     endSelectingCandidateAtIndex:(NSInteger)index
-    API_AVAILABLE(macos(10.12.2));
 @end
 
 @implementation RenderWidgetHostViewCocoa
 @synthesize markedRange = markedRange_;
 @synthesize textInputType = textInputType_;
-@synthesize spellCheckerForTesting = spellCheckerForTesting_;
 
 - (id)initWithClient:(RenderWidgetHostNSViewClient*)client
     withClientHelper:(RenderWidgetHostNSViewClientHelper*)clientHelper {
@@ -208,65 +191,12 @@
   client_->OnBoundsInWindowChanged(gfxViewBoundsInWindow, true);
 }
 
-- (void)requestTextSuggestions {
-  if (@available(macOS 10.12.2, *)) {
-    auto* touchBarItem = candidateListTouchBarItem_.get();
-    if (!touchBarItem)
-      return;
-    NSRange selectionRange = textSelectionRange_.ToNSRange();
-    NSString* selectionText = base::SysUTF16ToNSString(textSelectionText_);
-    selectionRange.location -= textSelectionOffset_;
-    NSSpellChecker* spell_checker = spellCheckerForTesting_
-                                        ? spellCheckerForTesting_
-                                        : [NSSpellChecker sharedSpellChecker];
-    textSuggestionsSequenceNumber_ = [spell_checker
-        requestCandidatesForSelectedRange:selectionRange
-                                 inString:selectionText
-                                    types:NSTextCheckingAllSystemTypes
-                                  options:nil
-                   inSpellDocumentWithTag:0
-                        completionHandler:^(
-                            NSInteger sequenceNumber,
-                            NSArray<NSTextCheckingResult*>* candidates) {
-                          dispatch_async(dispatch_get_main_queue(), ^{
-                            if (sequenceNumber !=
-                                textSuggestionsSequenceNumber_)
-                              return;
-                            [touchBarItem setCandidates:candidates
-                                       forSelectedRange:selectionRange
-                                               inString:selectionText];
-                          });
-                        }];
-  }
-}
-
 - (void)setTextSelectionText:(base::string16)text
                       offset:(size_t)offset
                        range:(gfx::Range)range {
   textSelectionText_ = text;
   textSelectionOffset_ = offset;
   textSelectionRange_ = range;
-  [self requestTextSuggestions];
-}
-
-- (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem*)anItem
-     endSelectingCandidateAtIndex:(NSInteger)index {
-  if (index == NSNotFound)
-    return;
-  NSTextCheckingResult* selectedResult = anItem.candidates[index];
-  NSRange replacementRange = selectedResult.range;
-  replacementRange.location += textSelectionOffset_;
-  [self insertText:selectedResult.replacementString
-      replacementRange:replacementRange];
-}
-
-- (void)setTextInputType:(ui::TextInputType)textInputType {
-  if (textInputType_ == textInputType)
-    return;
-  textInputType_ = textInputType;
-
-  if (@available(macOS 10.12.2, *))
-    [self invalidateTouchBar];
 }
 
 - (base::string16)selectedText {
@@ -1912,36 +1842,6 @@
   client_->RequestShutdown();
 }
 
-- (void)invalidateTouchBar {
-  candidateListTouchBarItem_.reset();
-  self.touchBar = nil;
-}
-
-- (NSTouchBar*)makeTouchBar {
-  if (textInputType_ != ui::TEXT_INPUT_TYPE_NONE &&
-      textInputType_ != ui::TEXT_INPUT_TYPE_PASSWORD &&
-      (base::FeatureList::IsEnabled(features::kTextSuggestionsTouchBar) ||
-       base::FeatureList::IsEnabled(features::kExperimentalUi))) {
-    candidateListTouchBarItem_.reset([[NSCandidateListTouchBarItem alloc]
-        initWithIdentifier:NSTouchBarItemIdentifierCandidateList]);
-    auto* candidateListItem = candidateListTouchBarItem_.get();
-
-    candidateListItem.delegate = self;
-    candidateListItem.client = self;
-    [self requestTextSuggestions];
-
-    base::scoped_nsobject<NSTouchBar> scopedTouchBar([[NSTouchBar alloc] init]);
-    auto* touchBar = scopedTouchBar.get();
-    touchBar.customizationIdentifier = ui::GetTouchBarId(kWebContentTouchBarId);
-    touchBar.templateItems = [NSSet setWithObject:candidateListTouchBarItem_];
-    touchBar.defaultItemIdentifiers =
-        @[ NSTouchBarItemIdentifierCandidateList ];
-    return scopedTouchBar.autorelease();
-  }
-
-  return [super makeTouchBar];
-}
-
 @end
 
 //
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 455a6ccb..94d6e793 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -627,6 +627,9 @@
     return;
   ns_view_bridge_->SetTextSelection(selection->text(), selection->offset(),
                                     selection->range());
+  if (host() && host()->delegate())
+    host()->delegate()->DidChangeTextSelection(
+        selection->text(), selection->range(), selection->offset());
 }
 
 bool RenderWidgetHostViewMac::ShouldWaitInPreCommit() {
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index f6549b3..ed5984e8 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -127,72 +127,6 @@
 
 @end
 
-@interface FakeTextCheckingResult : NSObject<NSCopying>
-@property(readonly) NSRange range;
-@property(readonly) NSString* replacementString;
-@end
-
-@implementation FakeTextCheckingResult {
-  base::scoped_nsobject<NSString> replacementString_;
-}
-@synthesize range = range_;
-
-+ (FakeTextCheckingResult*)resultWithRange:(NSRange)range
-                         replacementString:(NSString*)replacementString {
-  FakeTextCheckingResult* result =
-      [[[FakeTextCheckingResult alloc] init] autorelease];
-  result->range_ = range;
-  result->replacementString_.reset([replacementString retain]);
-  return result;
-}
-
-- (id)copyWithZone:(NSZone*)zone {
-  return
-      [[FakeTextCheckingResult resultWithRange:self.range
-                             replacementString:self.replacementString] retain];
-}
-
-- (NSString*)replacementString {
-  return replacementString_;
-}
-@end
-
-@interface FakeSpellChecker : NSObject
-@property NSInteger sequenceNumber;
-@end
-
-@implementation FakeSpellChecker {
-  base::mac::ScopedBlock<void (^)(NSInteger sequenceNumber,
-                                  NSArray<NSTextCheckingResult*>* candidates)>
-      lastCompletionHandler_;
-}
-@synthesize sequenceNumber = sequenceNumber_;
-
-- (NSInteger)
-requestCandidatesForSelectedRange:(NSRange)selectedRange
-                         inString:(NSString*)stringToCheck
-                            types:(NSTextCheckingTypes)checkingTypes
-                          options:
-                              (nullable NSDictionary<NSTextCheckingOptionKey,
-                                                     id>*)options
-           inSpellDocumentWithTag:(NSInteger)tag
-                completionHandler:
-                    (void (^__nullable)(NSInteger sequenceNumber,
-                                        NSArray<NSTextCheckingResult*>*
-                                            candidates))completionHandler
-    NS_AVAILABLE_MAC(10_12_2) {
-  sequenceNumber_ += 1;
-  lastCompletionHandler_.reset([completionHandler copy]);
-  return sequenceNumber_;
-}
-
-- (void (^)(NSInteger sequenceNumber,
-            NSArray<NSTextCheckingResult*>* candidates))lastCompletionHandler {
-  return lastCompletionHandler_;
-}
-
-@end
-
 namespace content {
 
 namespace {
@@ -1735,12 +1669,6 @@
   RenderWidgetHostImpl* tab_widget() { return widget_; }
   RenderWidgetHostViewCocoa* tab_cocoa_view() { return view_->cocoa_view(); }
 
-  API_AVAILABLE(macos(10.12.2))
-  NSCandidateListTouchBarItem* candidate_list_item() {
-    return [tab_cocoa_view().touchBar
-        itemForIdentifier:NSTouchBarItemIdentifierCandidateList];
-  }
-
  protected:
   MockRenderProcessHost* process_host_;
   MockRenderWidgetHostImpl* widget_;
@@ -2027,90 +1955,6 @@
   EXPECT_FALSE(message->monitor_request());
 }
 
-TEST_F(InputMethodMacTest, TouchBarTextSuggestionsDisabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(features::kTextSuggestionsTouchBar);
-  if (@available(macOS 10.12.2, *)) {
-    EXPECT_NSEQ(nil, candidate_list_item());
-    SetTextInputType(tab_view(), ui::TEXT_INPUT_TYPE_TEXT);
-    EXPECT_NSEQ(nil, candidate_list_item());
-  }
-}
-
-TEST_F(InputMethodMacTest, TouchBarTextSuggestionsPresence) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kTextSuggestionsTouchBar);
-  if (@available(macOS 10.12.2, *)) {
-    EXPECT_NSEQ(nil, candidate_list_item());
-    SetTextInputType(tab_view(), ui::TEXT_INPUT_TYPE_PASSWORD);
-    EXPECT_NSEQ(nil, candidate_list_item());
-    SetTextInputType(tab_view(), ui::TEXT_INPUT_TYPE_TEXT);
-    EXPECT_NSNE(nil, candidate_list_item());
-  }
-}
-
-TEST_F(InputMethodMacTest, TouchBarTextSuggestionsReplacement) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kTextSuggestionsTouchBar);
-  if (@available(macOS 10.12.2, *)) {
-    base::scoped_nsobject<FakeSpellChecker> spellChecker(
-        [[FakeSpellChecker alloc] init]);
-    tab_cocoa_view().spellCheckerForTesting =
-        static_cast<NSSpellChecker*>(spellChecker.get());
-
-    SetTextInputType(tab_view(), ui::TEXT_INPUT_TYPE_TEXT);
-    EXPECT_NSNE(nil, candidate_list_item());
-
-    FakeTextCheckingResult* fakeResult =
-        [FakeTextCheckingResult resultWithRange:NSMakeRange(0, 3)
-                              replacementString:@"foo"];
-
-    const base::string16 kOriginalString = base::UTF8ToUTF16("abcxxxghi");
-
-    // Change the selection once; requests completions from the spell checker.
-    tab_view()->SelectionChanged(kOriginalString, 3, gfx::Range(0, 0));
-
-    NSInteger firstSequenceNumber = [spellChecker sequenceNumber];
-    base::mac::ScopedBlock<void (^)(NSInteger sequenceNumber,
-                                    NSArray<NSTextCheckingResult*>* candidates)>
-        firstCompletionHandler([[spellChecker lastCompletionHandler] retain]);
-
-    EXPECT_NE(nil, (id)firstCompletionHandler.get());
-    EXPECT_EQ(0U, candidate_list_item().candidates.count);
-
-    // Instead of replying right away, change the selection again!
-    tab_view()->SelectionChanged(kOriginalString, 3, gfx::Range(3, 3));
-
-    EXPECT_NE(firstSequenceNumber, [spellChecker sequenceNumber]);
-
-    // Make sure that calling the stale completion handler is a no-op.
-    firstCompletionHandler.get()(
-        firstSequenceNumber,
-        @[ static_cast<NSTextCheckingResult*>(fakeResult) ]);
-    base::RunLoop().RunUntilIdle();
-    EXPECT_EQ(0U, candidate_list_item().candidates.count);
-
-    // But calling the current handler should work.
-    [spellChecker lastCompletionHandler](
-        [spellChecker sequenceNumber],
-        @[ static_cast<NSTextCheckingResult*>(fakeResult) ]);
-    base::RunLoop().RunUntilIdle();
-    EXPECT_EQ(1U, candidate_list_item().candidates.count);
-
-    base::RunLoop().RunUntilIdle();
-    MockWidgetInputHandler::MessageVector events =
-        host_->GetAndResetDispatchedMessages();
-    ASSERT_EQ("", GetMessageNames(events));
-
-    // Now, select that result.
-    [tab_cocoa_view() candidateListTouchBarItem:candidate_list_item()
-                   endSelectingCandidateAtIndex:0];
-    base::RunLoop().RunUntilIdle();
-    events = widget_->GetAndResetDispatchedMessages();
-    ASSERT_EQ("CommitText", GetMessageNames(events));
-  }
-}
-
 TEST_F(RenderWidgetHostViewMacTest, ClearCompositorFrame) {
   BrowserCompositorMac* browser_compositor = rwhv_mac_->BrowserCompositor();
   ui::Compositor* ui_compositor = browser_compositor->GetCompositor();
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 9952fc48..8d22338 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -287,6 +287,8 @@
   response_head_.load_timing.send_start = now;
   response_head_.load_timing.send_end = now;
 
+  devtools_attached_ = version->embedded_worker()->devtools_attached();
+
   // Note that we don't record worker preparation time in S13nServiceWorker
   // path for now. If we want to measure worker preparation time we can
   // calculate it from response_head_.service_worker_ready_time and
@@ -433,7 +435,7 @@
     body_as_blob_.Bind(std::move(response->blob->blob));
     mojo::ScopedDataPipeConsumerHandle data_pipe;
     int error = ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
-        &body_as_blob_, response->blob->size, resource_request_.headers,
+        &body_as_blob_, response->blob->size,
         base::BindOnce(&ServiceWorkerNavigationLoader::OnBlobReadingComplete,
                        weak_factory_.GetWeakPtr()),
         &data_pipe);
@@ -530,6 +532,10 @@
       !base::TimeTicks::IsConsistentAcrossProcesses())
     return;
 
+  // Don't record metrics when DevTools is attached to reduce noise.
+  if (devtools_attached_)
+    return;
+
   // Time between the request is made and the request is routed to this loader.
   UMA_HISTOGRAM_TIMES(
       "ServiceWorker.LoadTiming.MainFrame.MainResource."
diff --git a/content/browser/service_worker/service_worker_navigation_loader.h b/content/browser/service_worker/service_worker_navigation_loader.h
index 8f740b9..3e544aa 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.h
+++ b/content/browser/service_worker/service_worker_navigation_loader.h
@@ -186,6 +186,7 @@
   bool did_navigation_preload_ = false;
   network::ResourceResponseHead response_head_;
 
+  bool devtools_attached_ = false;
   blink::mojom::ServiceWorkerFetchEventTimingPtr fetch_event_timing_;
   base::TimeTicks completion_time_;
 
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index 29ed62539..3cb616e 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -621,6 +621,10 @@
 
   histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                       blink::ServiceWorkerStatusCode::kOk, 1);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      1);
 }
 
 TEST_F(ServiceWorkerNavigationLoaderTest, NoActiveWorker) {
@@ -638,6 +642,10 @@
 
   // No fetch event was dispatched.
   histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
 }
 
 // Test that the request body is passed to the fetch event.
@@ -663,6 +671,8 @@
 }
 
 TEST_F(ServiceWorkerNavigationLoaderTest, BlobResponse) {
+  base::HistogramTester histogram_tester;
+
   // Construct the blob to respond with.
   const std::string kResponseBody = "Here is sample text for the blob.";
   auto blob_data = std::make_unique<storage::BlobDataBuilder>("blob-id:myblob");
@@ -694,10 +704,18 @@
       mojo::BlockingCopyToString(client_.response_body_release(), &body));
   EXPECT_EQ(kResponseBody, body);
   EXPECT_EQ(net::OK, client_.completion_status().error_code);
+
+  // Test histogram of reading body.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      1);
 }
 
 // Tell the helper to respond with a non-existent Blob.
 TEST_F(ServiceWorkerNavigationLoaderTest, BrokenBlobResponse) {
+  base::HistogramTester histogram_tester;
+
   const std::string kBrokenUUID = "broken_uuid";
 
   // Create the broken blob.
@@ -724,9 +742,21 @@
   // the body.
   client_.RunUntilComplete();
   EXPECT_EQ(net::ERR_OUT_OF_MEMORY, client_.completion_status().error_code);
+
+  // Timing histograms shouldn't be recorded on broken response.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      0);
 }
 
 TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponse) {
+  base::HistogramTester histogram_tester;
+
   // Construct the Stream to respond with.
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
@@ -763,10 +793,18 @@
   EXPECT_TRUE(
       mojo::BlockingCopyToString(client_.response_body_release(), &response));
   EXPECT_EQ(kResponseBody, response);
+
+  // Test histogram of reading body.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      1);
 }
 
 // Test when a stream response body is aborted.
 TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponse_Abort) {
+  base::HistogramTester histogram_tester;
+
   // Construct the Stream to respond with.
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
@@ -801,10 +839,22 @@
   EXPECT_TRUE(
       mojo::BlockingCopyToString(client_.response_body_release(), &response));
   EXPECT_EQ(kResponseBody, response);
+
+  // Timing histograms shouldn't be recorded on abort.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      0);
 }
 
 // Test when the loader is cancelled while a stream response is being written.
 TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponseAndCancel) {
+  base::HistogramTester histogram_tester;
+
   // Construct the Stream to respond with.
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
@@ -846,6 +896,16 @@
   client_.RunUntilComplete();
   EXPECT_FALSE(data_pipe.consumer_handle.is_valid());
   EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
+
+  // Timing histograms shouldn't be recorded on cancel.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "ResponseReceivedToCompleted",
+      0);
 }
 
 // Test when the service worker responds with network fallback.
@@ -868,6 +928,12 @@
   EXPECT_FALSE(was_main_resource_load_failed_called_);
   histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                       blink::ServiceWorkerStatusCode::kOk, 1);
+
+  // Test histogram of network fallback.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "FetchHandlerEndToFallbackNetwork",
+      1);
 }
 
 // Test when the service worker rejects the FetchEvent.
@@ -885,6 +951,11 @@
   // Event dispatch still succeeded.
   histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                       blink::ServiceWorkerStatusCode::kOk, 1);
+  // Timing UMAs shouldn't be recorded when we receive an error response.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
 }
 
 // Test when dispatching the fetch event to the service worker failed.
@@ -904,6 +975,11 @@
   histogram_tester.ExpectUniqueSample(
       kHistogramMainResourceFetchEvent,
       blink::ServiceWorkerStatusCode::kErrorFailed, 1);
+  // Timing UMAs shouldn't be recorded when failed to dispatch an event.
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
 }
 
 // Test when the respondWith() promise resolves before the waitUntil() promise
@@ -957,6 +1033,10 @@
 
   // No fetch event was dispatched.
   histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0);
+  histogram_tester.ExpectTotalCount(
+      "ServiceWorker.LoadTiming.MainFrame.MainResource."
+      "StartToForwardServiceWorker",
+      0);
 }
 
 // Test responding to the fetch event with the navigation preload response.
diff --git a/content/browser/service_worker/service_worker_url_request_job.cc b/content/browser/service_worker/service_worker_url_request_job.cc
index 2b4ae78..472a726 100644
--- a/content/browser/service_worker/service_worker_url_request_job.cc
+++ b/content/browser/service_worker/service_worker_url_request_job.cc
@@ -586,7 +586,7 @@
   auto blob_builder =
       std::make_unique<storage::BlobDataBuilder>(base::GenerateGUID());
   for (const network::DataElement& element : (*body_->elements())) {
-    blob_builder->AppendIPCDataElement(element, nullptr,
+    blob_builder->AppendIPCDataElement(element,
                                        blob_storage_context_->registry());
   }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 169aa36..7e93694a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3246,6 +3246,15 @@
   focused_frame->GetFrameInputHandler()->SelectRange(base, extent);
 }
 
+#if defined(OS_MACOSX)
+void WebContentsImpl::DidChangeTextSelection(const base::string16& text,
+                                             const gfx::Range& range,
+                                             size_t offset) {
+  for (auto& observer : observers_)
+    observer.DidChangeTextSelection(text, range, offset);
+}
+#endif
+
 void WebContentsImpl::MoveCaret(const gfx::Point& extent) {
   RenderFrameHostImpl* focused_frame = GetFocusedFrame();
   if (!focused_frame)
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index ccd1485..83f28751 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -729,6 +729,11 @@
                           const base::Optional<base::string16>& value) override;
   void MoveRangeSelectionExtent(const gfx::Point& extent) override;
   void SelectRange(const gfx::Point& base, const gfx::Point& extent) override;
+#if defined(OS_MACOSX)
+  void DidChangeTextSelection(const base::string16& text,
+                              const gfx::Range& range,
+                              size_t offset) override;
+#endif
   void MoveCaret(const gfx::Point& extent) override;
   void AdjustSelectionByCharacterOffset(int start_adjust,
                                         int end_adjust,
diff --git a/content/browser/web_package/signed_exchange_request_handler.cc b/content/browser/web_package/signed_exchange_request_handler.cc
index 97e0b1e0..4bdb4e2b 100644
--- a/content/browser/web_package/signed_exchange_request_handler.cc
+++ b/content/browser/web_package/signed_exchange_request_handler.cc
@@ -66,7 +66,8 @@
   }
   if (signed_exchange_loader_->HasRedirectedToFallbackURL()) {
     signed_exchange_loader_ = nullptr;
-    std::move(callback).Run({});
+    std::move(fallback_callback)
+        .Run(false /* reset_subresource_loader_params */);
     return;
   }
 
diff --git a/content/browser/webauth/scoped_virtual_authenticator_environment.cc b/content/browser/webauth/scoped_virtual_authenticator_environment.cc
index 3f3d4a2d..482b3f0 100644
--- a/content/browser/webauth/scoped_virtual_authenticator_environment.cc
+++ b/content/browser/webauth/scoped_virtual_authenticator_environment.cc
@@ -96,7 +96,7 @@
   std::move(callback).Run();
 }
 
-std::unique_ptr<::device::FidoDiscovery>
+std::unique_ptr<::device::FidoDeviceDiscovery>
 ScopedVirtualAuthenticatorEnvironment::CreateFidoDiscovery(
     device::FidoTransportProtocol transport,
     ::service_manager::Connector* connector) {
diff --git a/content/browser/webauth/scoped_virtual_authenticator_environment.h b/content/browser/webauth/scoped_virtual_authenticator_environment.h
index 0c0bf6f..663738c 100644
--- a/content/browser/webauth/scoped_virtual_authenticator_environment.h
+++ b/content/browser/webauth/scoped_virtual_authenticator_environment.h
@@ -13,7 +13,7 @@
 #include "base/macros.h"
 #include "base/no_destructor.h"
 #include "content/common/content_export.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "third_party/blink/public/platform/modules/webauthn/virtual_authenticator.mojom.h"
 
@@ -53,7 +53,7 @@
   void ClearAuthenticators(ClearAuthenticatorsCallback callback) override;
 
   // ScopedFidoDiscoveryFactory:
-  std::unique_ptr<::device::FidoDiscovery> CreateFidoDiscovery(
+  std::unique_ptr<::device::FidoDeviceDiscovery> CreateFidoDiscovery(
       device::FidoTransportProtocol transport,
       ::service_manager::Connector* connector) override;
 
diff --git a/content/browser/webauth/virtual_discovery.cc b/content/browser/webauth/virtual_discovery.cc
index 1f37ee8..82e022b4 100644
--- a/content/browser/webauth/virtual_discovery.cc
+++ b/content/browser/webauth/virtual_discovery.cc
@@ -17,7 +17,7 @@
 VirtualFidoDiscovery::VirtualFidoDiscovery(
     ScopedVirtualAuthenticatorEnvironment* environment,
     ::device::FidoTransportProtocol transport)
-    : FidoDiscovery(transport), environment_(environment) {}
+    : FidoDeviceDiscovery(transport), environment_(environment) {}
 
 VirtualFidoDiscovery::~VirtualFidoDiscovery() {
   environment_->OnDiscoveryDestroyed(this);
@@ -28,7 +28,7 @@
   // The real implementation would never notify the client's observer about
   // devices before the client calls Start(), mimic the same behavior.
   if (is_start_requested()) {
-    FidoDiscovery::AddDevice(std::move(device));
+    FidoDeviceDiscovery::AddDevice(std::move(device));
   } else {
     devices_pending_discovery_start_.push_back(std::move(device));
   }
@@ -36,12 +36,12 @@
 
 bool VirtualFidoDiscovery::RemoveVirtualDevice(base::StringPiece device_id) {
   DCHECK(is_start_requested());
-  return ::device::FidoDiscovery::RemoveDevice(device_id);
+  return ::device::FidoDeviceDiscovery::RemoveDevice(device_id);
 }
 
 void VirtualFidoDiscovery::StartInternal() {
   for (auto& device : devices_pending_discovery_start_)
-    FidoDiscovery::AddDevice(std::move(device));
+    FidoDeviceDiscovery::AddDevice(std::move(device));
   devices_pending_discovery_start_.clear();
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/content/browser/webauth/virtual_discovery.h b/content/browser/webauth/virtual_discovery.h
index 7c57b04..b4c14fc 100644
--- a/content/browser/webauth/virtual_discovery.h
+++ b/content/browser/webauth/virtual_discovery.h
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
 #include "content/common/content_export.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 
 namespace device {
 class FidoDevice;
@@ -21,9 +21,10 @@
 
 class ScopedVirtualAuthenticatorEnvironment;
 
-// A fully automated FidoDiscovery implementation, which is disconnected from
-// the real world, and discovers VirtualFidoDevice instances.
-class CONTENT_EXPORT VirtualFidoDiscovery : public ::device::FidoDiscovery {
+// A fully automated FidoDeviceDiscovery implementation, which is disconnected
+// from the real world, and discovers VirtualFidoDevice instances.
+class CONTENT_EXPORT VirtualFidoDiscovery
+    : public ::device::FidoDeviceDiscovery {
  public:
   // The |environment| must outlive this instance.
   VirtualFidoDiscovery(ScopedVirtualAuthenticatorEnvironment* environment,
@@ -36,7 +37,7 @@
   bool RemoveVirtualDevice(base::StringPiece device_id);
 
  protected:
-  // FidoDiscovery:
+  // FidoDeviceDiscovery:
   void StartInternal() override;
 
  private:
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index 57bc30c..20c07c38 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -590,7 +590,7 @@
   // factory as one of the first steps. Here, the request should not have been
   // serviced at all, so the fake request should still be pending on the fake
   // factory.
-  auto hid_discovery = ::device::FidoDiscovery::Create(
+  auto hid_discovery = ::device::FidoDeviceDiscovery::Create(
       ::device::FidoTransportProtocol::kUsbHumanInterfaceDevice, nullptr);
   ASSERT_TRUE(!!hid_discovery);
 
diff --git a/content/browser/webui/web_ui_mojo_browsertest.cc b/content/browser/webui/web_ui_mojo_browsertest.cc
index 3f403159..0425adab 100644
--- a/content/browser/webui/web_ui_mojo_browsertest.cc
+++ b/content/browser/webui/web_ui_mojo_browsertest.cc
@@ -110,10 +110,23 @@
                       int bindings = BINDINGS_POLICY_MOJO_WEB_UI)
       : WebUIController(web_ui), run_loop_(run_loop) {
     web_ui->SetBindings(bindings);
-    WebUIDataSource* data_source = WebUIDataSource::Create("mojo-web-ui");
-    data_source->SetRequestFilter(base::Bind(&GetResource));
-    WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
-                         data_source);
+    {
+      WebUIDataSource* data_source = WebUIDataSource::Create("mojo-web-ui");
+      data_source->SetRequestFilter(base::BindRepeating(&GetResource));
+      WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
+                           data_source);
+    }
+    {
+      WebUIDataSource* data_source = WebUIDataSource::Create("dummy-web-ui");
+      data_source->SetRequestFilter(base::BindRepeating(
+          [](const std::string& id,
+             const WebUIDataSource::GotDataCallback& callback) {
+            callback.Run(new base::RefCountedString);
+            return true;
+          }));
+      WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
+                           data_source);
+    }
   }
 
  protected:
@@ -246,9 +259,9 @@
   TestWebUIControllerFactory* factory() { return &factory_; }
 
   void NavigateWithNewWebUI(const std::string& path) {
-    // Load an invalid URL first so that a new WebUI is set up when we load
+    // Load a dummy WebUI URL first so that a new WebUI is set up when we load
     // the URL we're actually interested in.
-    EXPECT_FALSE(NavigateToURL(shell(), GURL()));
+    EXPECT_TRUE(NavigateToURL(shell(), GURL("chrome://dummy-web-ui")));
 
     constexpr char kChromeUIMojoWebUIOrigin[] = "chrome://mojo-web-ui/";
     EXPECT_TRUE(NavigateToURL(shell(), GURL(kChromeUIMojoWebUIOrigin + path)));
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 52ad94e..10a4266 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -315,7 +315,6 @@
     "//media:shared_memory_support",
     "//media/base/ipc",
     "//media/capture",
-    "//media/capture/ipc",
     "//media/gpu:buildflags",
     "//media/gpu/ipc/client",
     "//media/gpu/ipc/common",
@@ -349,6 +348,7 @@
     "//ui/events/blink",
     "//ui/gfx",
     "//ui/gfx/geometry",
+    "//ui/gfx/geometry/mojo:struct_traits",
     "//ui/gfx/ipc",
     "//ui/gfx/ipc/color",
     "//ui/gfx/ipc/geometry",
diff --git a/content/common/render_frame_metadata.typemap b/content/common/render_frame_metadata.typemap
index 3d7a39e..da6d300 100644
--- a/content/common/render_frame_metadata.typemap
+++ b/content/common/render_frame_metadata.typemap
@@ -7,6 +7,7 @@
 traits_headers = [ "//content/common/render_frame_metadata_struct_traits.h" ]
 deps = [
   "//cc",
+  "//ui/gfx/geometry/mojo:struct_traits",
 ]
 sources = [
   "//content/common/render_frame_metadata_struct_traits.cc",
diff --git a/content/common/service_worker/service_worker_loader_helpers.cc b/content/common/service_worker/service_worker_loader_helpers.cc
index 4fece80..a05dbe61 100644
--- a/content/common/service_worker/service_worker_loader_helpers.cc
+++ b/content/common/service_worker/service_worker_loader_helpers.cc
@@ -44,71 +44,6 @@
   BlobCompleteCallback callback_;
 };
 
-// Sets |has_range_out| to true if |headers| specify a single range request, and
-// |offset_out| and |length_out| to the range. If the range has an unbounded
-// end, |length_out| is set to uint64_t's max value. Returns true on valid input
-// (regardless of |has_range_out|), and false if there is more than one range or
-// if the bounds overflow.
-bool ExtractSinglePartHttpRange(const net::HttpRequestHeaders& headers,
-                                bool* has_range_out,
-                                uint64_t* offset_out,
-                                uint64_t* length_out) {
-  std::string range_header;
-  *has_range_out = false;
-  if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header))
-    return true;
-
-  std::vector<net::HttpByteRange> ranges;
-  if (!net::HttpUtil::ParseRangeHeader(range_header, &ranges))
-    return true;
-
-  // Multi-part (or invalid) ranges are not supported.
-  if (ranges.size() != 1)
-    return false;
-
-  // Safely parse the single range to our more-sane output format.
-  *has_range_out = true;
-  const net::HttpByteRange& byte_range = ranges[0];
-
-  // The first byte must be non-negative.
-  if (byte_range.first_byte_position() < 0)
-    return false;
-
-  // The last byte can be -1 to specify unbounded end.
-  if (byte_range.last_byte_position() == -1) {
-    // The range [0, -1] is the same as the entire range (no range specified).
-    if (byte_range.first_byte_position() == 0 &&
-        byte_range.last_byte_position() == -1) {
-      *has_range_out = false;
-      return true;
-    }
-
-    // Otherwise, return the range with unbounded end.
-    *length_out = std::numeric_limits<uint64_t>::max();
-    return true;
-  }
-
-  // The last byte must be non-negative.
-  if (byte_range.last_byte_position() < 0)
-    return false;
-
-  uint64_t first_byte_position =
-      static_cast<uint64_t>(byte_range.first_byte_position());
-  uint64_t last_byte_position =
-      static_cast<uint64_t>(byte_range.last_byte_position());
-
-  base::CheckedNumeric<uint64_t> length = last_byte_position;
-  length -= first_byte_position;
-  length += 1;
-
-  if (!length.IsValid())
-    return false;
-
-  *offset_out = static_cast<uint64_t>(byte_range.first_byte_position());
-  *length_out = length.ValueOrDie();
-  return true;
-}
-
 }  // namespace
 
 // static
@@ -199,31 +134,18 @@
 int ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
     blink::mojom::BlobPtr* blob,
     uint64_t blob_size,
-    const net::HttpRequestHeaders& headers,
     base::OnceCallback<void(int)> on_blob_read_complete,
     mojo::ScopedDataPipeConsumerHandle* handle_out) {
-  bool byte_range_set = false;
-  uint64_t offset = 0;
-  uint64_t length = 0;
-  // We don't support multiple range requests in one single URL request,
-  // because we need to do multipart encoding here.
-  // TODO(falken): Support multipart byte range requests.
-  if (!ExtractSinglePartHttpRange(headers, &byte_range_set, &offset, &length))
-    return net::ERR_REQUEST_RANGE_NOT_SATISFIABLE;
-
+  // TODO(falken): Change to CreateDataPipe() and return an error if allocation
+  // failed.
   mojo::DataPipe data_pipe(blink::BlobUtils::GetDataPipeCapacity(blob_size));
   blink::mojom::BlobReaderClientPtr blob_reader_client;
   mojo::MakeStrongBinding(
       std::make_unique<BlobCompleteCaller>(std::move(on_blob_read_complete)),
       mojo::MakeRequest(&blob_reader_client));
 
-  if (byte_range_set) {
-    (*blob)->ReadRange(offset, length, std::move(data_pipe.producer_handle),
-                       std::move(blob_reader_client));
-  } else {
-    (*blob)->ReadAll(std::move(data_pipe.producer_handle),
-                     std::move(blob_reader_client));
-  }
+  (*blob)->ReadAll(std::move(data_pipe.producer_handle),
+                   std::move(blob_reader_client));
   *handle_out = std::move(data_pipe.consumer_handle);
   return net::OK;
 }
diff --git a/content/common/service_worker/service_worker_loader_helpers.h b/content/common/service_worker/service_worker_loader_helpers.h
index 60da247c..5b037576 100644
--- a/content/common/service_worker/service_worker_loader_helpers.h
+++ b/content/common/service_worker/service_worker_loader_helpers.h
@@ -40,14 +40,13 @@
       const network::ResourceRequest& original_request,
       const network::ResourceResponseHead& response_head);
 
-  // Reads |blob| using the range in |headers| (if any), writing into
-  // |handle_out|. Calls |on_blob_read_complete| when done or if an error
-  // occurred. Returns a net error code if the inputs were invalid and reading
-  // couldn't start. In that case |on_blob_read_complete| isn't called.
+  // Reads |blob| into |handle_out|. Calls |on_blob_read_complete| when done or
+  // if an error occurred. Currently this always returns net::OK but
+  // the plan is to return an error if reading couldn't start, in
+  // which case |on_blob_read_complete| isn't called.
   static int ReadBlobResponseBody(
       blink::mojom::BlobPtr* blob,
       uint64_t blob_size,
-      const net::HttpRequestHeaders& headers,
       base::OnceCallback<void(int net_error)> on_blob_read_complete,
       mojo::ScopedDataPipeConsumerHandle* handle_out);
 };
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index a7f73e6..e931fc0 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -40,7 +40,6 @@
 #include "media/base/audio_parameters.h"
 #include "media/base/channel_layout.h"
 #include "media/base/ipc/media_param_traits.h"
-#include "media/capture/ipc/capture_param_traits.h"
 #include "net/base/network_change_notifier.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "third_party/blink/public/common/manifest/web_display_mode.h"
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
index 31e7368..93e27a85 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
@@ -172,7 +172,6 @@
     // The initial value is MODERATE since a newly created connection has moderate bindings.
     private @ChildProcessImportance int mEffectiveImportance = ChildProcessImportance.MODERATE;
     private boolean mVisible;
-    private boolean mIntersectsViewport;
 
     @CalledByNative
     private static FileDescriptorInfo makeFdInfo(
@@ -468,14 +467,13 @@
         }
 
         // Add first and remove second.
-        boolean wasAddedToBindingManager = mVisible && mIntersectsViewport;
-        boolean shouldAddToBindingManager = visible && intersectsViewport;
-        if (shouldAddToBindingManager && !wasAddedToBindingManager) {
+        if (visible && !mVisible) {
             BindingManager manager = getBindingManager();
             if (mUseBindingManager && manager != null) {
                 manager.addConnection(connection);
             }
         }
+        mVisible = visible;
 
         if (mEffectiveImportance != newEffectiveImportance) {
             switch (newEffectiveImportance) {
@@ -521,8 +519,6 @@
         }
 
         mEffectiveImportance = newEffectiveImportance;
-        mVisible = visible;
-        mIntersectsViewport = intersectsViewport;
     }
 
     @CalledByNative
diff --git a/content/public/browser/render_widget_host_view.h b/content/public/browser/render_widget_host_view.h
index b691dd6..c662115 100644
--- a/content/public/browser/render_widget_host_view.h
+++ b/content/public/browser/render_widget_host_view.h
@@ -141,6 +141,16 @@
   // Returns the currently selected text.
   virtual base::string16 GetSelectedText() = 0;
 
+  // Returns part of the text on the page which includes the selected text plus
+  // possibly several characters before and after it.
+  virtual base::string16 GetSurroundingText() = 0;
+
+  // Returns the range of the selection in the page.
+  virtual gfx::Range GetSelectedRange() = 0;
+
+  // The offset of the surrounding text relative to the start of the total text.
+  virtual size_t GetOffsetForSurroundingText() = 0;
+
   // This only returns non-null on platforms that implement touch
   // selection editing (TSE), currently Aura and (soon) Android.
   // TODO(wjmaclean): update this comment when OOPIF TSE is implemented on
diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h
index 3cb21f578..89ff658d 100644
--- a/content/public/browser/web_contents_observer.h
+++ b/content/public/browser/web_contents_observer.h
@@ -25,6 +25,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/base/window_open_disposition.h"
+#include "ui/gfx/range/range.h"
 
 namespace blink {
 namespace mojom {
@@ -454,6 +455,11 @@
   // Invoked when theme color is changed to |theme_color|.
   virtual void DidChangeThemeColor(SkColor theme_color) {}
 
+  // Invoked when text selection is changed.
+  virtual void DidChangeTextSelection(const base::string16& text,
+                                      const gfx::Range& range,
+                                      size_t offset) {}
+
   // Invoked when media is playing or paused.  |id| is unique per player and per
   // RenderFrameHost.  There may be multiple players within a RenderFrameHost
   // and subsequently within a WebContents.  MediaStartedPlaying() will always
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 963de01..cc603a60 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -665,10 +665,6 @@
 // entire life of the process.
 const base::Feature kMacV2Sandbox{"MacV2Sandbox",
                                   base::FEATURE_ENABLED_BY_DEFAULT};
-//
-// Enables the suggested text touch bar for autocomplete in textfields.
-const base::Feature kTextSuggestionsTouchBar{"TextSuggestionsTouchBar",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // defined(OS_MACOSX)
 
 enum class VideoCaptureServiceConfiguration {
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 95b77a7..874fc98f 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -161,7 +161,6 @@
 CONTENT_EXPORT extern const base::Feature kDeviceMonitorMac;
 CONTENT_EXPORT extern const base::Feature kIOSurfaceCapturer;
 CONTENT_EXPORT extern const base::Feature kMacV2Sandbox;
-CONTENT_EXPORT extern const base::Feature kTextSuggestionsTouchBar;
 #endif  // defined(OS_MACOSX)
 
 // DON'T ADD RANDOM STUFF HERE. Put it in the main section above in
diff --git a/content/public/test/ppapi_test_utils.cc b/content/public/test/ppapi_test_utils.cc
index f74bb7a7..35ef116 100644
--- a/content/public/test/ppapi_test_utils.cc
+++ b/content/public/test/ppapi_test_utils.cc
@@ -11,6 +11,8 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "build/build_config.h"
+#include "content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h"
+#include "content/browser/renderer_host/pepper/pepper_tcp_socket_message_filter.h"
 #include "content/browser/renderer_host/pepper/pepper_udp_socket_message_filter.h"
 #include "content/public/common/content_constants.h"
 #include "content/public/common/content_switches.h"
@@ -145,6 +147,14 @@
   return RegisterPlugins(command_line, plugins);
 }
 
+void SetPepperTCPNetworkContextForTesting(
+    network::mojom::NetworkContext* network_context) {
+  content::PepperTCPServerSocketMessageFilter::SetNetworkContextForTesting(
+      network_context);
+  content::PepperTCPSocketMessageFilter::SetNetworkContextForTesting(
+      network_context);
+}
+
 void SetPepperUDPSocketCallackForTesting(
     const CreateUDPSocketCallback* create_udp_socket_callback) {
   content::PepperUDPSocketMessageFilter::SetCreateUDPSocketCallbackForTesting(
diff --git a/content/public/test/ppapi_test_utils.h b/content/public/test/ppapi_test_utils.h
index cbe1361..37bc819 100644
--- a/content/public/test/ppapi_test_utils.h
+++ b/content/public/test/ppapi_test_utils.h
@@ -48,6 +48,12 @@
     network::mojom::UDPSocketRequest socket_request,
     network::mojom::UDPSocketReceiverPtr socket_receiver)>;
 
+// Sets a NetworkContext to be used by the Pepper TCP classes for testing.
+// Passed in NetworkContext must remain valid until the method is called again
+// with a nullptr, to clear the callback.
+void SetPepperTCPNetworkContextForTesting(
+    network::mojom::NetworkContext* network_context);
+
 // Sets callback to be invoked when creating a UDPSocket for use by pepper.
 // Passed in callback must remain valid until the method is called again with
 // a nullptr, to clear the callback.
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index e62bc40..8c69919 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -747,6 +747,7 @@
     "//third_party/webrtc/pc:rtc_pc_base",
     "//third_party/webrtc/rtc_base:rtc_base",
     "//third_party/webrtc/rtc_base:rtc_task_queue",
+    "//third_party/webrtc/rtc_base:timeutils",
 
     # TODO(titovartem) remove dependency on WebRTC internals.
     "//third_party/webrtc/rtc_base/third_party/sigslot:sigslot",
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 57cd9e4..02f57f3 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -827,7 +827,7 @@
                              aria_rowcount);
     }
 
-    if (dst->role == ax::mojom::Role::kRow) {
+    if (ui::IsTableRowRole(dst->role)) {
       dst->AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex,
                            src.RowIndex());
       WebAXObject header = src.RowHeader();
@@ -836,35 +836,33 @@
                              header.AxID());
     }
 
-    if (dst->role == ax::mojom::Role::kCell ||
-        dst->role == ax::mojom::Role::kRowHeader ||
-        dst->role == ax::mojom::Role::kColumnHeader ||
-        dst->role == ax::mojom::Role::kRow) {
-      if (dst->role != ax::mojom::Role::kRow) {
-        dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex,
-                             src.CellColumnIndex());
-        dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
-                             src.CellColumnSpan());
-        dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex,
-                             src.CellRowIndex());
-        dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan,
-                             src.CellRowSpan());
+    if (ui::IsCellOrTableHeaderRole(dst->role)) {
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex,
+                           src.CellColumnIndex());
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
+                           src.CellColumnSpan());
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex,
+                           src.CellRowIndex());
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan,
+                           src.CellRowSpan());
 
-        int aria_colindex = src.AriaColumnIndex();
-        if (aria_colindex) {
-          dst->AddIntAttribute(ax::mojom::IntAttribute::kAriaCellColumnIndex,
-                               aria_colindex);
-        }
+      int aria_colindex = src.AriaColumnIndex();
+      if (aria_colindex) {
+        dst->AddIntAttribute(ax::mojom::IntAttribute::kAriaCellColumnIndex,
+                             aria_colindex);
       }
+    }
 
+    if (ui::IsCellOrTableHeaderRole(dst->role) ||
+        ui::IsTableRowRole(dst->role)) {
+      // aria-rowindex is supported on cells, headers and rows.
       int aria_rowindex = src.AriaRowIndex();
       if (aria_rowindex)
         dst->AddIntAttribute(ax::mojom::IntAttribute::kAriaCellRowIndex,
                              aria_rowindex);
     }
 
-    if ((dst->role == ax::mojom::Role::kRowHeader ||
-         dst->role == ax::mojom::Role::kColumnHeader) &&
+    if (ui::IsTableHeaderRole(dst->role) &&
         src.SortDirection() != ax::mojom::SortDirection::kNone) {
       dst->AddIntAttribute(ax::mojom::IntAttribute::kSortDirection,
                            static_cast<int32_t>(src.SortDirection()));
diff --git a/content/renderer/gpu/layer_tree_view.cc b/content/renderer/gpu/layer_tree_view.cc
index a4a31970..c843316 100644
--- a/content/renderer/gpu/layer_tree_view.cc
+++ b/content/renderer/gpu/layer_tree_view.cc
@@ -612,15 +612,9 @@
   delegate_->UpdateVisualState();
 }
 
-void LayerTreeView::ApplyViewportDeltas(
-    const gfx::Vector2dF& inner_delta,
-    const gfx::Vector2dF& outer_delta,
-    const gfx::Vector2dF& elastic_overscroll_delta,
-    float page_scale,
-    float top_controls_delta) {
-  delegate_->ApplyViewportDeltas(inner_delta, outer_delta,
-                                 elastic_overscroll_delta, page_scale,
-                                 top_controls_delta);
+void LayerTreeView::ApplyViewportChanges(
+    const cc::ApplyViewportChangesArgs& args) {
+  delegate_->ApplyViewportChanges(args);
 }
 
 void LayerTreeView::RecordWheelAndTouchScrollingCount(
diff --git a/content/renderer/gpu/layer_tree_view.h b/content/renderer/gpu/layer_tree_view.h
index 37d5783..daa3034 100644
--- a/content/renderer/gpu/layer_tree_view.h
+++ b/content/renderer/gpu/layer_tree_view.h
@@ -181,11 +181,7 @@
   void BeginMainFrameNotExpectedSoon() override;
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override;
+  void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override;
   void RequestNewLayerTreeFrameSink() override;
diff --git a/content/renderer/gpu/layer_tree_view_delegate.h b/content/renderer/gpu/layer_tree_view_delegate.h
index 2ef7d29..68d11d6 100644
--- a/content/renderer/gpu/layer_tree_view_delegate.h
+++ b/content/renderer/gpu/layer_tree_view_delegate.h
@@ -17,10 +17,6 @@
 class SwapPromise;
 }  // namespace cc
 
-namespace gfx {
-class Vector2dF;
-}
-
 namespace viz {
 class CopyOutputRequest;
 }
@@ -36,12 +32,8 @@
 
   // Report viewport related properties during a commit from the compositor
   // thread.
-  virtual void ApplyViewportDeltas(
-      const gfx::Vector2dF& inner_delta,
-      const gfx::Vector2dF& outer_delta,
-      const gfx::Vector2dF& elastic_overscroll_delta,
-      float page_scale,
-      float top_controls_delta) = 0;
+  virtual void ApplyViewportChanges(
+      const cc::ApplyViewportChangesArgs& args) = 0;
 
   // Record use count of wheel/touch sources for scrolling on the compositor
   // thread.
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index a24f837..88edfef 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -929,17 +929,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // LayerTreeViewDelegate
 
-void RenderWidget::ApplyViewportDeltas(
-    const gfx::Vector2dF& inner_delta,
-    const gfx::Vector2dF& outer_delta,
-    const gfx::Vector2dF& elastic_overscroll_delta,
-    float page_scale,
-    float top_controls_delta) {
+void RenderWidget::ApplyViewportChanges(
+    const cc::ApplyViewportChangesArgs& args) {
   if (!GetWebWidget())
     return;
-  GetWebWidget()->ApplyViewportDeltas(inner_delta, outer_delta,
-                                      elastic_overscroll_delta, page_scale,
-                                      top_controls_delta);
+  GetWebWidget()->ApplyViewportChanges(args);
 }
 
 void RenderWidget::RecordWheelAndTouchScrollingCount(
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index ff560a1..682cad6 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -87,6 +87,7 @@
 }  // namespace blink
 
 namespace cc {
+struct ApplyViewportChangesArgs;
 class SwapPromise;
 }
 
@@ -255,11 +256,7 @@
   bool Send(IPC::Message* msg) override;
 
   // LayerTreeViewDelegate
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override;
+  void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override;
   void BeginMainFrame(base::TimeTicks frame_time) override;
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index 44cf532..1c0fae8b 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -586,7 +586,7 @@
   UMA_HISTOGRAM_TIMES("ServiceWorker.SubresourceStartBlobReadingDelay", delay);
 
   return ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
-      &body_as_blob_, body_as_blob_size_, resource_request_.headers,
+      &body_as_blob_, body_as_blob_size_,
       base::BindOnce(&ServiceWorkerSubresourceLoader::OnBlobReadingComplete,
                      weak_factory_.GetWeakPtr()),
       body_pipe);
diff --git a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
index 0caa164..3f6a0ba6 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
@@ -11,6 +11,7 @@
 
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "content/common/service_worker/service_worker_container.mojom.h"
@@ -190,6 +191,7 @@
     redirect_location_header_ = redirect_location_header;
   }
 
+  // Tells this controller to respond to fetch events with a blob response body.
   void RespondWithBlob(base::Optional<std::vector<uint8_t>> metadata,
                        std::string body) {
     response_mode_ = ResponseMode::kBlob;
@@ -201,6 +203,14 @@
         mojo::MakeRequest(&blob_body_->blob));
   }
 
+  // Tells this controller to respond to fetch events with a 206 partial
+  // response, returning a blob composed of the requested bytes of |body|
+  // according to the request headers.
+  void RespondWithBlobRange(std::string body) {
+    response_mode_ = ResponseMode::kBlobRange;
+    blob_range_body_ = body;
+  }
+
   void ReadRequestBody(std::string* out_string) {
     ASSERT_TRUE(request_body_);
     std::vector<network::DataElement>* elements =
@@ -270,6 +280,44 @@
             blink::mojom::ServiceWorkerEventStatus::COMPLETED,
             base::TimeTicks());
         break;
+
+      case ResponseMode::kBlobRange: {
+        // Parse the Range header.
+        std::string range_header;
+        std::vector<net::HttpByteRange> ranges;
+        ASSERT_TRUE(params->request.headers.GetHeader(
+            net::HttpRequestHeaders::kRange, &range_header));
+        ASSERT_TRUE(net::HttpUtil::ParseRangeHeader(range_header, &ranges));
+        ASSERT_EQ(1u, ranges.size());
+        ASSERT_TRUE(ranges[0].ComputeBounds(blob_range_body_.size()));
+        const net::HttpByteRange& range = ranges[0];
+
+        // Build a Blob composed of the requested bytes from |blob_range_body_|.
+        size_t start = static_cast<size_t>(range.first_byte_position());
+        size_t end = static_cast<size_t>(range.last_byte_position());
+        size_t size = end - start + 1;
+        std::string body = blob_range_body_.substr(start, size);
+        auto blob = blink::mojom::SerializedBlob::New();
+        blob->uuid = "dummy-blob-uuid";
+        blob->size = size;
+        mojo::MakeStrongBinding(std::make_unique<FakeBlob>(base::nullopt, body),
+                                mojo::MakeRequest(&blob->blob));
+
+        // Respond with a 206 response.
+        auto response = OkResponse(std::move(blob));
+        response->status_code = 206;
+        response->headers.emplace(
+            "Content-Range", base::StringPrintf("bytes %zu-%zu/%zu", start, end,
+                                                blob_range_body_.size()));
+        response_callback->OnResponse(
+            std::move(response),
+            blink::mojom::ServiceWorkerFetchEventTiming::New());
+        std::move(callback).Run(
+            blink::mojom::ServiceWorkerEventStatus::COMPLETED,
+            base::TimeTicks::Now());
+        break;
+      }
+
       case ResponseMode::kFallbackResponse:
         response_callback->OnFallback(
             blink::mojom::ServiceWorkerFetchEventTiming::New());
@@ -320,6 +368,7 @@
     kAbort,
     kStream,
     kBlob,
+    kBlobRange,
     kFallbackResponse,
     kErrorResponse,
     kRedirectResponse
@@ -339,6 +388,9 @@
   // For ResponseMode::kBlob.
   blink::mojom::SerializedBlobPtr blob_body_;
 
+  // For ResponseMode::kBlobRange.
+  std::string blob_range_body_;
+
   // For ResponseMode::kRedirectResponse
   std::string redirect_location_header_;
 
@@ -539,6 +591,30 @@
     // cover this case in fetch-event.https.html.
   }
 
+  // Performs a range request using |range_header| and returns the resulting
+  // client after completion.
+  std::unique_ptr<network::TestURLLoaderClient> DoRangeRequest(
+      const std::string& range_header) {
+    network::mojom::URLLoaderFactoryPtr factory =
+        CreateSubresourceLoaderFactory();
+    network::ResourceRequest request =
+        CreateRequest(GURL("https://www.example.com/big-file"));
+    request.headers.SetHeader("Range", range_header);
+    network::mojom::URLLoaderPtr loader;
+    std::unique_ptr<network::TestURLLoaderClient> client;
+    StartRequest(factory, request, &loader, &client);
+    client->RunUntilComplete();
+    return client;
+  }
+
+  std::string TakeResponseBody(network::TestURLLoaderClient* client) {
+    std::string body;
+    EXPECT_TRUE(client->response_body().is_valid());
+    EXPECT_TRUE(
+        mojo::BlockingCopyToString(client->response_body_release(), &body));
+    return body;
+  }
+
   TestBrowserThreadBundle thread_bundle_;
   scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
   scoped_refptr<ControllerServiceWorkerConnector> connector_;
@@ -1212,5 +1288,73 @@
   RunFallbackWithRequestBodyTest(std::move(request_body), kData);
 }
 
+// Test a range request that the service worker responds to with a 200
+// (non-ranged) response. The client should get the entire response as-is from
+// the service worker.
+TEST_F(ServiceWorkerSubresourceLoaderTest, RangeRequest_200Response) {
+  // Construct the Blob to respond with.
+  const std::string kResponseBody = "Here is sample text for the Blob.";
+  fake_controller_.RespondWithBlob(base::nullopt, kResponseBody);
+
+  // Perform the request.
+  std::unique_ptr<network::TestURLLoaderClient> client =
+      DoRangeRequest("bytes=5-13");
+  EXPECT_EQ(net::OK, client->completion_status().error_code);
+
+  // Test the response.
+  const network::ResourceResponseHead& info = client->response_head();
+  ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker());
+  EXPECT_EQ(33, info.content_length);
+  EXPECT_FALSE(info.headers->HasHeader("Content-Range"));
+  EXPECT_EQ(kResponseBody, TakeResponseBody(client.get()));
+}
+
+// Test a range request that the service worker responds to with a 206 ranged
+// response. The client should get the partial response as-is from the service
+// worker.
+TEST_F(ServiceWorkerSubresourceLoaderTest, RangeRequest_206Response) {
+  // Tell the controller to respond with a 206 response.
+  const std::string kResponseBody = "Here is sample text for the Blob.";
+  fake_controller_.RespondWithBlobRange(kResponseBody);
+
+  // Perform the request.
+  std::unique_ptr<network::TestURLLoaderClient> client =
+      DoRangeRequest("bytes=5-13");
+  EXPECT_EQ(net::OK, client->completion_status().error_code);
+
+  // Test the response.
+  const network::ResourceResponseHead& info = client->response_head();
+  EXPECT_EQ(206, info.headers->response_code());
+  std::string range;
+  ASSERT_TRUE(info.headers->GetNormalizedHeader("Content-Range", &range));
+  EXPECT_EQ("bytes 5-13/33", range);
+  EXPECT_EQ(9, info.content_length);
+  EXPECT_EQ("is sample", TakeResponseBody(client.get()));
+}
+
+// Test a range request that the service worker responds to with a 206 ranged
+// response. The requested range has an unbounded end. The client should get the
+// partial response as-is from the service worker.
+TEST_F(ServiceWorkerSubresourceLoaderTest,
+       RangeRequest_UnboundedRight_206Response) {
+  // Tell the controller to respond with a 206 response.
+  const std::string kResponseBody = "Here is sample text for the Blob.";
+  fake_controller_.RespondWithBlobRange(kResponseBody);
+
+  // Perform the request.
+  std::unique_ptr<network::TestURLLoaderClient> client =
+      DoRangeRequest("bytes=5-");
+  EXPECT_EQ(net::OK, client->completion_status().error_code);
+
+  // Test the response.
+  const network::ResourceResponseHead& info = client->response_head();
+  EXPECT_EQ(206, info.headers->response_code());
+  std::string range;
+  ASSERT_TRUE(info.headers->GetNormalizedHeader("Content-Range", &range));
+  EXPECT_EQ("bytes 5-32/33", range);
+  EXPECT_EQ(28, info.content_length);
+  EXPECT_EQ("is sample text for the Blob.", TakeResponseBody(client.get()));
+}
+
 }  // namespace service_worker_subresource_loader_unittest
 }  // namespace content
diff --git a/content/shell/browser/layout_test/test_info_extractor.cc b/content/shell/browser/layout_test/test_info_extractor.cc
index c425c9e..d8e9481 100644
--- a/content/shell/browser/layout_test/test_info_extractor.cc
+++ b/content/shell/browser/layout_test/test_info_extractor.cc
@@ -13,6 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "content/shell/common/layout_test/layout_test_switches.h"
 #include "net/base/filename_util.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
@@ -97,10 +98,18 @@
     if (!base::PathExists(local_file)) {
       base::FilePath base_path;
       base::PathService::Get(base::DIR_SOURCE_ROOT, &base_path);
-      local_file = base_path.Append(FILE_PATH_LITERAL("third_party"))
-                       .Append(FILE_PATH_LITERAL("WebKit"))
-                       .Append(FILE_PATH_LITERAL("LayoutTests"))
-                       .Append(local_file);
+      if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kTestsInBlink)) {
+        local_file = base_path.Append(FILE_PATH_LITERAL("third_party"))
+                         .Append(FILE_PATH_LITERAL("blink"))
+                         .Append(FILE_PATH_LITERAL("web_tests"))
+                         .Append(local_file);
+      } else {
+        local_file = base_path.Append(FILE_PATH_LITERAL("third_party"))
+                         .Append(FILE_PATH_LITERAL("WebKit"))
+                         .Append(FILE_PATH_LITERAL("LayoutTests"))
+                         .Append(local_file);
+      }
     }
     test_url = net::FilePathToFileURL(base::MakeAbsoluteFilePath(local_file));
   }
diff --git a/content/shell/common/layout_test/layout_test_switches.cc b/content/shell/common/layout_test/layout_test_switches.cc
index f1260fe..262a246 100644
--- a/content/shell/common/layout_test/layout_test_switches.cc
+++ b/content/shell/common/layout_test/layout_test_switches.cc
@@ -56,6 +56,11 @@
 // http://dev.chromium.org/blink/runtime-enabled-features.
 const char kStableReleaseMode[] = "stable-release-mode";
 
+// Test files are in //third_party/blink/web_tests, not in
+// //third_party/WebKit/LayoutTests.
+// TODO(tkent): Remove this flag after the move.
+const char kTestsInBlink[] = "tests-in-blink";
+
 // Enable pixel dumps via "real" surface readbacks, instead of synchronously
 // compositing and reading back pixels.
 const char kEnableDisplayCompositorPixelDump[] =
diff --git a/content/shell/common/layout_test/layout_test_switches.h b/content/shell/common/layout_test/layout_test_switches.h
index f0dedd32..ebbf47c 100644
--- a/content/shell/common/layout_test/layout_test_switches.h
+++ b/content/shell/common/layout_test/layout_test_switches.h
@@ -30,6 +30,7 @@
 extern const char kEncodeBinary[];
 extern const char kRunWebTests[];
 extern const char kStableReleaseMode[];
+extern const char kTestsInBlink[];
 extern const char kEnableDisplayCompositorPixelDump[];
 
 }  // namespace switches
diff --git a/content/shell/renderer/layout_test/blink_test_helpers.cc b/content/shell/renderer/layout_test/blink_test_helpers.cc
index 105b153..8f247ac4 100644
--- a/content/shell/renderer/layout_test/blink_test_helpers.cc
+++ b/content/shell/renderer/layout_test/blink_test_helpers.cc
@@ -26,6 +26,24 @@
 
 namespace {
 
+base::FilePath GetWebTestsFilePath() {
+  static base::FilePath path;
+  if (path.empty()) {
+    base::FilePath root_path;
+    bool success = base::PathService::Get(base::DIR_SOURCE_ROOT, &root_path);
+    CHECK(success);
+    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+            switches::kTestsInBlink)) {
+      path =
+          root_path.Append(FILE_PATH_LITERAL("third_party/blink/web_tests/"));
+    } else {
+      path = root_path.Append(
+          FILE_PATH_LITERAL("third_party/WebKit/LayoutTests/"));
+    }
+  }
+  return path;
+}
+
 // Tests in csswg-test use absolute path links such as
 //   <script src="/resources/testharness.js">.
 // Because we load the tests as local files, such links don't work.
@@ -38,7 +56,8 @@
   static constexpr base::StringPiece kFileScheme = "file:///";
   if (!base::StartsWith(utf8_url, kFileScheme, base::CompareCase::SENSITIVE))
     return WebURL();
-  if (utf8_url.find("/LayoutTests/") != std::string::npos)
+  if (utf8_url.find("/LayoutTests/") != std::string::npos ||
+      utf8_url.find("/web_tests/") != std::string::npos)
     return WebURL();
 #if defined(OS_WIN)
   // +3 for a drive letter, :, and /.
@@ -49,9 +68,7 @@
 #else
   std::string path = utf8_url.substr(kFileScheme.size());
 #endif
-  base::FilePath new_path = content::GetWebKitRootDirFilePath()
-                                .Append(FILE_PATH_LITERAL("LayoutTests/"))
-                                .AppendASCII(path);
+  base::FilePath new_path = GetWebTestsFilePath().AppendASCII(path);
   return WebURL(net::FilePathToFileURL(new_path));
 }
 
@@ -148,13 +165,6 @@
   prefs->translate_service_available = true;
 }
 
-base::FilePath GetWebKitRootDirFilePath() {
-  base::FilePath base_path;
-  bool success = base::PathService::Get(base::DIR_SOURCE_ROOT, &base_path);
-  CHECK(success);
-  return base_path.Append(FILE_PATH_LITERAL("third_party/WebKit"));
-}
-
 base::FilePath GetBuildDirectory() {
   base::FilePath result;
   bool success = base::PathService::Get(base::DIR_EXE, &result);
@@ -193,14 +203,13 @@
     return WebURL(GURL(new_url));
   }
 
+  // TODO(tkent): Replace "tmp/LayoutTests" in tests with "tmp/web_tests".
   static constexpr base::StringPiece kPrefix = "file:///tmp/LayoutTests/";
 
   if (!base::StartsWith(utf8_url, kPrefix, base::CompareCase::SENSITIVE))
     return WebURL(GURL(utf8_url));
 
-  base::FilePath replace_path =
-      GetWebKitRootDirFilePath().Append(FILE_PATH_LITERAL("LayoutTests/"));
-  std::string utf8_path = replace_path.AsUTF8Unsafe();
+  std::string utf8_path = GetWebTestsFilePath().AsUTF8Unsafe();
   std::string new_url =
       std::string("file://") + utf8_path + utf8_url.substr(kPrefix.size());
   return WebURL(GURL(new_url));
diff --git a/content/shell/renderer/layout_test/blink_test_helpers.h b/content/shell/renderer/layout_test/blink_test_helpers.h
index 9cbc647..e734eaaf 100644
--- a/content/shell/renderer/layout_test/blink_test_helpers.h
+++ b/content/shell/renderer/layout_test/blink_test_helpers.h
@@ -28,9 +28,6 @@
 // Applies settings that differ between layout tests and regular mode.
 void ApplyLayoutTestDefaultPreferences(WebPreferences* prefs);
 
-// Returns the root of the Blink checkout.
-base::FilePath GetWebKitRootDirFilePath();
-
 // The build directory of the Blink checkout.
 base::FilePath GetBuildDirectory();
 
diff --git a/content/shell/test_runner/test_common.cc b/content/shell/test_runner/test_common.cc
index 0079ebd..520fcde 100644
--- a/content/shell/test_runner/test_common.cc
+++ b/content/shell/test_runner/test_common.cc
@@ -19,6 +19,9 @@
 const char layout_tests_pattern[] = "/LayoutTests/";
 const std::string::size_type layout_tests_pattern_size =
     sizeof(layout_tests_pattern) - 1;
+const char web_tests_pattern[] = "/web_tests/";
+const std::string::size_type web_tests_pattern_size =
+    sizeof(web_tests_pattern) - 1;
 const char file_url_pattern[] = "file:/";
 const char file_test_prefix[] = "(file test):";
 const char data_url_pattern[] = "data:";
@@ -43,6 +46,10 @@
       ((pos = url.find(layout_tests_pattern)) != std::string::npos)) {
     // adjust file URLs to match upstream results.
     result.replace(0, pos + layout_tests_pattern_size, file_test_prefix);
+  } else if (!url.find(file_url_pattern) &&
+             ((pos = url.find(web_tests_pattern)) != std::string::npos)) {
+    // adjust file URLs to match upstream results.
+    result.replace(0, pos + web_tests_pattern_size, file_test_prefix);
   } else if (!url.find(data_url_pattern)) {
     // URL-escape data URLs to match results upstream.
     std::string path = url.substr(data_url_pattern_size);
diff --git a/content/shell/test_runner/test_interfaces.cc b/content/shell/test_runner/test_interfaces.cc
index e86a9a3..253ad08f 100644
--- a/content/shell/test_runner/test_interfaces.cc
+++ b/content/shell/test_runner/test_interfaces.cc
@@ -98,8 +98,14 @@
                                              bool initial_configuration) {
   std::string spec = GURL(test_url).spec();
   size_t path_start = spec.rfind("LayoutTests/");
-  if (path_start != std::string::npos)
+  if (path_start != std::string::npos) {
     spec = spec.substr(path_start);
+  } else {
+    path_start = spec.rfind("web_tests/");
+    if (path_start != std::string::npos)
+      spec = spec.substr(path_start);
+  }
+
   bool is_devtools_test = spec.find("/devtools/") != std::string::npos;
   if (is_devtools_test) {
     test_runner_->SetDumpConsoleMessages(false);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 0b1ea0a..8d1779f 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1930,6 +1930,7 @@
     "//third_party/webrtc/modules/video_coding:video_codec_interface",
     "//third_party/webrtc/pc:libjingle_peerconnection",
     "//third_party/webrtc/rtc_base:rtc_base",
+    "//third_party/webrtc/rtc_base:timeutils",
     "//third_party/webrtc/stats:rtc_stats_test_utils",
     "//third_party/webrtc_overrides",
     "//third_party/webrtc_overrides:init_webrtc",
@@ -1945,6 +1946,7 @@
     "//ui/events/blink",
     "//ui/gfx:test_support",
     "//ui/gfx/geometry",
+    "//ui/gfx/geometry/mojo:struct_traits",
     "//ui/gfx/ipc",
     "//ui/gfx/ipc/skia",
     "//ui/gl",
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index aa545ca7..82caed7 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -726,6 +726,8 @@
         ['android', 'nvidia'], bug=830901)
     self.Flaky('conformance/extensions/oes-texture-half-float-with-video.html',
         ['android', 'nvidia'], bug=891456)
+    self.Flaky('conformance/extensions/oes-texture-float-with-video.html',
+        ['android', 'nvidia'], bug=891456)
     self.Flaky('conformance/textures/image_bitmap_from_video/' +
         'tex-2d-rgb-rgb-unsigned_short_5_6_5.html', ['android', 'nvidia'],
         bug=891456)
diff --git a/content/test/stub_layer_tree_view_delegate.h b/content/test/stub_layer_tree_view_delegate.h
index a2545a6..52b0e4f 100644
--- a/content/test/stub_layer_tree_view_delegate.h
+++ b/content/test/stub_layer_tree_view_delegate.h
@@ -7,16 +7,16 @@
 
 #include "content/renderer/gpu/layer_tree_view_delegate.h"
 
+namespace cc {
+struct ApplyViewportChangesArgs;
+}
+
 namespace content {
 
 class StubLayerTreeViewDelegate : public LayerTreeViewDelegate {
  public:
   // LayerTreeViewDelegate implementation.
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override {}
+  void ApplyViewportChanges(const cc::ApplyViewportChangesArgs&) override {}
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override {}
   void BeginMainFrame(base::TimeTicks frame_time) override {}
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 3bcd647..28d6cc8 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -82,7 +82,7 @@
     "fido/ctap_request_unittest.cc",
     "fido/ctap_response_unittest.cc",
     "fido/fake_fido_discovery_unittest.cc",
-    "fido/fido_discovery_unittest.cc",
+    "fido/fido_device_discovery_unittest.cc",
     "fido/fido_parsing_utils_unittest.cc",
     "fido/fido_request_handler_unittest.cc",
     "fido/get_assertion_handler_unittest.cc",
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index bf97afc..b1cbe99 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -71,8 +71,8 @@
     "fido_device.h",
     "fido_device_authenticator.cc",
     "fido_device_authenticator.h",
-    "fido_discovery.cc",
-    "fido_discovery.h",
+    "fido_device_discovery.cc",
+    "fido_device_discovery.h",
     "fido_discovery_base.cc",
     "fido_discovery_base.h",
     "fido_parsing_utils.cc",
diff --git a/device/fido/ble/fido_ble_discovery.cc b/device/fido/ble/fido_ble_discovery.cc
index 073585e..4a6ade3 100644
--- a/device/fido/ble/fido_ble_discovery.cc
+++ b/device/fido/ble/fido_ble_discovery.cc
@@ -15,6 +15,7 @@
 #include "device/fido/ble/fido_ble_device.h"
 #include "device/fido/ble/fido_ble_uuids.h"
 #include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_device_authenticator.h"
 
 namespace device {
 
@@ -74,8 +75,8 @@
   }
 
   const auto device_id = FidoBleDevice::GetId(device->GetAddress());
-  auto* fido_device = GetDevice(device_id);
-  if (!fido_device) {
+  auto* authenticator = GetAuthenticator(device_id);
+  if (!authenticator) {
     VLOG(2) << "Discovered U2F service on existing BLE device: "
             << device->GetAddress();
     AddDevice(std::make_unique<FidoBleDevice>(adapter, device->GetAddress()));
@@ -87,7 +88,7 @@
   // and BluetoothAdapter::DeviceRemoved() is invoked instead of returning back
   // to regular "non-pairing" mode. As so, we only notify observer when
   // |fido_device| goes into pairing mode.
-  if (observer() && fido_device->IsInPairingMode())
+  if (observer() && authenticator->device()->IsInPairingMode())
     observer()->AuthenticatorPairingModeChanged(this, device_id);
 }
 
@@ -113,14 +114,14 @@
                                             const std::string& old_address) {
   auto previous_device_id = FidoBleDevice::GetId(old_address);
   auto new_device_id = FidoBleDevice::GetId(device->GetAddress());
-  auto it = devices_.find(previous_device_id);
-  if (it == devices_.end())
+  auto it = authenticators_.find(previous_device_id);
+  if (it == authenticators_.end())
     return;
 
   VLOG(2) << "Discovered FIDO BLE device address change from old address : "
           << old_address << " to new address : " << device->GetAddress();
-  devices_.emplace(new_device_id, std::move(it->second));
-  devices_.erase(it);
+  authenticators_.emplace(new_device_id, std::move(it->second));
+  authenticators_.erase(it);
 
   if (observer()) {
     observer()->AuthenticatorIdChanged(this, previous_device_id,
diff --git a/device/fido/ble/fido_ble_discovery_base.cc b/device/fido/ble/fido_ble_discovery_base.cc
index 6ef5475..240513c7 100644
--- a/device/fido/ble/fido_ble_discovery_base.cc
+++ b/device/fido/ble/fido_ble_discovery_base.cc
@@ -20,7 +20,7 @@
 namespace device {
 
 FidoBleDiscoveryBase::FidoBleDiscoveryBase(FidoTransportProtocol transport)
-    : FidoDiscovery(transport), weak_factory_(this) {}
+    : FidoDeviceDiscovery(transport), weak_factory_(this) {}
 
 FidoBleDiscoveryBase::~FidoBleDiscoveryBase() {
   if (adapter_)
diff --git a/device/fido/ble/fido_ble_discovery_base.h b/device/fido/ble/fido_ble_discovery_base.h
index 3a1f64c..c8885d7 100644
--- a/device/fido/ble/fido_ble_discovery_base.h
+++ b/device/fido/ble/fido_ble_discovery_base.h
@@ -13,14 +13,14 @@
 #include "base/memory/weak_ptr.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_uuid.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 
 namespace device {
 
 class BluetoothDiscoverySession;
 
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscoveryBase
-    : public FidoDiscovery,
+    : public FidoDeviceDiscovery,
       public BluetoothAdapter::Observer {
  public:
   explicit FidoBleDiscoveryBase(FidoTransportProtocol transport);
@@ -44,7 +44,7 @@
  private:
   void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter);
 
-  // FidoDiscovery:
+  // FidoDeviceDiscovery:
   void StartInternal() override;
 
   scoped_refptr<BluetoothAdapter> adapter_;
diff --git a/device/fido/ble/fido_ble_discovery_unittest.cc b/device/fido/ble/fido_ble_discovery_unittest.cc
index 2fa673e4..48533cc 100644
--- a/device/fido/ble/fido_ble_discovery_unittest.cc
+++ b/device/fido/ble/fido_ble_discovery_unittest.cc
@@ -16,7 +16,7 @@
 #include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "device/fido/ble/fido_ble_device.h"
 #include "device/fido/ble/fido_ble_uuids.h"
-#include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_device_authenticator.h"
 #include "device/fido/mock_fido_discovery_observer.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -192,7 +192,7 @@
     discovery.Start();
     run_loop.Run();
 
-    EXPECT_THAT(discovery.GetDevices(), ::testing::IsEmpty());
+    EXPECT_THAT(discovery.GetAuthenticatorsForTesting(), ::testing::IsEmpty());
   }
 
   {
@@ -209,10 +209,10 @@
 
     run_loop.Run();
 
-    const auto devices = discovery.GetDevices();
-    ASSERT_THAT(devices, ::testing::SizeIs(1u));
+    const auto authenticators = discovery.GetAuthenticatorsForTesting();
+    ASSERT_THAT(authenticators, ::testing::SizeIs(1u));
     EXPECT_EQ(FidoBleDevice::GetId(BluetoothTestBase::kTestDeviceAddress1),
-              devices[0]->GetId());
+              authenticators[0]->GetId());
   }
 }
 
@@ -288,8 +288,8 @@
 
   mock_adapter->NotifyDeviceChanged(mock_device.get());
 
-  EXPECT_EQ(1u, discovery.GetDevices().size());
-  EXPECT_TRUE(discovery.GetDevice(kAuthenticatorChangedId));
+  EXPECT_EQ(1u, discovery.GetAuthenticatorsForTesting().size());
+  EXPECT_TRUE(discovery.GetAuthenticatorForTesting(kAuthenticatorChangedId));
 }
 
 TEST_F(BluetoothTest, DiscoveryNotifiesObserverWhenDeviceInPairingMode) {
diff --git a/device/fido/fake_fido_discovery.cc b/device/fido/fake_fido_discovery.cc
index c0c9283..7e49f9a4 100644
--- a/device/fido/fake_fido_discovery.cc
+++ b/device/fido/fake_fido_discovery.cc
@@ -20,7 +20,7 @@
 
 FakeFidoDiscovery::FakeFidoDiscovery(FidoTransportProtocol transport,
                                      StartMode mode)
-    : FidoDiscovery(transport), mode_(mode) {}
+    : FidoDeviceDiscovery(transport), mode_(mode) {}
 FakeFidoDiscovery::~FakeFidoDiscovery() = default;
 
 void FakeFidoDiscovery::WaitForCallToStart() {
@@ -80,7 +80,7 @@
   return next_cable_discovery_.get();
 }
 
-std::unique_ptr<FidoDiscovery>
+std::unique_ptr<FidoDeviceDiscovery>
 ScopedFakeFidoDiscoveryFactory::CreateFidoDiscovery(
     FidoTransportProtocol transport,
     ::service_manager::Connector* connector) {
diff --git a/device/fido/fake_fido_discovery.h b/device/fido/fake_fido_discovery.h
index 15f55bd..57a65b7 100644
--- a/device/fido/fake_fido_discovery.h
+++ b/device/fido/fake_fido_discovery.h
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "device/fido/fido_transport_protocol.h"
 
 namespace service_manager {
@@ -32,10 +32,11 @@
 //   auto* fake_ble_discovery = factory.ForgeNextBleDiscovery();
 //
 //   // Run the production code that will eventually call:
-//   //// FidoDiscovery::Create(FidoTransportProtocol::kUsbHumanInterfaceDevice)
-//   //// hid_instance->Start();
-//   //// FidoDiscovery::Create(FidoTransportProtocol::kBluetoothLowEnergy)
-//   //// ble_instance->Start();
+//   // FidoDeviceDiscovery::Create(
+//   //     FidoTransportProtocol::kUsbHumanInterfaceDevice)
+//   // hid_instance->Start();
+//   // FidoDeviceDiscovery::Create(FidoTransportProtocol::kBluetoothLowEnergy)
+//   // ble_instance->Start();
 //
 //   // Wait, i.e. spin the message loop until the fake discoveries are started.
 //   fake_hid_discovery->WaitForCallToStart();
@@ -54,7 +55,7 @@
 //   // Destroy the production instance to eventually stop the discovery.
 //   // hid_instance.reset();
 //
-class FakeFidoDiscovery : public FidoDiscovery,
+class FakeFidoDiscovery : public FidoDeviceDiscovery,
                           public base::SupportsWeakPtr<FakeFidoDiscovery> {
  public:
   enum class StartMode {
@@ -80,11 +81,11 @@
 
   // Tests are to directly call Add/RemoveDevice to simulate adding/removing
   // devices. Observers are automatically notified.
-  using FidoDiscovery::AddDevice;
-  using FidoDiscovery::RemoveDevice;
+  using FidoDeviceDiscovery::AddDevice;
+  using FidoDeviceDiscovery::RemoveDevice;
 
  private:
-  // FidoDiscovery:
+  // FidoDeviceDiscovery:
   void StartInternal() override;
 
   const StartMode mode_;
@@ -93,8 +94,8 @@
   DISALLOW_COPY_AND_ASSIGN(FakeFidoDiscovery);
 };
 
-// Overrides FidoDiscovery::Create to construct FakeFidoDiscoveries while this
-// instance is in scope.
+// Overrides FidoDeviceDiscovery::Create to construct FakeFidoDiscoveries while
+// this instance is in scope.
 class ScopedFakeFidoDiscoveryFactory
     : public ::device::internal::ScopedFidoDiscoveryFactory {
  public:
@@ -104,11 +105,11 @@
   ~ScopedFakeFidoDiscoveryFactory() override;
 
   // Constructs a fake discovery to be returned from the next call to
-  // FidoDiscovery::Create. Returns a raw pointer to the fake so that tests can
-  // set it up according to taste.
+  // FidoDeviceDiscovery::Create. Returns a raw pointer to the fake so that
+  // tests can set it up according to taste.
   //
   // It is an error not to call the relevant method prior to a call to
-  // FidoDiscovery::Create with the respective transport.
+  // FidoDeviceDiscovery::Create with the respective transport.
   FakeFidoDiscovery* ForgeNextHidDiscovery(StartMode mode = StartMode::kManual);
   FakeFidoDiscovery* ForgeNextNfcDiscovery(StartMode mode = StartMode::kManual);
   FakeFidoDiscovery* ForgeNextBleDiscovery(StartMode mode = StartMode::kManual);
@@ -116,7 +117,7 @@
       StartMode mode = StartMode::kManual);
 
  protected:
-  std::unique_ptr<FidoDiscovery> CreateFidoDiscovery(
+  std::unique_ptr<FidoDeviceDiscovery> CreateFidoDiscovery(
       FidoTransportProtocol transport,
       ::service_manager::Connector* connector) override;
 
diff --git a/device/fido/fake_fido_discovery_unittest.cc b/device/fido/fake_fido_discovery_unittest.cc
index 8e38849..407d710 100644
--- a/device/fido/fake_fido_discovery_unittest.cc
+++ b/device/fido/fake_fido_discovery_unittest.cc
@@ -150,7 +150,7 @@
   auto* injected_fake_discovery = factory.ForgeNextHidDiscovery();
   ASSERT_EQ(FidoTransportProtocol::kUsbHumanInterfaceDevice,
             injected_fake_discovery->transport());
-  auto produced_discovery = FidoDiscovery::Create(
+  auto produced_discovery = FidoDeviceDiscovery::Create(
       FidoTransportProtocol::kUsbHumanInterfaceDevice, nullptr);
   EXPECT_TRUE(produced_discovery);
   EXPECT_EQ(injected_fake_discovery, produced_discovery.get());
@@ -164,14 +164,14 @@
   auto* injected_fake_discovery_1 = factory.ForgeNextBleDiscovery();
   ASSERT_EQ(FidoTransportProtocol::kBluetoothLowEnergy,
             injected_fake_discovery_1->transport());
-  auto produced_discovery_1 = FidoDiscovery::Create(
+  auto produced_discovery_1 = FidoDeviceDiscovery::Create(
       FidoTransportProtocol::kBluetoothLowEnergy, nullptr);
   EXPECT_EQ(injected_fake_discovery_1, produced_discovery_1.get());
 
   auto* injected_fake_discovery_2 = factory.ForgeNextBleDiscovery();
   ASSERT_EQ(FidoTransportProtocol::kBluetoothLowEnergy,
             injected_fake_discovery_2->transport());
-  auto produced_discovery_2 = FidoDiscovery::Create(
+  auto produced_discovery_2 = FidoDeviceDiscovery::Create(
       FidoTransportProtocol::kBluetoothLowEnergy, nullptr);
   EXPECT_EQ(injected_fake_discovery_2, produced_discovery_2.get());
 }
diff --git a/device/fido/fido_device.h b/device/fido/fido_device.h
index 9e7678f..5a8bb38 100644
--- a/device/fido/fido_device.h
+++ b/device/fido/fido_device.h
@@ -27,7 +27,7 @@
 // Devices are instantiated with an unknown protocol version. Users should call
 // |DiscoverSupportedProtocolAndDeviceInfo| to determine a device's
 // capabilities and initialize the instance accordingly. Instances returned by
-// |FidoDiscovery| are already fully initialized.
+// |FidoDeviceDiscovery| are not fully initialized.
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice {
  public:
   using WinkCallback = base::OnceClosure;
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index d7c68533..f011e9e 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -18,8 +18,9 @@
 
 namespace device {
 
-FidoDeviceAuthenticator::FidoDeviceAuthenticator(FidoDevice* device)
-    : device_(device), weak_factory_(this) {}
+FidoDeviceAuthenticator::FidoDeviceAuthenticator(
+    std::unique_ptr<FidoDevice> device)
+    : device_(std::move(device)), weak_factory_(this) {}
 FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default;
 
 void FidoDeviceAuthenticator::InitializeAuthenticator(
@@ -37,15 +38,15 @@
       << "InitializeAuthenticator() must be called first.";
   // TODO(martinkr): Change FidoTasks to take all request parameters by const
   // reference, so we can avoid copying these from the RequestHandler.
-  task_ = std::make_unique<MakeCredentialTask>(device_, std::move(request),
-                                               std::move(callback));
+  task_ = std::make_unique<MakeCredentialTask>(
+      device_.get(), std::move(request), std::move(callback));
 }
 
 void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request,
                                            GetAssertionCallback callback) {
   DCHECK(device_->SupportedProtocolIsInitialized())
       << "InitializeAuthenticator() must be called first.";
-  task_ = std::make_unique<GetAssertionTask>(device_, std::move(request),
+  task_ = std::make_unique<GetAssertionTask>(device_.get(), std::move(request),
                                              std::move(callback));
 }
 
diff --git a/device/fido/fido_device_authenticator.h b/device/fido/fido_device_authenticator.h
index 84a7b44..06738e4 100644
--- a/device/fido/fido_device_authenticator.h
+++ b/device/fido/fido_device_authenticator.h
@@ -29,7 +29,7 @@
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
     : public FidoAuthenticator {
  public:
-  FidoDeviceAuthenticator(FidoDevice* device);
+  FidoDeviceAuthenticator(std::unique_ptr<FidoDevice> device);
   ~FidoDeviceAuthenticator() override;
 
   // FidoAuthenticator:
@@ -46,7 +46,7 @@
   bool IsInPairingMode() const override;
   base::WeakPtr<FidoAuthenticator> GetWeakPtr() override;
 
-  FidoDevice* GetDeviceForTesting() { return device_; }
+  FidoDevice* device() { return device_.get(); }
 
  protected:
   void OnCtapMakeCredentialResponseReceived(
@@ -56,11 +56,10 @@
       GetAssertionCallback callback,
       base::Optional<std::vector<uint8_t>> response_data);
 
-  FidoDevice* device() { return device_; }
   void SetTaskForTesting(std::unique_ptr<FidoTask> task);
 
  private:
-  FidoDevice* const device_;
+  const std::unique_ptr<FidoDevice> device_;
   std::unique_ptr<FidoTask> task_;
   base::WeakPtrFactory<FidoDeviceAuthenticator> weak_factory_;
 
diff --git a/device/fido/fido_device_discovery.cc b/device/fido/fido_device_discovery.cc
new file mode 100644
index 0000000..fe7fbe1a
--- /dev/null
+++ b/device/fido/fido_device_discovery.cc
@@ -0,0 +1,223 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/fido/fido_device_discovery.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "build/build_config.h"
+#include "device/fido/ble/fido_ble_discovery.h"
+#include "device/fido/cable/fido_cable_discovery.h"
+#include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_device.h"
+#include "device/fido/fido_device_authenticator.h"
+
+// HID is not supported on Android.
+#if !defined(OS_ANDROID)
+#include "device/fido/hid/fido_hid_discovery.h"
+#endif  // !defined(OS_ANDROID)
+
+namespace device {
+
+namespace {
+
+std::unique_ptr<FidoDeviceDiscovery> CreateFidoDiscoveryImpl(
+    FidoTransportProtocol transport,
+    service_manager::Connector* connector) {
+  switch (transport) {
+    case FidoTransportProtocol::kUsbHumanInterfaceDevice:
+#if !defined(OS_ANDROID)
+      DCHECK(connector);
+      return std::make_unique<FidoHidDiscovery>(connector);
+#else
+      NOTREACHED() << "USB HID not supported on Android.";
+      return nullptr;
+#endif  // !defined(OS_ANDROID)
+    case FidoTransportProtocol::kBluetoothLowEnergy:
+      return std::make_unique<FidoBleDiscovery>();
+    case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy:
+      NOTREACHED() << "Cable discovery is constructed using the dedicated "
+                      "factory method.";
+      return nullptr;
+    case FidoTransportProtocol::kNearFieldCommunication:
+      // TODO(https://crbug.com/825949): Add NFC support.
+      return nullptr;
+    case FidoTransportProtocol::kInternal:
+      NOTREACHED() << "Internal authenticators should be handled separately.";
+      return nullptr;
+  }
+  NOTREACHED() << "Unhandled transport type";
+  return nullptr;
+}
+
+std::unique_ptr<FidoDeviceDiscovery> CreateCableDiscoveryImpl(
+    std::vector<CableDiscoveryData> cable_data) {
+  return std::make_unique<FidoCableDiscovery>(std::move(cable_data));
+}
+
+}  // namespace
+
+FidoDeviceDiscovery::Observer::~Observer() = default;
+
+// static
+FidoDeviceDiscovery::FactoryFuncPtr FidoDeviceDiscovery::g_factory_func_ =
+    &CreateFidoDiscoveryImpl;
+
+// static
+FidoDeviceDiscovery::CableFactoryFuncPtr
+    FidoDeviceDiscovery::g_cable_factory_func_ = &CreateCableDiscoveryImpl;
+
+// static
+std::unique_ptr<FidoDeviceDiscovery> FidoDeviceDiscovery::Create(
+    FidoTransportProtocol transport,
+    service_manager::Connector* connector) {
+  return (*g_factory_func_)(transport, connector);
+}
+
+//  static
+std::unique_ptr<FidoDeviceDiscovery> FidoDeviceDiscovery::CreateCable(
+    std::vector<CableDiscoveryData> cable_data) {
+  return (*g_cable_factory_func_)(std::move(cable_data));
+}
+
+FidoDeviceDiscovery::FidoDeviceDiscovery(FidoTransportProtocol transport)
+    : FidoDiscoveryBase(transport), weak_factory_(this) {}
+
+FidoDeviceDiscovery::~FidoDeviceDiscovery() = default;
+
+void FidoDeviceDiscovery::Start() {
+  DCHECK_EQ(state_, State::kIdle);
+  state_ = State::kStarting;
+  // TODO(hongjunchoi): Fix so that NotifiyStarted() is never called
+  // synchronously after StartInternal().
+  // See: https://crbug.com/823686
+  StartInternal();
+}
+
+void FidoDeviceDiscovery::NotifyDiscoveryStarted(bool success) {
+  DCHECK_EQ(state_, State::kStarting);
+  if (success)
+    state_ = State::kRunning;
+  if (!observer())
+    return;
+  observer()->DiscoveryStarted(this, success);
+}
+
+void FidoDeviceDiscovery::NotifyAuthenticatorAdded(
+    FidoAuthenticator* authenticator) {
+  DCHECK_NE(state_, State::kIdle);
+  if (!observer())
+    return;
+  observer()->AuthenticatorAdded(this, authenticator);
+}
+
+void FidoDeviceDiscovery::NotifyAuthenticatorRemoved(
+    FidoAuthenticator* authenticator) {
+  DCHECK_NE(state_, State::kIdle);
+  if (!observer())
+    return;
+  observer()->AuthenticatorRemoved(this, authenticator);
+}
+
+std::vector<FidoDeviceAuthenticator*>
+FidoDeviceDiscovery::GetAuthenticatorsForTesting() {
+  std::vector<FidoDeviceAuthenticator*> authenticators;
+  authenticators.reserve(authenticators_.size());
+  for (const auto& authenticator : authenticators_)
+    authenticators.push_back(authenticator.second.get());
+  return authenticators;
+}
+
+std::vector<const FidoDeviceAuthenticator*>
+FidoDeviceDiscovery::GetAuthenticatorsForTesting() const {
+  std::vector<const FidoDeviceAuthenticator*> authenticators;
+  authenticators.reserve(authenticators_.size());
+  for (const auto& authenticator : authenticators_)
+    authenticators.push_back(authenticator.second.get());
+  return authenticators;
+}
+
+FidoDeviceAuthenticator* FidoDeviceDiscovery::GetAuthenticatorForTesting(
+    base::StringPiece authenticator_id) {
+  return GetAuthenticator(authenticator_id);
+}
+
+FidoDeviceAuthenticator* FidoDeviceDiscovery::GetAuthenticator(
+    base::StringPiece authenticator_id) {
+  auto found = authenticators_.find(authenticator_id);
+  return found != authenticators_.end() ? found->second.get() : nullptr;
+}
+
+bool FidoDeviceDiscovery::AddDevice(std::unique_ptr<FidoDevice> device) {
+  auto authenticator =
+      std::make_unique<FidoDeviceAuthenticator>(std::move(device));
+  const auto result =
+      authenticators_.emplace(authenticator->GetId(), std::move(authenticator));
+  if (!result.second) {
+    return false;  // Duplicate device id.
+  }
+
+  NotifyAuthenticatorAdded(result.first->second.get());
+  return true;
+}
+
+bool FidoDeviceDiscovery::RemoveDevice(base::StringPiece device_id) {
+  auto found = authenticators_.find(device_id);
+  if (found == authenticators_.end())
+    return false;
+
+  auto authenticator = std::move(found->second);
+  authenticators_.erase(found);
+  NotifyAuthenticatorRemoved(authenticator.get());
+  return true;
+}
+
+// ScopedFidoDiscoveryFactory -------------------------------------------------
+
+namespace internal {
+
+ScopedFidoDiscoveryFactory::ScopedFidoDiscoveryFactory() {
+  DCHECK(!g_current_factory);
+  g_current_factory = this;
+  original_factory_func_ =
+      std::exchange(FidoDeviceDiscovery::g_factory_func_,
+                    &ForwardCreateFidoDiscoveryToCurrentFactory);
+  original_cable_factory_func_ =
+      std::exchange(FidoDeviceDiscovery::g_cable_factory_func_,
+                    &ForwardCreateCableDiscoveryToCurrentFactory);
+}
+
+ScopedFidoDiscoveryFactory::~ScopedFidoDiscoveryFactory() {
+  g_current_factory = nullptr;
+  FidoDeviceDiscovery::g_factory_func_ = original_factory_func_;
+  FidoDeviceDiscovery::g_cable_factory_func_ = original_cable_factory_func_;
+}
+
+// static
+std::unique_ptr<FidoDeviceDiscovery>
+ScopedFidoDiscoveryFactory::ForwardCreateFidoDiscoveryToCurrentFactory(
+    FidoTransportProtocol transport,
+    ::service_manager::Connector* connector) {
+  DCHECK(g_current_factory);
+  return g_current_factory->CreateFidoDiscovery(transport, connector);
+}
+
+// static
+std::unique_ptr<FidoDeviceDiscovery>
+ScopedFidoDiscoveryFactory::ForwardCreateCableDiscoveryToCurrentFactory(
+    std::vector<CableDiscoveryData> cable_data) {
+  DCHECK(g_current_factory);
+  g_current_factory->set_last_cable_data(std::move(cable_data));
+  return g_current_factory->CreateFidoDiscovery(
+      FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy,
+      nullptr /* connector */);
+}
+
+// static
+ScopedFidoDiscoveryFactory* ScopedFidoDiscoveryFactory::g_current_factory =
+    nullptr;
+
+}  // namespace internal
+}  // namespace device
diff --git a/device/fido/fido_discovery.h b/device/fido/fido_device_discovery.h
similarity index 71%
rename from device/fido/fido_discovery.h
rename to device/fido/fido_device_discovery.h
index e8d67c7..d5b844fac 100644
--- a/device/fido/fido_discovery.h
+++ b/device/fido/fido_device_discovery.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef DEVICE_FIDO_FIDO_DISCOVERY_H_
-#define DEVICE_FIDO_FIDO_DISCOVERY_H_
+#ifndef DEVICE_FIDO_FIDO_DEVICE_DISCOVERY_H_
+#define DEVICE_FIDO_FIDO_DEVICE_DISCOVERY_H_
 
 #include <functional>
 #include <map>
@@ -28,12 +28,14 @@
 namespace device {
 
 class FidoDevice;
+class FidoDeviceAuthenticator;
 
 namespace internal {
 class ScopedFidoDiscoveryFactory;
 }
 
-class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscovery : public FidoDiscoveryBase {
+class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceDiscovery
+    : public FidoDiscoveryBase {
  public:
   enum class State {
     kIdle,
@@ -47,29 +49,28 @@
   //
   // FidoTransportProtocol::kUsbHumanInterfaceDevice requires specifying a valid
   // |connector| on Desktop, and is not valid on Android.
-  static std::unique_ptr<FidoDiscovery> Create(
+  static std::unique_ptr<FidoDeviceDiscovery> Create(
       FidoTransportProtocol transport,
       ::service_manager::Connector* connector);
-  static std::unique_ptr<FidoDiscovery> CreateCable(
+  static std::unique_ptr<FidoDeviceDiscovery> CreateCable(
       std::vector<CableDiscoveryData> cable_data);
 
-  ~FidoDiscovery() override;
+  ~FidoDeviceDiscovery() override;
 
   bool is_start_requested() const { return state_ != State::kIdle; }
   bool is_running() const { return state_ == State::kRunning; }
 
-  std::vector<FidoDevice*> GetDevices();
-  std::vector<const FidoDevice*> GetDevices() const;
-
-  // TODO(martinkr): Rename to GetDeviceForTesting.
-  FidoDevice* GetDevice(base::StringPiece device_id);
-  const FidoDevice* GetDevice(base::StringPiece device_id) const;
+  std::vector<FidoDeviceAuthenticator*> GetAuthenticatorsForTesting();
+  std::vector<const FidoDeviceAuthenticator*> GetAuthenticatorsForTesting()
+      const;
+  FidoDeviceAuthenticator* GetAuthenticatorForTesting(
+      base::StringPiece authenticator_id);
 
   // FidoDiscoveryBase:
   void Start() override;
 
  protected:
-  FidoDiscovery(FidoTransportProtocol transport);
+  FidoDeviceDiscovery(FidoTransportProtocol transport);
 
   void NotifyDiscoveryStarted(bool success);
   void NotifyAuthenticatorAdded(FidoAuthenticator* authenticator);
@@ -78,6 +79,8 @@
   bool AddDevice(std::unique_ptr<FidoDevice> device);
   bool RemoveDevice(base::StringPiece device_id);
 
+  FidoDeviceAuthenticator* GetAuthenticator(base::StringPiece authenticator_id);
+
   // Subclasses should implement this to actually start the discovery when it is
   // requested.
   //
@@ -85,11 +88,10 @@
   // the discovery is s tarted.
   virtual void StartInternal() = 0;
 
-  std::map<std::string,
-           std::pair<std::unique_ptr<FidoAuthenticator>,
-                     std::unique_ptr<FidoDevice>>,
-           std::less<>>
-      devices_;
+  // Map of ID to authenticator. It is a guarantee to subclasses that the ID of
+  // the authenticator equals the ID of the device.
+  std::map<std::string, std::unique_ptr<FidoDeviceAuthenticator>, std::less<>>
+      authenticators_;
 
  private:
   friend class internal::ScopedFidoDiscoveryFactory;
@@ -101,14 +103,14 @@
   static CableFactoryFuncPtr g_cable_factory_func_;
 
   State state_ = State::kIdle;
-  base::WeakPtrFactory<FidoDiscovery> weak_factory_;
+  base::WeakPtrFactory<FidoDeviceDiscovery> weak_factory_;
 
-  DISALLOW_COPY_AND_ASSIGN(FidoDiscovery);
+  DISALLOW_COPY_AND_ASSIGN(FidoDeviceDiscovery);
 };
 
 namespace internal {
 
-// Base class for a scoped override of FidoDiscovery::Create, used in unit
+// Base class for a scoped override of FidoDeviceDiscovery::Create, used in unit
 // tests, layout tests, and when running with the Web Authn Testing API enabled.
 //
 // While there is a subclass instance in scope, calls to the factory method will
@@ -129,24 +131,24 @@
     last_cable_data_ = std::move(cable_data);
   }
 
-  virtual std::unique_ptr<FidoDiscovery> CreateFidoDiscovery(
+  virtual std::unique_ptr<FidoDeviceDiscovery> CreateFidoDiscovery(
       FidoTransportProtocol transport,
       ::service_manager::Connector* connector) = 0;
 
  private:
-  static std::unique_ptr<FidoDiscovery>
+  static std::unique_ptr<FidoDeviceDiscovery>
   ForwardCreateFidoDiscoveryToCurrentFactory(
       FidoTransportProtocol transport,
       ::service_manager::Connector* connector);
 
-  static std::unique_ptr<FidoDiscovery>
+  static std::unique_ptr<FidoDeviceDiscovery>
   ForwardCreateCableDiscoveryToCurrentFactory(
       std::vector<CableDiscoveryData> cable_data);
 
   static ScopedFidoDiscoveryFactory* g_current_factory;
 
-  FidoDiscovery::FactoryFuncPtr original_factory_func_;
-  FidoDiscovery::CableFactoryFuncPtr original_cable_factory_func_;
+  FidoDeviceDiscovery::FactoryFuncPtr original_factory_func_;
+  FidoDeviceDiscovery::CableFactoryFuncPtr original_cable_factory_func_;
   std::vector<CableDiscoveryData> last_cable_data_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedFidoDiscoveryFactory);
@@ -155,4 +157,4 @@
 }  // namespace internal
 }  // namespace device
 
-#endif  // DEVICE_FIDO_FIDO_DISCOVERY_H_
+#endif  // DEVICE_FIDO_FIDO_DEVICE_DISCOVERY_H_
diff --git a/device/fido/fido_discovery_unittest.cc b/device/fido/fido_device_discovery_unittest.cc
similarity index 82%
rename from device/fido/fido_discovery_unittest.cc
rename to device/fido/fido_device_discovery_unittest.cc
index 4ebf0ab1..9e984c94 100644
--- a/device/fido/fido_discovery_unittest.cc
+++ b/device/fido/fido_device_discovery_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 
 #include <utility>
 
@@ -27,18 +27,18 @@
 using ::testing::SaveArg;
 using ::testing::UnorderedElementsAre;
 
-// A minimal implementation of FidoDiscovery that is no longer abstract.
-class ConcreteFidoDiscovery : public FidoDiscovery {
+// A minimal implementation of FidoDeviceDiscovery that is no longer abstract.
+class ConcreteFidoDiscovery : public FidoDeviceDiscovery {
  public:
   explicit ConcreteFidoDiscovery(FidoTransportProtocol transport)
-      : FidoDiscovery(transport) {}
+      : FidoDeviceDiscovery(transport) {}
   ~ConcreteFidoDiscovery() override = default;
 
   MOCK_METHOD0(StartInternal, void());
 
-  using FidoDiscovery::AddDevice;
-  using FidoDiscovery::NotifyDiscoveryStarted;
-  using FidoDiscovery::RemoveDevice;
+  using FidoDeviceDiscovery::AddDevice;
+  using FidoDeviceDiscovery::NotifyDiscoveryStarted;
+  using FidoDeviceDiscovery::RemoveDevice;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ConcreteFidoDiscovery);
@@ -112,8 +112,8 @@
   EXPECT_CALL(*device0, GetId()).WillRepeatedly(Return("device0"));
   EXPECT_TRUE(discovery.AddDevice(std::move(device0)));
   EXPECT_EQ("device0", authenticator0->GetId());
-  EXPECT_EQ(device0_raw, static_cast<FidoDeviceAuthenticator*>(authenticator0)
-                             ->GetDeviceForTesting());
+  EXPECT_EQ(device0_raw,
+            static_cast<FidoDeviceAuthenticator*>(authenticator0)->device());
   device0_done.Run();
   ::testing::Mock::VerifyAndClearExpectations(&observer);
 
@@ -129,8 +129,8 @@
   EXPECT_CALL(*device1, GetId()).WillRepeatedly(Return("device1"));
   EXPECT_TRUE(discovery.AddDevice(std::move(device1)));
   EXPECT_EQ("device1", authenticator1->GetId());
-  EXPECT_EQ(device1_raw, static_cast<FidoDeviceAuthenticator*>(authenticator1)
-                             ->GetDeviceForTesting());
+  EXPECT_EQ(device1_raw,
+            static_cast<FidoDeviceAuthenticator*>(authenticator1)->device());
   device1_done.Run();
   ::testing::Mock::VerifyAndClearExpectations(&observer);
 
@@ -141,16 +141,14 @@
   EXPECT_FALSE(discovery.AddDevice(std::move(device1_dup)));
   ::testing::Mock::VerifyAndClearExpectations(&observer);
 
-  EXPECT_EQ(device0_raw, discovery.GetDevice("device0"));
-  EXPECT_EQ(device1_raw, discovery.GetDevice("device1"));
-  EXPECT_THAT(discovery.GetDevices(),
-              UnorderedElementsAre(device0_raw, device1_raw));
+  EXPECT_EQ(authenticator0, discovery.GetAuthenticatorForTesting("device0"));
+  EXPECT_EQ(authenticator1, discovery.GetAuthenticatorForTesting("device1"));
+  EXPECT_THAT(discovery.GetAuthenticatorsForTesting(),
+              UnorderedElementsAre(authenticator0, authenticator1));
 
-  const FidoDiscovery& const_discovery = discovery;
-  EXPECT_EQ(device0_raw, const_discovery.GetDevice("device0"));
-  EXPECT_EQ(device1_raw, const_discovery.GetDevice("device1"));
-  EXPECT_THAT(const_discovery.GetDevices(),
-              UnorderedElementsAre(device0_raw, device1_raw));
+  const FidoDeviceDiscovery& const_discovery = discovery;
+  EXPECT_THAT(const_discovery.GetAuthenticatorsForTesting(),
+              UnorderedElementsAre(authenticator0, authenticator1));
 
   // Trying to remove a non-present device should fail.
   EXPECT_CALL(observer, AuthenticatorRemoved(_, _)).Times(0);
diff --git a/device/fido/fido_discovery.cc b/device/fido/fido_discovery.cc
deleted file mode 100644
index ccedf32..0000000
--- a/device/fido/fido_discovery.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/fido/fido_discovery.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "build/build_config.h"
-#include "device/fido/ble/fido_ble_discovery.h"
-#include "device/fido/cable/fido_cable_discovery.h"
-#include "device/fido/fido_authenticator.h"
-#include "device/fido/fido_device.h"
-#include "device/fido/fido_device_authenticator.h"
-
-// HID is not supported on Android.
-#if !defined(OS_ANDROID)
-#include "device/fido/hid/fido_hid_discovery.h"
-#endif  // !defined(OS_ANDROID)
-
-namespace device {
-
-namespace {
-
-std::unique_ptr<FidoDiscovery> CreateFidoDiscoveryImpl(
-    FidoTransportProtocol transport,
-    service_manager::Connector* connector) {
-  switch (transport) {
-    case FidoTransportProtocol::kUsbHumanInterfaceDevice:
-#if !defined(OS_ANDROID)
-      DCHECK(connector);
-      return std::make_unique<FidoHidDiscovery>(connector);
-#else
-      NOTREACHED() << "USB HID not supported on Android.";
-      return nullptr;
-#endif  // !defined(OS_ANDROID)
-    case FidoTransportProtocol::kBluetoothLowEnergy:
-      return std::make_unique<FidoBleDiscovery>();
-    case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy:
-      NOTREACHED() << "Cable discovery is constructed using the dedicated "
-                      "factory method.";
-      return nullptr;
-    case FidoTransportProtocol::kNearFieldCommunication:
-      // TODO(https://crbug.com/825949): Add NFC support.
-      return nullptr;
-    case FidoTransportProtocol::kInternal:
-      NOTREACHED() << "Internal authenticators should be handled separately.";
-      return nullptr;
-  }
-  NOTREACHED() << "Unhandled transport type";
-  return nullptr;
-}
-
-std::unique_ptr<FidoDiscovery> CreateCableDiscoveryImpl(
-    std::vector<CableDiscoveryData> cable_data) {
-  return std::make_unique<FidoCableDiscovery>(std::move(cable_data));
-}
-
-}  // namespace
-
-FidoDiscovery::Observer::~Observer() = default;
-
-// static
-FidoDiscovery::FactoryFuncPtr FidoDiscovery::g_factory_func_ =
-    &CreateFidoDiscoveryImpl;
-
-// static
-FidoDiscovery::CableFactoryFuncPtr FidoDiscovery::g_cable_factory_func_ =
-    &CreateCableDiscoveryImpl;
-
-// static
-std::unique_ptr<FidoDiscovery> FidoDiscovery::Create(
-    FidoTransportProtocol transport,
-    service_manager::Connector* connector) {
-  return (*g_factory_func_)(transport, connector);
-}
-
-//  static
-std::unique_ptr<FidoDiscovery> FidoDiscovery::CreateCable(
-    std::vector<CableDiscoveryData> cable_data) {
-  return (*g_cable_factory_func_)(std::move(cable_data));
-}
-
-FidoDiscovery::FidoDiscovery(FidoTransportProtocol transport)
-    : FidoDiscoveryBase(transport), weak_factory_(this) {}
-
-FidoDiscovery::~FidoDiscovery() = default;
-
-void FidoDiscovery::Start() {
-  DCHECK_EQ(state_, State::kIdle);
-  state_ = State::kStarting;
-  // TODO(hongjunchoi): Fix so that NotifiyStarted() is never called
-  // synchronously after StartInternal().
-  // See: https://crbug.com/823686
-  StartInternal();
-}
-
-void FidoDiscovery::NotifyDiscoveryStarted(bool success) {
-  DCHECK_EQ(state_, State::kStarting);
-  if (success)
-    state_ = State::kRunning;
-  if (!observer())
-    return;
-  observer()->DiscoveryStarted(this, success);
-}
-
-void FidoDiscovery::NotifyAuthenticatorAdded(FidoAuthenticator* authenticator) {
-  DCHECK_NE(state_, State::kIdle);
-  if (!observer())
-    return;
-  observer()->AuthenticatorAdded(this, authenticator);
-}
-
-void FidoDiscovery::NotifyAuthenticatorRemoved(
-    FidoAuthenticator* authenticator) {
-  DCHECK_NE(state_, State::kIdle);
-  if (!observer())
-    return;
-  observer()->AuthenticatorRemoved(this, authenticator);
-}
-
-std::vector<FidoDevice*> FidoDiscovery::GetDevices() {
-  std::vector<FidoDevice*> devices;
-  devices.reserve(devices_.size());
-  for (const auto& device : devices_)
-    devices.push_back(device.second.second.get());
-  return devices;
-}
-
-std::vector<const FidoDevice*> FidoDiscovery::GetDevices() const {
-  std::vector<const FidoDevice*> devices;
-  devices.reserve(devices_.size());
-  for (const auto& device : devices_)
-    devices.push_back(device.second.second.get());
-  return devices;
-}
-
-FidoDevice* FidoDiscovery::GetDevice(base::StringPiece device_id) {
-  return const_cast<FidoDevice*>(
-      static_cast<const FidoDiscovery*>(this)->GetDevice(device_id));
-}
-
-const FidoDevice* FidoDiscovery::GetDevice(base::StringPiece device_id) const {
-  auto found = devices_.find(device_id);
-  return found != devices_.end() ? found->second.second.get() : nullptr;
-}
-
-bool FidoDiscovery::AddDevice(std::unique_ptr<FidoDevice> device) {
-  std::string device_id = device->GetId();
-  auto authenticator = std::make_unique<FidoDeviceAuthenticator>(device.get());
-  const auto result = devices_.emplace(
-      std::move(device_id),
-      std::make_pair(std::move(authenticator), std::move(device)));
-  if (!result.second) {
-    return false;  // Duplicate device id.
-  }
-
-  NotifyAuthenticatorAdded(result.first->second.first.get());
-  return true;
-}
-
-bool FidoDiscovery::RemoveDevice(base::StringPiece device_id) {
-  auto found = devices_.find(device_id);
-  if (found == devices_.end())
-    return false;
-
-  auto authenticator_device_pair = std::move(found->second);
-  devices_.erase(found);
-  NotifyAuthenticatorRemoved(authenticator_device_pair.first.get());
-  return true;
-}
-
-// ScopedFidoDiscoveryFactory -------------------------------------------------
-
-namespace internal {
-
-ScopedFidoDiscoveryFactory::ScopedFidoDiscoveryFactory() {
-  DCHECK(!g_current_factory);
-  g_current_factory = this;
-  original_factory_func_ =
-      std::exchange(FidoDiscovery::g_factory_func_,
-                    &ForwardCreateFidoDiscoveryToCurrentFactory);
-  original_cable_factory_func_ =
-      std::exchange(FidoDiscovery::g_cable_factory_func_,
-                    &ForwardCreateCableDiscoveryToCurrentFactory);
-}
-
-ScopedFidoDiscoveryFactory::~ScopedFidoDiscoveryFactory() {
-  g_current_factory = nullptr;
-  FidoDiscovery::g_factory_func_ = original_factory_func_;
-  FidoDiscovery::g_cable_factory_func_ = original_cable_factory_func_;
-}
-
-// static
-std::unique_ptr<FidoDiscovery>
-ScopedFidoDiscoveryFactory::ForwardCreateFidoDiscoveryToCurrentFactory(
-    FidoTransportProtocol transport,
-    ::service_manager::Connector* connector) {
-  DCHECK(g_current_factory);
-  return g_current_factory->CreateFidoDiscovery(transport, connector);
-}
-
-// static
-std::unique_ptr<FidoDiscovery>
-ScopedFidoDiscoveryFactory::ForwardCreateCableDiscoveryToCurrentFactory(
-    std::vector<CableDiscoveryData> cable_data) {
-  DCHECK(g_current_factory);
-  g_current_factory->set_last_cable_data(std::move(cable_data));
-  return g_current_factory->CreateFidoDiscovery(
-      FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy,
-      nullptr /* connector */);
-}
-
-// static
-ScopedFidoDiscoveryFactory* ScopedFidoDiscoveryFactory::g_current_factory =
-    nullptr;
-
-}  // namespace internal
-}  // namespace device
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc
index 54138b6..6164f84 100644
--- a/device/fido/fido_request_handler_base.cc
+++ b/device/fido/fido_request_handler_base.cc
@@ -84,7 +84,7 @@
       continue;
     }
 
-    auto discovery = FidoDiscovery::Create(transport, connector);
+    auto discovery = FidoDeviceDiscovery::Create(transport, connector);
     if (discovery == nullptr) {
       // This can occur in tests when a ScopedVirtualU2fDevice is in effect and
       // HID transports are not configured.
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 550d36d..76a301c 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -20,7 +20,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece_forward.h"
 #include "device/fido/fido_device_authenticator.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "device/fido/fido_transport_protocol.h"
 
 namespace service_manager {
@@ -223,8 +223,8 @@
   TransportAvailabilityInfo transport_availability_info_;
   base::RepeatingClosure notify_observer_callback_;
   std::unique_ptr<BleAdapterManager> bluetooth_adapter_manager_;
-  // TODO(martinkr): Inject platform authenticators through FidoDiscovery and
-  // hold ownership there.
+  // TODO(martinkr): Inject platform authenticators through a new
+  // FidoDiscoveryBase specialization and hold ownership there.
   std::unique_ptr<FidoAuthenticator> platform_authenticator_;
 
   base::WeakPtrFactory<FidoRequestHandlerBase> weak_factory_;
diff --git a/device/fido/fido_request_handler_unittest.cc b/device/fido/fido_request_handler_unittest.cc
index 117d54a..aea5531 100644
--- a/device/fido/fido_request_handler_unittest.cc
+++ b/device/fido/fido_request_handler_unittest.cc
@@ -163,8 +163,8 @@
 
 class FakeFidoAuthenticator : public FidoDeviceAuthenticator {
  public:
-  explicit FakeFidoAuthenticator(FidoDevice* device)
-      : FidoDeviceAuthenticator(device) {}
+  explicit FakeFidoAuthenticator(std::unique_ptr<FidoDevice> device)
+      : FidoDeviceAuthenticator(std::move(device)) {}
 
   void RunFakeTask(FakeTaskCallback callback) {
     SetTaskForTesting(
@@ -506,7 +506,8 @@
   device->ExpectRequestAndRespondWith(std::vector<uint8_t>(),
                                       CreateFakeSuccessDeviceResponse());
   device->SetDeviceTransport(FidoTransportProtocol::kInternal);
-  auto authenticator = std::make_unique<FakeFidoAuthenticator>(device.get());
+  auto authenticator =
+      std::make_unique<FakeFidoAuthenticator>(std::move(device));
 
   TestTransportAvailabilityObserver observer;
   auto request_handler = std::make_unique<FakeFidoRequestHandler>(
@@ -541,7 +542,8 @@
   device->ExpectRequestAndRespondWith(std::vector<uint8_t>(),
                                       CreateFakeSuccessDeviceResponse());
   device->SetDeviceTransport(FidoTransportProtocol::kInternal);
-  auto authenticator = std::make_unique<FakeFidoAuthenticator>(device.get());
+  auto authenticator =
+      std::make_unique<FakeFidoAuthenticator>(std::move(device));
 
   TestTransportAvailabilityObserver observer;
   auto request_handler = std::make_unique<FakeFidoRequestHandler>(
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index e7626578..c31d827 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -147,7 +147,7 @@
   }
 
   void set_mock_platform_device(std::unique_ptr<MockFidoDevice> device) {
-    mock_platform_device_ = std::move(device);
+    pending_mock_platform_device_ = std::move(device);
   }
 
   void set_supported_transports(
@@ -157,10 +157,11 @@
 
  protected:
   base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() {
-    if (!mock_platform_device_)
+    if (!pending_mock_platform_device_)
       return base::nullopt;
     return PlatformAuthenticatorInfo(
-        std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()),
+        std::make_unique<FidoDeviceAuthenticator>(
+            std::move(pending_mock_platform_device_)),
         false /* has_recognized_mac_touch_id_credential_available */);
   }
 
@@ -173,7 +174,7 @@
   test::FakeFidoDiscovery* cable_discovery_;
   test::FakeFidoDiscovery* nfc_discovery_;
   scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
-  std::unique_ptr<MockFidoDevice> mock_platform_device_;
+  std::unique_ptr<MockFidoDevice> pending_mock_platform_device_;
   TestGetAssertionRequestCallback get_assertion_cb_;
   base::flat_set<FidoTransportProtocol> supported_transports_ =
       GetAllTransportProtocols();
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 1e7888a..9d71e48 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -175,7 +175,8 @@
           transport_availability_info().available_transports,
           FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) {
     DCHECK(request_.cable_extension());
-    auto discovery = FidoDiscovery::CreateCable(*request_.cable_extension());
+    auto discovery =
+        FidoDeviceDiscovery::CreateCable(*request_.cable_extension());
     discovery->set_observer(this);
     discoveries().push_back(std::move(discovery));
   }
diff --git a/device/fido/hid/fido_hid_discovery.cc b/device/fido/hid/fido_hid_discovery.cc
index b1eb519..92b908ec 100644
--- a/device/fido/hid/fido_hid_discovery.cc
+++ b/device/fido/hid/fido_hid_discovery.cc
@@ -14,7 +14,7 @@
 namespace device {
 
 FidoHidDiscovery::FidoHidDiscovery(::service_manager::Connector* connector)
-    : FidoDiscovery(FidoTransportProtocol::kUsbHumanInterfaceDevice),
+    : FidoDeviceDiscovery(FidoTransportProtocol::kUsbHumanInterfaceDevice),
       connector_(connector),
       binding_(this),
       weak_factory_(this) {
diff --git a/device/fido/hid/fido_hid_discovery.h b/device/fido/hid/fido_hid_discovery.h
index c6a5e78..f2aae1a 100644
--- a/device/fido/hid/fido_hid_discovery.h
+++ b/device/fido/hid/fido_hid_discovery.h
@@ -11,7 +11,7 @@
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "services/device/public/cpp/hid/hid_device_filter.h"
 #include "services/device/public/mojom/hid.mojom.h"
@@ -26,14 +26,14 @@
 // servicification is unblocked, we'll move U2F back to //service/device/.
 // Then it will talk to HID via C++ as part of servicifying U2F.
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDiscovery
-    : public FidoDiscovery,
+    : public FidoDeviceDiscovery,
       device::mojom::HidManagerClient {
  public:
   explicit FidoHidDiscovery(::service_manager::Connector* connector);
   ~FidoHidDiscovery() override;
 
  private:
-  // FidoDiscovery:
+  // FidoDeviceDiscovery:
   void StartInternal() override;
 
   // device::mojom::HidManagerClient implementation:
diff --git a/device/fido/make_credential_handler_unittest.cc b/device/fido/make_credential_handler_unittest.cc
index 8b47f1b..9fb5b67c 100644
--- a/device/fido/make_credential_handler_unittest.cc
+++ b/device/fido/make_credential_handler_unittest.cc
@@ -120,7 +120,7 @@
   TestMakeCredentialRequestCallback& callback() { return cb_; }
 
   void set_mock_platform_device(std::unique_ptr<MockFidoDevice> device) {
-    mock_platform_device_ = std::move(device);
+    pending_mock_platform_device_ = std::move(device);
   }
 
   void set_supported_transports(
@@ -130,10 +130,11 @@
 
  protected:
   base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() {
-    if (!mock_platform_device_)
+    if (!pending_mock_platform_device_)
       return base::nullopt;
     return PlatformAuthenticatorInfo(
-        std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()),
+        std::make_unique<FidoDeviceAuthenticator>(
+            std::move(pending_mock_platform_device_)),
         false /* has_recognized_mac_touch_id_credential_available */);
   }
 
@@ -145,7 +146,7 @@
   test::FakeFidoDiscovery* ble_discovery_;
   test::FakeFidoDiscovery* nfc_discovery_;
   scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
-  std::unique_ptr<MockFidoDevice> mock_platform_device_;
+  std::unique_ptr<MockFidoDevice> pending_mock_platform_device_;
   TestMakeCredentialRequestCallback cb_;
   base::flat_set<FidoTransportProtocol> supported_transports_ =
       GetAllTransportProtocols();
diff --git a/device/fido/mock_fido_device.h b/device/fido/mock_fido_device.h
index 2337341..83dfaca 100644
--- a/device/fido/mock_fido_device.h
+++ b/device/fido/mock_fido_device.h
@@ -27,19 +27,19 @@
  public:
   // MakeU2f returns a fully initialized U2F device. This represents the state
   // after |DiscoverSupportedProtocolAndDeviceInfo| has been called by the
-  // FidoDiscovery.
+  // FidoDeviceDiscovery.
   static std::unique_ptr<MockFidoDevice> MakeU2f();
   // MakeCtap returns a fully initialized CTAP device. This represents the
   // state after |DiscoverSupportedProtocolAndDeviceInfo| has been called by
-  // the FidoDiscovery.
+  // the FidoDeviceDiscovery.
   static std::unique_ptr<MockFidoDevice> MakeCtap(
       base::Optional<AuthenticatorGetInfoResponse> device_info = base::nullopt);
   // MakeU2fWithDeviceInfoExpectation returns a uninitialized U2F device
-  // suitable for injecting into a FidoDiscovery, which will determine its
+  // suitable for injecting into a FidoDeviceDiscovery, which will determine its
   // protocol version by invoking |DiscoverSupportedProtocolAndDeviceInfo|.
   static std::unique_ptr<MockFidoDevice> MakeU2fWithGetInfoExpectation();
   // MakeCtapWithDeviceInfoExpectation returns a uninitialized CTAP device
-  // suitable for injecting into a FidoDiscovery, which will determine its
+  // suitable for injecting into a FidoDeviceDiscovery, which will determine its
   // protocol version by invoking |DiscoverSupportedProtocolAndDeviceInfo|. If a
   // response is supplied, the mock will use that to reply; otherwise it will
   // use |test_data::kTestAuthenticatorGetInfoResponse|.
diff --git a/device/fido/mock_fido_discovery_observer.h b/device/fido/mock_fido_discovery_observer.h
index 21bd96ad..883b20a 100644
--- a/device/fido/mock_fido_discovery_observer.h
+++ b/device/fido/mock_fido_discovery_observer.h
@@ -9,7 +9,7 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace device {
diff --git a/device/fido/scoped_virtual_fido_device.cc b/device/fido/scoped_virtual_fido_device.cc
index b0807e3..7bfd960d 100644
--- a/device/fido/scoped_virtual_fido_device.cc
+++ b/device/fido/scoped_virtual_fido_device.cc
@@ -18,15 +18,15 @@
 namespace device {
 namespace test {
 
-// A FidoDiscovery that always vends a single |VirtualFidoDevice|.
+// A FidoDeviceDiscovery that always vends a single |VirtualFidoDevice|.
 class VirtualFidoDeviceDiscovery
-    : public FidoDiscovery,
+    : public FidoDeviceDiscovery,
       public base::SupportsWeakPtr<VirtualFidoDeviceDiscovery> {
  public:
   explicit VirtualFidoDeviceDiscovery(
       scoped_refptr<VirtualFidoDevice::State> state,
       ProtocolVersion supported_protocol)
-      : FidoDiscovery(FidoTransportProtocol::kUsbHumanInterfaceDevice),
+      : FidoDeviceDiscovery(FidoTransportProtocol::kUsbHumanInterfaceDevice),
         state_(std::move(state)),
         supported_protocol_(supported_protocol) {}
   ~VirtualFidoDeviceDiscovery() override = default;
@@ -65,7 +65,8 @@
   return state_.get();
 }
 
-std::unique_ptr<FidoDiscovery> ScopedVirtualFidoDevice::CreateFidoDiscovery(
+std::unique_ptr<FidoDeviceDiscovery>
+ScopedVirtualFidoDevice::CreateFidoDiscovery(
     FidoTransportProtocol transport,
     ::service_manager::Connector* connector) {
   if (transport != FidoTransportProtocol::kUsbHumanInterfaceDevice) {
diff --git a/device/fido/scoped_virtual_fido_device.h b/device/fido/scoped_virtual_fido_device.h
index 4c63e52..88bc6b4e 100644
--- a/device/fido/scoped_virtual_fido_device.h
+++ b/device/fido/scoped_virtual_fido_device.h
@@ -9,7 +9,7 @@
 
 #include "base/macros.h"
 #include "device/fido/fido_constants.h"
-#include "device/fido/fido_discovery.h"
+#include "device/fido/fido_device_discovery.h"
 #include "device/fido/virtual_fido_device.h"
 
 namespace device {
@@ -28,7 +28,7 @@
   VirtualFidoDevice::State* mutable_state();
 
  protected:
-  std::unique_ptr<FidoDiscovery> CreateFidoDiscovery(
+  std::unique_ptr<FidoDeviceDiscovery> CreateFidoDiscovery(
       FidoTransportProtocol transport,
       ::service_manager::Connector* connector) override;
 
diff --git a/device/vr/android/arcore/arcore_device_provider_factory.cc b/device/vr/android/arcore/arcore_device_provider_factory.cc
index 16da7db..43ee7b6 100644
--- a/device/vr/android/arcore/arcore_device_provider_factory.cc
+++ b/device/vr/android/arcore/arcore_device_provider_factory.cc
@@ -10,20 +10,20 @@
 namespace device {
 
 namespace {
-ARCoreDeviceProviderFactory* g_arcore_device_provider_factory = nullptr;
+ArCoreDeviceProviderFactory* g_arcore_device_provider_factory = nullptr;
 }  // namespace
 
 // static
 std::unique_ptr<device::VRDeviceProvider>
-ARCoreDeviceProviderFactory::Create() {
+ArCoreDeviceProviderFactory::Create() {
   if (!g_arcore_device_provider_factory)
     return nullptr;
   return g_arcore_device_provider_factory->CreateDeviceProvider();
 }
 
 // static
-void ARCoreDeviceProviderFactory::Install(
-    std::unique_ptr<ARCoreDeviceProviderFactory> factory) {
+void ArCoreDeviceProviderFactory::Install(
+    std::unique_ptr<ArCoreDeviceProviderFactory> factory) {
   DCHECK_NE(g_arcore_device_provider_factory, factory.get());
   if (g_arcore_device_provider_factory)
     delete g_arcore_device_provider_factory;
diff --git a/device/vr/android/arcore/arcore_device_provider_factory.h b/device/vr/android/arcore/arcore_device_provider_factory.h
index 2b29ec28..79472ef 100644
--- a/device/vr/android/arcore/arcore_device_provider_factory.h
+++ b/device/vr/android/arcore/arcore_device_provider_factory.h
@@ -13,20 +13,20 @@
 
 class VRDeviceProvider;
 
-class DEVICE_VR_EXPORT ARCoreDeviceProviderFactory {
+class DEVICE_VR_EXPORT ArCoreDeviceProviderFactory {
  public:
   static std::unique_ptr<device::VRDeviceProvider> Create();
-  static void Install(std::unique_ptr<ARCoreDeviceProviderFactory> factory);
+  static void Install(std::unique_ptr<ArCoreDeviceProviderFactory> factory);
 
-  virtual ~ARCoreDeviceProviderFactory() = default;
+  virtual ~ArCoreDeviceProviderFactory() = default;
 
  protected:
-  ARCoreDeviceProviderFactory() = default;
+  ArCoreDeviceProviderFactory() = default;
 
   virtual std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() = 0;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ARCoreDeviceProviderFactory);
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactory);
 };
 
 }  // namespace device
diff --git a/device/vr/android/gvr/gvr_device.cc b/device/vr/android/gvr/gvr_device.cc
index c47946f2..a8a3118d 100644
--- a/device/vr/android/gvr/gvr_device.cc
+++ b/device/vr/android/gvr/gvr_device.cc
@@ -135,29 +135,10 @@
 
 }  // namespace
 
-std::unique_ptr<GvrDevice> GvrDevice::Create() {
-  std::unique_ptr<GvrDevice> device = base::WrapUnique(new GvrDevice());
-  if (!device->gvr_api_)
-    return nullptr;
-  return device;
-}
-
 GvrDevice::GvrDevice()
     : VRDeviceBase(mojom::XRDeviceId::GVR_DEVICE_ID),
       exclusive_controller_binding_(this),
       weak_ptr_factory_(this) {
-  GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
-  if (!delegate_provider || delegate_provider->ShouldDisableGvrDevice())
-    return;
-  JNIEnv* env = base::android::AttachCurrentThread();
-  non_presenting_context_.Reset(
-      Java_NonPresentingGvrContext_create(env, reinterpret_cast<jlong>(this)));
-  if (!non_presenting_context_.obj())
-    return;
-  jlong context = Java_NonPresentingGvrContext_getNativeGvrContext(
-      env, non_presenting_context_);
-  gvr_api_ = gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context));
-  SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
   GvrDelegateProviderFactory::SetDevice(this);
 }
 
@@ -179,9 +160,18 @@
 void GvrDevice::RequestSession(
     mojom::XRRuntimeSessionOptionsPtr options,
     mojom::XRRuntime::RequestSessionCallback callback) {
+  if (!gvr_api_) {
+    EnsureGvrReady();
+    if (!gvr_api_) {
+      std::move(callback).Run(nullptr, nullptr);
+      return;
+    }
+  }
+
   if (!options->immersive) {
     // TODO(https://crbug.com/695937): This should be NOTREACHED() once we no
-    // longer need the hacked GRV non-immersive mode.
+    // longer need the hacked GRV non-immersive mode.  This should now only be
+    // hit if orientation devices are disabled by flag.
     ReturnNonImmersiveSession(std::move(callback));
     return;
   }
@@ -243,8 +233,36 @@
   exclusive_controller_binding_.Close();
 }
 
+void GvrDevice::EnsureGvrReady() {
+  if (!non_presenting_context_.obj() || !gvr_api_) {
+    GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
+    if (!delegate_provider || delegate_provider->ShouldDisableGvrDevice())
+      return;
+    JNIEnv* env = base::android::AttachCurrentThread();
+    non_presenting_context_.Reset(Java_NonPresentingGvrContext_create(
+        env, reinterpret_cast<jlong>(this)));
+    if (!non_presenting_context_.obj())
+      return;
+    jlong context = Java_NonPresentingGvrContext_getNativeGvrContext(
+        env, non_presenting_context_);
+    gvr_api_ =
+        gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context));
+    SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
+
+    if (paused_) {
+      PauseTracking();
+    } else {
+      ResumeTracking();
+    }
+  }
+}
+
 void GvrDevice::OnMagicWindowFrameDataRequest(
     mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
+  if (!gvr_api_) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
   mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
   frame_data->pose =
       GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), nullptr);
@@ -259,15 +277,26 @@
 }
 
 void GvrDevice::PauseTracking() {
-  gvr_api_->PauseTracking();
-  JNIEnv* env = base::android::AttachCurrentThread();
-  Java_NonPresentingGvrContext_pause(env, non_presenting_context_);
+  paused_ = true;
+  if (gvr_api_ && non_presenting_context_.obj()) {
+    gvr_api_->PauseTracking();
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_NonPresentingGvrContext_pause(env, non_presenting_context_);
+  }
 }
 
 void GvrDevice::ResumeTracking() {
-  gvr_api_->ResumeTracking();
-  JNIEnv* env = base::android::AttachCurrentThread();
-  Java_NonPresentingGvrContext_resume(env, non_presenting_context_);
+  paused_ = false;
+  if (gvr_api_ && non_presenting_context_.obj()) {
+    gvr_api_->ResumeTracking();
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_NonPresentingGvrContext_resume(env, non_presenting_context_);
+  }
+}
+
+void GvrDevice::EnsureInitialized(EnsureInitializedCallback callback) {
+  EnsureGvrReady();
+  std::move(callback).Run();
 }
 
 GvrDelegateProvider* GvrDevice::GetGvrDelegateProvider() {
@@ -281,6 +310,7 @@
 
 void GvrDevice::OnDisplayConfigurationChanged(JNIEnv* env,
                                               const JavaRef<jobject>& obj) {
+  DCHECK(gvr_api_);
   SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
 }
 
diff --git a/device/vr/android/gvr/gvr_device.h b/device/vr/android/gvr/gvr_device.h
index 1d802b2..93388c05 100644
--- a/device/vr/android/gvr/gvr_device.h
+++ b/device/vr/android/gvr/gvr_device.h
@@ -21,7 +21,7 @@
 class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase,
                                    public mojom::XRSessionController {
  public:
-  static std::unique_ptr<GvrDevice> Create();
+  GvrDevice();
   ~GvrDevice() override;
 
   // VRDeviceBase
@@ -30,6 +30,7 @@
       mojom::XRRuntime::RequestSessionCallback callback) override;
   void PauseTracking() override;
   void ResumeTracking() override;
+  void EnsureInitialized(EnsureInitializedCallback callback) override;
 
   void OnDisplayConfigurationChanged(
       JNIEnv* env,
@@ -52,13 +53,14 @@
 
   void OnPresentingControllerMojoConnectionError();
   void StopPresenting();
-
-  GvrDevice();
+  void EnsureGvrReady();
   GvrDelegateProvider* GetGvrDelegateProvider();
 
   base::android::ScopedJavaGlobalRef<jobject> non_presenting_context_;
   std::unique_ptr<gvr::GvrApi> gvr_api_;
 
+  bool paused_ = true;
+
   mojo::Binding<mojom::XRSessionController> exclusive_controller_binding_;
 
   base::WeakPtrFactory<GvrDevice> weak_ptr_factory_;
diff --git a/device/vr/android/gvr/gvr_device_provider.cc b/device/vr/android/gvr/gvr_device_provider.cc
index 56f8e79..67c7dcb 100644
--- a/device/vr/android/gvr/gvr_device_provider.cc
+++ b/device/vr/android/gvr/gvr_device_provider.cc
@@ -4,6 +4,7 @@
 
 #include "device/vr/android/gvr/gvr_device_provider.h"
 
+#include "base/android/build_info.h"
 #include "device/vr/android/gvr/gvr_device.h"
 
 namespace device {
@@ -17,7 +18,12 @@
                                  mojom::XRRuntimePtr)> add_device_callback,
     base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
     base::OnceClosure initialization_complete) {
-  vr_device_ = GvrDevice::Create();
+  // Version check should match MIN_SDK_VERSION in VrCoreVersionChecker.java.
+  // We only expose GvrDevice if we could potentially install VRServices to
+  // support presentation.
+  if (base::android::BuildInfo::GetInstance()->sdk_int() >=
+      base::android::SDK_VERSION_KITKAT)
+    vr_device_ = base::WrapUnique(new GvrDevice());
   if (vr_device_)
     add_device_callback.Run(vr_device_->GetId(), vr_device_->GetVRDisplayInfo(),
                             vr_device_->BindXRRuntimePtr());
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index 3fede7aa..ca921e0 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -72,8 +72,13 @@
 
   // The browser may register for changes to a device. Initial VRDisplayInfo
   // will immediately be returned to the listener to prevent races.
-  ListenToDeviceChanges(XRRuntimeEventListener listener) =>
-      (VRDisplayInfo display_info);
+  ListenToDeviceChanges(associated XRRuntimeEventListener listener) =>
+      (VRDisplayInfo? display_info);
+
+  // Ensure that the runtime has installed all prerequisites, and is ready to
+  // start. May result in updated display info being sent to registered
+  // listeners. RequestSession will fail if this hasn't been called.
+  EnsureInitialized() => ();
 
   SetListeningForActivate(bool listen_for_activation);
 };
diff --git a/device/vr/vr_device_base.cc b/device/vr/vr_device_base.cc
index af19fab1..61556842 100644
--- a/device/vr/vr_device_base.cc
+++ b/device/vr/vr_device_base.cc
@@ -23,7 +23,6 @@
 void VRDeviceBase::ResumeTracking() {}
 
 mojom::VRDisplayInfoPtr VRDeviceBase::GetVRDisplayInfo() {
-  DCHECK(display_info_);
   return display_info_.Clone();
 }
 
@@ -46,9 +45,9 @@
 }
 
 void VRDeviceBase::ListenToDeviceChanges(
-    mojom::XRRuntimeEventListenerPtr listener,
+    mojom::XRRuntimeEventListenerAssociatedPtrInfo listener_info,
     mojom::XRRuntime::ListenToDeviceChangesCallback callback) {
-  listener_ = std::move(listener);
+  listener_.Bind(std::move(listener_info));
   std::move(callback).Run(display_info_.Clone());
 }
 
@@ -65,13 +64,8 @@
 void VRDeviceBase::SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info) {
   DCHECK(display_info);
   DCHECK(display_info->id == id_);
-  bool initialized = !!display_info_;
   display_info_ = std::move(display_info);
 
-  // Don't notify when the VRDisplayInfo is initially set.
-  if (!initialized)
-    return;
-
   if (listener_)
     listener_->OnDisplayInfoChanged(display_info_.Clone());
 }
@@ -103,6 +97,10 @@
   OnListeningForActivate(is_listening);
 }
 
+void VRDeviceBase::EnsureInitialized(EnsureInitializedCallback callback) {
+  std::move(callback).Run();
+}
+
 void VRDeviceBase::RequestHitTest(
     mojom::XRRayPtr ray,
     mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) {
diff --git a/device/vr/vr_device_base.h b/device/vr/vr_device_base.h
index 8d10ef5..be6daa5 100644
--- a/device/vr/vr_device_base.h
+++ b/device/vr/vr_device_base.h
@@ -27,9 +27,10 @@
 
   // VRDevice Implementation
   void ListenToDeviceChanges(
-      mojom::XRRuntimeEventListenerPtr listener,
+      mojom::XRRuntimeEventListenerAssociatedPtrInfo listener,
       mojom::XRRuntime::ListenToDeviceChangesCallback callback) final;
   void SetListeningForActivate(bool is_listening) override;
+  void EnsureInitialized(EnsureInitializedCallback callback) override;
 
   void GetFrameData(mojom::XRFrameDataProvider::GetFrameDataCallback callback);
 
@@ -86,7 +87,7 @@
   virtual void OnMagicWindowFrameDataRequest(
       mojom::XRFrameDataProvider::GetFrameDataCallback callback);
 
-  mojom::XRRuntimeEventListenerPtr listener_;
+  mojom::XRRuntimeEventListenerAssociatedPtr listener_;
 
   bool presenting_ = false;
 
diff --git a/device/vr/vr_device_base_unittest.cc b/device/vr/vr_device_base_unittest.cc
index 318b292..952ae70b 100644
--- a/device/vr/vr_device_base_unittest.cc
+++ b/device/vr/vr_device_base_unittest.cc
@@ -12,6 +12,7 @@
 #include "device/vr/test/fake_vr_device.h"
 #include "device/vr/test/fake_vr_service_client.h"
 #include "device/vr/vr_device_base.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -72,14 +73,15 @@
   MOCK_METHOD0(OnBlur, void());
   MOCK_METHOD0(OnFocus, void());
   MOCK_METHOD1(OnDeviceIdle, void(mojom::VRDisplayEventReason));
+  MOCK_METHOD0(OnInitialized, void());
 
-  mojom::XRRuntimeEventListenerPtr BindPtr() {
-    mojom::XRRuntimeEventListenerPtr ret;
+  mojom::XRRuntimeEventListenerAssociatedPtrInfo BindPtrInfo() {
+    mojom::XRRuntimeEventListenerAssociatedPtrInfo ret;
     binding_.Bind(mojo::MakeRequest(&ret));
     return ret;
   }
 
-  mojo::Binding<mojom::XRRuntimeEventListener> binding_;
+  mojo::AssociatedBinding<mojom::XRRuntimeEventListener> binding_;
 };
 
 }  // namespace
@@ -122,10 +124,12 @@
 // will receive the "vrdevicechanged" event.
 TEST_F(VRDeviceTest, DeviceChangedDispatched) {
   auto device = MakeVRDevice();
+  auto device_ptr = device->BindXRRuntimePtr();
   StubVRDeviceEventListener listener;
-  device->ListenToDeviceChanges(
-      listener.BindPtr(),
+  device_ptr->ListenToDeviceChanges(
+      listener.BindPtrInfo(),
       base::DoNothing());  // TODO: consider getting initial info
+  base::RunLoop().RunUntilIdle();
   EXPECT_CALL(listener, DoOnChanged(testing::_)).Times(1);
   device->SetVRDisplayInfoForTest(MakeVRDisplayInfo(device->GetId()));
   base::RunLoop().RunUntilIdle();
@@ -135,10 +139,12 @@
   device::mojom::VRDisplayEventReason mounted =
       device::mojom::VRDisplayEventReason::MOUNTED;
   auto device = MakeVRDevice();
+  auto device_ptr = device->BindXRRuntimePtr();
   StubVRDeviceEventListener listener;
-  device->ListenToDeviceChanges(
-      listener.BindPtr(),
+  device_ptr->ListenToDeviceChanges(
+      listener.BindPtrInfo(),
       base::DoNothing());  // TODO: consider getting initial data
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(device->ListeningForActivate());
   device->SetListeningForActivate(true);
diff --git a/docs/README.md b/docs/README.md
index f9e5b0f..86db0ce7 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -107,6 +107,8 @@
     builder migration for Chromium
 *   [Tour of Continuous Integration UI](tour_of_luci_ui.md) - A tour of our
     the user interface for LUCI, our continuous integration system
+*   [Parsing Test Results](parsing_test_results.md) - An introduction for how to
+    understand the results emitted by polygerrit and CI builds.
 *   [Closure Compilation](closure_compilation.md) - The _Closure_ JavaScript
     compiler
 *   [Threading and Tasks in Chrome](threading_and_tasks.md) - How to run tasks
@@ -290,7 +292,7 @@
     Enabling spoken feedback (ChromeVox) on desktop Linux.
 *   [Offscreen, Invisible and Size](accessibility/offscreen.md) - How Chrome
     defines offscreen, invisible and size in the accessibility tree.
-*   [Text to Speech](accessibility/tts.md) - Overview of text to speech in 
+*   [Text to Speech](accessibility/tts.md) - Overview of text to speech in
     Chrome and Chrome OS.
 *   [BRLTTY in Chrome OS](accessibility/brltty.md) - Chrome OS integration with
     BRLTTY to support refreshable braille displays
diff --git a/docs/images/parsing_test_results_build_results_1.png b/docs/images/parsing_test_results_build_results_1.png
new file mode 100644
index 0000000..cfc7136
--- /dev/null
+++ b/docs/images/parsing_test_results_build_results_1.png
Binary files differ
diff --git a/docs/images/parsing_test_results_polygerrit.png b/docs/images/parsing_test_results_polygerrit.png
new file mode 100644
index 0000000..0a7e4b9
--- /dev/null
+++ b/docs/images/parsing_test_results_polygerrit.png
Binary files differ
diff --git a/docs/parsing_test_results.md b/docs/parsing_test_results.md
new file mode 100644
index 0000000..909877f
--- /dev/null
+++ b/docs/parsing_test_results.md
@@ -0,0 +1,89 @@
+# Parsing Test Results
+
+Chromium runs over 500,000 tests for each CL. There are many layers of UI for
+parsing and interpreting these test results. This doc provides a brief guide
+for navigating these UI layers.
+
+## Polygerrit UI
+
+Tests are segmented by build and test configurations. The segments are usually
+referred to as *builds*. In the example below, each green and red rectangle
+refers to a *build*.
+
+![Example polygerrit build status](images/parsing_test_results_polygerrit.png)
+
+The name of each build usually contains enough information to get a rough idea
+of the configuration. Some examples:
+
+* *android_compile_dbg* is a compile-only [no tests] build of Chromium for
+  Android, using the *debug* configuration.
+* *android-kitkat-arm-rel* builds and runs tests for Chromium for Android,
+  using the *release* configuration on a kitkat device.
+* *win7_chromium_rel_ng* builds and runs tests for Chromium on Windows, using
+  the release configuration on a Windows 7 device. *ng* stands for next
+  generation, but this has no meaning as the previous generation was already
+  phased out.
+
+Green boxes refer to builds that passed. Red boxes refer to builds that failed.
+Some failed builds get automatically retried by the CQ. In this example,
+*linux_chromium_rel_ng* and *mac_chromium_rel_ng* were automatically retried
+[hence the two **X**s], but *win7_chromium_rel_ng* was not. The **X** on the
+left is the first build, and the **X** on the right is the second build.
+
+Each of these boxes is a link that provides more information about the build
+failure.
+
+## Build Results UI
+
+Selecting any of the build results from the previous section will navigate to
+the build results UI. Each build is implemented by a [recipe] --
+effectively a Python script. Each recipe is divided into *steps*. Each *step*
+represents a well-defined action, such as updating the repository to point to
+tip of tree, or compiling the necessary build artifacts.
+
+[recipe]: https://chromium.googlesource.com/external/github.com/luci/recipes-py/+/master/doc/user_guide.md
+
+![Example 1 Build Results UI](images/parsing_test_results_build_results_1.png)
+
+Under the **Steps and Logfiles** heading is a list of numbered *steps*. Each
+*step* has a color (red, green or purple) which indicates whether the *step*
+failed, succeeded, or encountered an unexpected condition. Failing steps are
+also grouped into the **Results** section at the very top for convenience.
+
+## Build Results UI -- Overview
+
+Most builds follow a similar pattern. The key *steps* are listed here.
+
+* **bot_update** Update the repository to point to tip of tree. Apply the CL
+  as a patch.
+* **analyze** Analyze dependencies of test suites to determine which test
+  suites are affected by the patch.
+* **compile (with patch)** Builds test suites and associated artifacts.
+* **isolate tests** Archives test suite binaries and artifacts.
+* **test_pre_run.[trigger] webkit_layout_tests (with patch)** Triggers a test
+  suite on swarming [remote execution framework] -- in this case,
+  webkit_layout_tests.
+* **webkit_layout_tests (with patch)** Collects the results from swarming for a
+  test suite.
+
+If all test suites pass, then the *build* is marked as a success and no further
+steps are run. If at least one test suite has failures, then the failing tests
+are rerun with the patch deapplied. This allows the recipe to determine if the
+test failure is due to the CL or due to a problem with tip of tree.
+
+* **bot_update [without patch]** Deapplies the CL patch.
+* **compile [without patch]** Compiles test suites.
+* **isolate tests (2)** Archives test suite binaries and artifacts.
+* **test_pre_run.[trigger] webkit_layout_tests (without patch)** Triggers test
+  suite on swarming. Only failing tests are rerun.
+* **webkit_layout_tests (without patch)** Collects results from swarming.
+
+**Important safety notice**. When test suites are run with the patch applied,
+each test is run up to N times -- any success will mark the test as a success.
+When test suites are run without the patch, each failing test is run exactly N
+times. Any failure will mark the test as a failure.
+
+If there are tests that failed with the patch applied, but not with the patch
+deapplied, then that implies that it's likely that the CL broke a test. Just to
+confirm, the first suite of steps is run again, this time with the suffix
+**(retry with patch)**.
diff --git a/docs/static_initializers.md b/docs/static_initializers.md
index 7fcd3297..8226922e 100644
--- a/docs/static_initializers.md
+++ b/docs/static_initializers.md
@@ -9,7 +9,7 @@
 # How Static Initializers are Checked
 
 * For Linux and Mac:
-  * The expected count is stored in [//tools/perf_expectations/perf_expectations.json](https://cs.chromium.org/chromium/src/tools/perf_expectations/perf_expectations.json)
+  * The expected count is stored in [//infra/scripts/legacy/scripts/slave/chromium/sizes.py](https://cs.chromium.org/chromium/src/infra/scripts/legacy/scripts/slave/chromium/sizes.py)
 * For Android:
   * The expected count is stored in the build target [//chrome/android:monochrome_static_initializers](https://cs.chromium.org/chromium/src/chrome/android/BUILD.gn)
 
diff --git a/extensions/browser/api/storage/local_value_store_cache.cc b/extensions/browser/api/storage/local_value_store_cache.cc
index d0652c52..70aa853b 100644
--- a/extensions/browser/api/storage/local_value_store_cache.cc
+++ b/extensions/browser/api/storage/local_value_store_cache.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <limits>
+#include <utility>
 
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/api/storage/backend_task_runner.h"
@@ -34,8 +35,8 @@
 }  // namespace
 
 LocalValueStoreCache::LocalValueStoreCache(
-    const scoped_refptr<ValueStoreFactory>& factory)
-    : storage_factory_(factory), quota_(GetLocalQuotaLimits()) {
+    scoped_refptr<ValueStoreFactory> factory)
+    : storage_factory_(std::move(factory)), quota_(GetLocalQuotaLimits()) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 }
 
diff --git a/extensions/browser/api/storage/local_value_store_cache.h b/extensions/browser/api/storage/local_value_store_cache.h
index 56277f0..7507d987 100644
--- a/extensions/browser/api/storage/local_value_store_cache.h
+++ b/extensions/browser/api/storage/local_value_store_cache.h
@@ -21,8 +21,7 @@
 // another for extensions. Each backend takes care of persistence.
 class LocalValueStoreCache : public ValueStoreCache {
  public:
-  explicit LocalValueStoreCache(
-      const scoped_refptr<ValueStoreFactory>& factory);
+  explicit LocalValueStoreCache(scoped_refptr<ValueStoreFactory> factory);
   ~LocalValueStoreCache() override;
 
   // ValueStoreCache implementation:
diff --git a/extensions/browser/api/storage/storage_frontend.cc b/extensions/browser/api/storage/storage_frontend.cc
index 571472a..e550cd2e 100644
--- a/extensions/browser/api/storage/storage_frontend.cc
+++ b/extensions/browser/api/storage/storage_frontend.cc
@@ -78,23 +78,23 @@
 
 // static
 std::unique_ptr<StorageFrontend> StorageFrontend::CreateForTesting(
-    const scoped_refptr<ValueStoreFactory>& storage_factory,
+    scoped_refptr<ValueStoreFactory> storage_factory,
     BrowserContext* context) {
-  return base::WrapUnique(new StorageFrontend(storage_factory, context));
+  return base::WrapUnique(
+      new StorageFrontend(std::move(storage_factory), context));
 }
 
 StorageFrontend::StorageFrontend(BrowserContext* context)
     : StorageFrontend(ExtensionSystem::Get(context)->store_factory(), context) {
 }
 
-StorageFrontend::StorageFrontend(
-    const scoped_refptr<ValueStoreFactory>& factory,
-    BrowserContext* context)
+StorageFrontend::StorageFrontend(scoped_refptr<ValueStoreFactory> factory,
+                                 BrowserContext* context)
     : browser_context_(context) {
-  Init(factory);
+  Init(std::move(factory));
 }
 
-void StorageFrontend::Init(const scoped_refptr<ValueStoreFactory>& factory) {
+void StorageFrontend::Init(scoped_refptr<ValueStoreFactory> factory) {
   TRACE_EVENT0("browser,startup", "StorageFrontend::Init")
   SCOPED_UMA_HISTOGRAM_TIMER("Extensions.StorageFrontendInitTime");
 
diff --git a/extensions/browser/api/storage/storage_frontend.h b/extensions/browser/api/storage/storage_frontend.h
index c43b81e..71dd7c21 100644
--- a/extensions/browser/api/storage/storage_frontend.h
+++ b/extensions/browser/api/storage/storage_frontend.h
@@ -32,7 +32,7 @@
 
   // Creates with a specific |storage_factory|.
   static std::unique_ptr<StorageFrontend> CreateForTesting(
-      const scoped_refptr<ValueStoreFactory>& storage_factory,
+      scoped_refptr<ValueStoreFactory> storage_factory,
       content::BrowserContext* context);
 
   // Public so tests can create and delete their own instances.
@@ -75,10 +75,10 @@
   explicit StorageFrontend(content::BrowserContext* context);
 
   // Constructor for tests.
-  StorageFrontend(const scoped_refptr<ValueStoreFactory>& storage_factory,
+  StorageFrontend(scoped_refptr<ValueStoreFactory> storage_factory,
                   content::BrowserContext* context);
 
-  void Init(const scoped_refptr<ValueStoreFactory>& storage_factory);
+  void Init(scoped_refptr<ValueStoreFactory> storage_factory);
 
   // The (non-incognito) browser context this Frontend belongs to.
   content::BrowserContext* const browser_context_;
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 11d9211..49ae13d 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -245,10 +245,6 @@
       "permissions/manifest_permission.h",
       "permissions/manifest_permission_set.cc",
       "permissions/manifest_permission_set.h",
-      "permissions/media_galleries_permission.cc",
-      "permissions/media_galleries_permission.h",
-      "permissions/media_galleries_permission_data.cc",
-      "permissions/media_galleries_permission_data.h",
       "permissions/permission_message.cc",
       "permissions/permission_message.h",
       "permissions/permission_message_provider.cc",
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index c03cae1a..583023a 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -18,14 +18,13 @@
 #include "content/public/common/socket_permission_request.h"
 #include "extensions/common/api/messaging/message.h"
 #include "extensions/common/api/messaging/port_id.h"
-#include "extensions/common/constants.h"
 #include "extensions/common/common_param_traits.h"
+#include "extensions/common/constants.h"
 #include "extensions/common/draggable_region.h"
 #include "extensions/common/event_filtering_info.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/host_id.h"
-#include "extensions/common/permissions/media_galleries_permission_data.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "extensions/common/permissions/socket_permission_data.h"
 #include "extensions/common/permissions/usb_device_permission_data.h"
@@ -264,10 +263,6 @@
   IPC_STRUCT_TRAITS_MEMBER(interface_class())
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(extensions::MediaGalleriesPermissionData)
-  IPC_STRUCT_TRAITS_MEMBER(permission())
-IPC_STRUCT_TRAITS_END()
-
 IPC_STRUCT_TRAITS_BEGIN(extensions::Message)
   IPC_STRUCT_TRAITS_MEMBER(data)
   IPC_STRUCT_TRAITS_MEMBER(user_gesture)
diff --git a/gpu/command_buffer/service/client_service_map.h b/gpu/command_buffer/service/client_service_map.h
index af3098a..42974ae5 100644
--- a/gpu/command_buffer/service/client_service_map.h
+++ b/gpu/command_buffer/service/client_service_map.h
@@ -63,33 +63,35 @@
     client_to_service_map_.clear();
   }
 
-  bool GetServiceID(ClientType client_id, ServiceType* service_id) const {
-    if (client_id < kMaxFlatArraySize) {
-      if (client_id < client_to_service_array_.size() &&
-          client_to_service_array_[client_id] != invalid_service_id()) {
-        if (service_id) {
-          *service_id = client_to_service_array_[client_id];
-        }
-        return true;
-      }
-    } else {
-      auto iter = client_to_service_map_.find(client_id);
-      if (iter != client_to_service_map_.end()) {
-        if (service_id) {
-          *service_id = iter->second;
-        }
-        return true;
-      }
+  ALWAYS_INLINE bool GetServiceID(ClientType client_id,
+                                  ServiceType* service_id) const {
+    DCHECK(service_id);
+    if (client_id >= kMaxFlatArraySize) {
+      return GetServiceIDFromMap(client_id, service_id);
+    }
+
+    if (client_id < client_to_service_array_.size() &&
+        client_to_service_array_[client_id] != invalid_service_id()) {
+      *service_id = client_to_service_array_[client_id];
+      return true;
     }
     if (client_id == 0) {
-      if (service_id) {
-        *service_id = 0;
-      }
+      *service_id = 0;
       return true;
     }
     return false;
   }
 
+  ALWAYS_INLINE bool HasClientID(ClientType client_id) const {
+    if (client_id >= kMaxFlatArraySize) {
+      return GetServiceIDFromMap(client_id, nullptr);
+    }
+
+    return client_id == 0 ||
+           (client_id < client_to_service_array_.size() &&
+            client_to_service_array_[client_id] != invalid_service_id());
+  }
+
   ServiceType GetServiceIDOrInvalid(ClientType client_id) {
     ServiceType service_id;
     if (GetServiceID(client_id, &service_id)) {
@@ -118,10 +120,7 @@
       }
     }
     if (service_id == 0) {
-      if (client_id) {
-        *client_id = 0;
-      }
-      return true;
+      *client_id = 0;
     }
     return false;
   }
@@ -148,6 +147,19 @@
   static constexpr size_t kMaxFlatArraySize = 0x4000;
 
  private:
+  bool GetServiceIDFromMap(ClientType client_id,
+                           ServiceType* service_id) const {
+    DCHECK(client_id >= kMaxFlatArraySize);
+    auto iter = client_to_service_map_.find(client_id);
+    if (iter != client_to_service_map_.end()) {
+      if (service_id) {
+        *service_id = iter->second;
+      }
+      return true;
+    }
+    return false;
+  }
+
   ServiceType invalid_service_id_;
   std::vector<ServiceType> client_to_service_array_;
   std::unordered_map<ClientType, ServiceType> client_to_service_map_;
diff --git a/gpu/command_buffer/service/client_service_map_unittest.cc b/gpu/command_buffer/service/client_service_map_unittest.cc
index 6c11337..c16d42a 100644
--- a/gpu/command_buffer/service/client_service_map_unittest.cc
+++ b/gpu/command_buffer/service/client_service_map_unittest.cc
@@ -25,7 +25,7 @@
   EXPECT_EQ(kServiceId, service_id);
 
   // Check null is handled for GetServiceID
-  EXPECT_TRUE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_TRUE(map.HasClientID(kClientId));
 
   // Check service -> client ID lookup
   MapDataType client_id = 0;
@@ -90,19 +90,19 @@
   ClientServiceMapType map;
 
   map.SetIDMapping(kClientId, kServiceId);
-  EXPECT_TRUE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_TRUE(map.HasClientID(kClientId));
   map.RemoveClientID(kClientId);
-  EXPECT_FALSE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_FALSE(map.HasClientID(kClientId));
 
   map.SetIDMapping(kClientId, kServiceId);
-  EXPECT_TRUE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_TRUE(map.HasClientID(kClientId));
   map.Clear();
-  EXPECT_FALSE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_FALSE(map.HasClientID(kClientId));
 
   map.SetIDMapping(kClientId, kLargeServiceId);
-  EXPECT_TRUE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_TRUE(map.HasClientID(kClientId));
   map.RemoveClientID(kClientId);
-  EXPECT_FALSE(map.GetServiceID(kClientId, nullptr));
+  EXPECT_FALSE(map.HasClientID(kClientId));
 }
 
 TEST(ClientServiceMap, ManyIDs) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index 6e7ae295..e4b21a6 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -143,13 +143,12 @@
   bool have_context = !!api;
   // Only delete textures that are not referenced by a TexturePassthrough
   // object, they handle their own deletion once all references are lost
-  DeleteServiceObjects(
-      &texture_id_map, have_context,
-      [this, api](GLuint client_id, GLuint texture) {
-        if (!texture_object_map.GetServiceID(client_id, nullptr)) {
-          api->glDeleteTexturesFn(1, &texture);
-        }
-      });
+  DeleteServiceObjects(&texture_id_map, have_context,
+                       [this, api](GLuint client_id, GLuint texture) {
+                         if (!texture_object_map.HasClientID(client_id)) {
+                           api->glDeleteTexturesFn(1, &texture);
+                         }
+                       });
   DeleteServiceObjects(&buffer_id_map, have_context,
                        [api](GLuint client_id, GLuint buffer) {
                          api->glDeleteBuffersARBFn(1, &buffer);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 35d6cc9..9b09e7f 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -26,7 +26,7 @@
   DCHECK(n >= 0);
   std::vector<ClientType> client_ids_copy(client_ids, client_ids + n);
   for (GLsizei ii = 0; ii < n; ++ii) {
-    if (id_map->GetServiceID(client_ids_copy[ii], nullptr)) {
+    if (id_map->HasClientID(client_ids_copy[ii])) {
       return error::kInvalidArguments;
     }
   }
@@ -47,7 +47,7 @@
 error::Error CreateHelper(ClientType client_id,
                           ClientServiceMap<ClientType, ServiceType>* id_map,
                           GenFunction create_function) {
-  if (id_map->GetServiceID(client_id, nullptr)) {
+  if (id_map->HasClientID(client_id)) {
     return error::kInvalidArguments;
   }
   ServiceType service_id = create_function();
@@ -110,12 +110,19 @@
                            GLuint client_id,
                            PassthroughResources* resources,
                            bool create_if_missing) {
-  return GetServiceID(client_id, &resources->texture_id_map, create_if_missing,
-                      [api]() {
-                        GLuint service_id = 0;
-                        api->glGenTexturesFn(1, &service_id);
-                        return service_id;
-                      });
+  GLuint service_id = resources->texture_id_map.invalid_service_id();
+  if (resources->texture_id_map.GetServiceID(client_id, &service_id)) {
+    return service_id;
+  }
+
+  if (create_if_missing) {
+    GLuint service_id = 0;
+    api->glGenTexturesFn(1, &service_id);
+    resources->texture_id_map.SetIDMapping(client_id, service_id);
+    return service_id;
+  }
+
+  return resources->texture_id_map.invalid_service_id();
 }
 
 GLuint GetBufferServiceID(gl::GLApi* api,
@@ -1091,7 +1098,7 @@
 error::Error GLES2DecoderPassthroughImpl::DoFenceSync(GLenum condition,
                                                       GLbitfield flags,
                                                       GLuint client_id) {
-  if (resources_->sync_id_map.GetServiceID(client_id, nullptr)) {
+  if (resources_->sync_id_map.HasClientID(client_id)) {
     return error::kInvalidArguments;
   }
 
@@ -4038,7 +4045,7 @@
     GLuint texture_client_id,
     const volatile GLbyte* mailbox) {
   if (!texture_client_id ||
-      resources_->texture_id_map.GetServiceID(texture_client_id, nullptr)) {
+      resources_->texture_id_map.HasClientID(texture_client_id)) {
     return error::kInvalidArguments;
   }
 
diff --git a/gpu/ipc/service/command_buffer_stub.cc b/gpu/ipc/service/command_buffer_stub.cc
index f76e4a8..ed7e19a 100644
--- a/gpu/ipc/service/command_buffer_stub.cc
+++ b/gpu/ipc/service/command_buffer_stub.cc
@@ -707,8 +707,8 @@
 
 void CommandBufferStub::OnWaitSyncTokenCompleted(const SyncToken& sync_token) {
   DCHECK(waiting_for_sync_point_);
-  TRACE_EVENT_ASYNC_END1("gpu", "WaitSyncTokenCompleted", this,
-                         "CommandBufferStub", this);
+  TRACE_EVENT_ASYNC_END1("gpu", "WaitSyncToken", this, "CommandBufferStub",
+                         this);
   // Don't call PullTextureUpdates here because we can't MakeCurrent if we're
   // executing commands on another context. The WaitSyncToken command will run
   // again and call PullTextureUpdates once this command buffer gets scheduled.
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 635fb38..9d896f14 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -143,7 +143,7 @@
     }
     links {
       text: "perf.fyi"
-      url: "/p/chromium/g/chromium.perf.fyi"
+      url: "/p/chrome/g/chrome.perf.fyi/console"
       alt: "Chromium Perf FYI console"
     }
     links {
@@ -3697,78 +3697,6 @@
 
 consoles {
   header_id: "chromium"
-  id: "chromium.perf.fyi"
-  name: "chromium.perf.fyi"
-  repo_url: "https://chromium.googlesource.com/chromium/src"
-  refs: "refs/heads/master"
-  manifest_name: "REVISION"
-  builders {
-    name: "buildbot/chromium.perf.fyi/Android Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Android Builder Perf FYI"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Android arm64 Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Android arm64 Builder Perf FYI"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Android CFI Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Android CFI Builder Perf FYI"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Android CFI arm64 Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Android CFI arm64 Builder Perf FYI"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Linux Compile Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Linux Compile Perf FYI"
-    category: "linux"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Android Nexus 5X Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Android Nexus 5X Perf FYI"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/android-pixel2-perf"
-    name: "buildbucket/luci.chromium.ci/android-pixel2-perf"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/android-pixel2_webview-perf"
-    name: "buildbucket/luci.chromium.ci/android-pixel2_webview-perf"
-    category: "android"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/android-go_webview-perf"
-    name: "buildbucket/luci.chromium.ci/android-go_webview-perf"
-    category: "android"
-  }
-  builders {
-    name: "buildbucket/luci.chrome.ci/linux-perf-fyi"
-    category: "linux"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/One Buildbot Step Test Builder"
-    name: "buildbucket/luci.chromium.ci/One Buildbot Step Test Builder"
-    category: "linux"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Mac Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Mac Builder Perf FYI"
-    category: "mac"
-  }
-  builders {
-    name: "buildbot/chromium.perf.fyi/Win Builder Perf FYI"
-    name: "buildbucket/luci.chromium.ci/Win Builder Perf FYI"
-  }
-}
-
-consoles {
-  header_id: "chromium"
   id: "chromium.tools.build"
   name: "chromium.tools.build"
   repo_url: "https://chromium.googlesource.com/chromium/src"
diff --git a/infra/scripts/legacy/scripts/slave/chromium/sizes.py b/infra/scripts/legacy/scripts/slave/chromium/sizes.py
index c1479b21..41d0137c 100755
--- a/infra/scripts/legacy/scripts/slave/chromium/sizes.py
+++ b/infra/scripts/legacy/scripts/slave/chromium/sizes.py
@@ -29,10 +29,19 @@
 SRC_DIR = os.path.abspath(
     os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', '..'))
 
+EXPECTED_LINUX_SI_COUNTS = {
+  'chrome': 8,
+  'nacl_helper': 6,
+  'nacl_helper_bootstrap': 0,
+}
+
+EXPECTED_MAC_SI_COUNT = 1
+
 
 class ResultsCollector(object):
   def __init__(self):
     self.results = {}
+    self.failures = []
 
   def add_result(self, name, identifier, value, units):
     assert name not in self.results
@@ -45,6 +54,9 @@
     # Legacy printing, previously used for parsing the text logs.
     print 'RESULT %s: %s= %s %s' % (name, identifier, value, units)
 
+  def add_failure(self, failure):
+    self.failures.append(failure)
+
 
 def get_size(filename):
   return os.stat(filename)[stat.ST_SIZE]
@@ -164,7 +176,11 @@
 
       # For Release builds only, use dump-static-initializers.py to print the
       # list of static initializers.
-      if si_count > 0 and options.target == 'Release':
+      if si_count > EXPECTED_MAC_SI_COUNT and options.target == 'Release':
+        result = 125
+        results_collector.add_failure(
+            'Expected 0 static initializers in %s, but found %d' %
+            (chromium_framework_executable, si_count))
         print '\n# Static initializers in %s:' % chromium_framework_executable
 
         # First look for a dSYM to get information about the initializers. If
@@ -230,7 +246,7 @@
   return 66
 
 
-def check_linux_binary(target_dir, binary_name, options):
+def check_linux_binary(target_dir, binary_name, options, results_collector):
   """Collect appropriate size information about the built Linux binary given.
 
   Returns a tuple (result, sizes).  result is the first non-zero exit
@@ -302,16 +318,23 @@
 
   # For Release builds only, use dump-static-initializers.py to print the list
   # of static initializers.
-  if si_count > 0 and options.target == 'Release':
-    build_dir = os.path.dirname(target_dir)
-    dump_static_initializers = os.path.join(os.path.dirname(build_dir),
-                                            'tools', 'linux',
-                                            'dump-static-initializers.py')
-    result, stdout = run_process(result, [dump_static_initializers,
-                                          '-d', binary_file])
-    print '\n# Static initializers in %s:' % binary_file
-    print_si_fail_hint('tools/linux/dump-static-initializers.py')
-    print stdout
+  if options.target == 'Release':
+    if (binary_name in EXPECTED_LINUX_SI_COUNTS and
+        si_count > EXPECTED_LINUX_SI_COUNTS[binary_name]):
+      result = 125
+      results_collector.add_failure(
+          'Expected <= %d static initializers in %s, but found %d' %
+          (EXPECTED_LINUX_SI_COUNTS[binary_name], binary_name, si_count))
+    if si_count > 0:
+      build_dir = os.path.dirname(target_dir)
+      dump_static_initializers = os.path.join(os.path.dirname(build_dir),
+                                              'tools', 'linux',
+                                              'dump-static-initializers.py')
+      result, stdout = run_process(result, [dump_static_initializers,
+                                            '-d', binary_file])
+      print '\n# Static initializers in %s:' % binary_file
+      print_si_fail_hint('tools/linux/dump-static-initializers.py')
+      print stdout
 
   # Determine if the binary has the DT_TEXTREL marker.
   result, stdout = run_process(result, ['readelf', '-Wd', binary_file])
@@ -350,7 +373,8 @@
   totals = {}
 
   for binary in binaries:
-    this_result, this_sizes = check_linux_binary(target_dir, binary, options)
+    this_result, this_sizes = check_linux_binary(target_dir, binary, options,
+                                                 results_collector)
     if result == 0:
       result = this_result
     for name, identifier, totals_id, value, units in this_sizes:
@@ -399,7 +423,8 @@
     binaries_to_print = binaries
 
   for (binary, binary_to_print) in zip(binaries, binaries_to_print):
-    this_result, this_sizes = check_linux_binary(target_dir, binary, options)
+    this_result, this_sizes = check_linux_binary(target_dir, binary, options,
+                                                 results_collector)
     if result == 0:
       result = this_result
     for name, identifier, _, value, units in this_sizes:
@@ -534,6 +559,8 @@
                            help='specify platform (%s) [default: %%default]'
                                 % ', '.join(platforms))
   option_parser.add_option('--json', help='Path to JSON output file')
+  option_parser.add_option('--failures',
+                           help='Path to JSON output file for failures')
 
   options, args = option_parser.parse_args()
 
@@ -554,6 +581,10 @@
     with open(options.json, 'w') as f:
       json.dump(results_collector.results, f)
 
+  if options.failures:
+    with open(options.failures, 'w') as f:
+      json.dump(results_collector.failures, f)
+
   return rc
 
 
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index da03793..6d58a2b 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -355,6 +355,11 @@
     {"toolbar-container", flag_descriptions::kToolbarContainerName,
      flag_descriptions::kToolbarContainerDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(toolbar_container::kToolbarContainerEnabled)},
+    {"toolbar-container-custom-view",
+     flag_descriptions::kToolbarContainerCustomViewName,
+     flag_descriptions::kToolbarContainerCustomViewDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(toolbar_container::kToolbarContainerCustomViewEnabled)},
     {"omnibox-popup-shortcuts",
      flag_descriptions::kOmniboxPopupShortcutIconsInZeroStateName,
      flag_descriptions::kOmniboxPopupShortcutIconsInZeroStateDescription,
@@ -372,6 +377,10 @@
     {"use-multilogin-endpoint", flag_descriptions::kUseMultiloginEndpointName,
      flag_descriptions::kUseMultiloginEndpointDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kUseMultiloginEndpoint)},
+    {"closing-last-incognito-tab",
+     flag_descriptions::kClosingLastIncognitoTabName,
+     flag_descriptions::kClosingLastIncognitoTabDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kClosingLastIncognitoTab)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.cc b/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.cc
index ba1bfc38..37daccf 100644
--- a/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.cc
+++ b/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.cc
@@ -4,6 +4,7 @@
 
 #include "ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.h"
 
+#include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/history/core/browser/history_service.h"
@@ -104,6 +105,11 @@
   return nullptr;
 }
 
+OmniboxPedalProvider* AutocompleteProviderClientImpl::GetPedalProvider() const {
+  NOTREACHED();
+  return nullptr;
+}
+
 scoped_refptr<ShortcutsBackend>
 AutocompleteProviderClientImpl::GetShortcutsBackend() {
   return ios::ShortcutsBackendFactory::GetForBrowserState(browser_state_);
diff --git a/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.h b/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.h
index 56a4e005..9451c99 100644
--- a/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.h
+++ b/ios/chrome/browser/autocomplete/autocomplete_provider_client_impl.h
@@ -41,6 +41,7 @@
       bool create_if_necessary) const override;
   DocumentSuggestionsService* GetDocumentSuggestionsService(
       bool create_if_necessary) const override;
+  OmniboxPedalProvider* GetPedalProvider() const override;
   scoped_refptr<ShortcutsBackend> GetShortcutsBackend() override;
   scoped_refptr<ShortcutsBackend> GetShortcutsBackendIfExists() override;
   std::unique_ptr<KeywordExtensionsDelegate> GetKeywordExtensionsDelegate(
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index ab7e8d8..e14ac76 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -139,6 +139,12 @@
     "When enabled, some network issues will trigger a test to check if a "
     "Captive Portal network is the cause of the issue.";
 
+// TODO(crbug.com/893314) : Remove this flag.
+const char kClosingLastIncognitoTabName[] = "Closing Last Incognito Tab";
+const char kClosingLastIncognitoTabDescription[] =
+    "Automatically switches to the regular tabs panel in the tab grid after "
+    "closing the last incognito tab";
+
 const char kContextualSearch[] = "Contextual Search";
 const char kContextualSearchDescription[] =
     "Whether or not Contextual Search is enabled.";
@@ -251,6 +257,11 @@
     "When enabled, the toolbars and their fullscreen animations will be "
     "managed by the toolbar container coordinator rather than BVC.";
 
+const char kToolbarContainerCustomViewName[] =
+    "Use custom toolbar container view.";
+const char kToolbarContainerCustomViewDescription[] =
+    "Use the custom toolbar container view fix for crbug.com/889884.";
+
 const char kUnifiedConsentName[] = "Unified Consent";
 const char kUnifiedConsentDescription[] =
     "Enables a unified management of user consent for privacy-related "
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index fa74a6e..88b3729e 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -110,6 +110,11 @@
 extern const char kCaptivePortalMetricsName[];
 extern const char kCaptivePortalMetricsDescription[];
 
+// Title and description for the flag to enable automatically switching to the
+// regular tabs after closing the last incognito tab.
+extern const char kClosingLastIncognitoTabName[];
+extern const char kClosingLastIncognitoTabDescription[];
+
 // Title and description for the flag to enable Contextual Search.
 extern const char kContextualSearch[];
 extern const char kContextualSearchDescription[];
@@ -207,6 +212,11 @@
 extern const char kToolbarContainerName[];
 extern const char kToolbarContainerDescription[];
 
+// Title and description for the flag to enable the custom toolbar container
+// view fix for crbug.com/889884.
+extern const char kToolbarContainerCustomViewName[];
+extern const char kToolbarContainerCustomViewDescription[];
+
 // Title and description for the flag to enable the unified consent.
 extern const char kUnifiedConsentName[];
 extern const char kUnifiedConsentDescription[];
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index e77d34b0..862b422 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -206,6 +206,7 @@
 #import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h"
 #import "ios/chrome/browser/ui/toolbar/secondary_toolbar_coordinator.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_coordinator_adaptor.h"
+#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h"
 #import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #include "ios/chrome/browser/ui/ui_util.h"
@@ -2465,7 +2466,10 @@
     if (self.secondaryToolbarCoordinator) {
       // Create the container view for the secondary toolbar and add it to the
       // hierarchy
-      UIView* container = [[ToolbarContainerView alloc] init];
+      bool useCustomView = base::FeatureList::IsEnabled(
+          toolbar_container::kToolbarContainerCustomViewEnabled);
+      UIView* container = useCustomView ? [[ToolbarContainerView alloc] init]
+                                        : [[UIView alloc] init];
       container.translatesAutoresizingMaskIntoConstraints = NO;
       [container
           addSubview:self.secondaryToolbarCoordinator.viewController.view];
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_util.mm b/ios/chrome/browser/ui/omnibox/omnibox_util.mm
index f6774545..acc3d77c 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_util.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_util.mm
@@ -54,6 +54,7 @@
     case AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
     case AutocompleteMatchType::URL_WHAT_YOU_TYPED:
     case AutocompleteMatchType::DOCUMENT_SUGGESTION:
+    case AutocompleteMatchType::PEDAL:
       return DEFAULT_FAVICON;
     case AutocompleteMatchType::HISTORY_BODY:
     case AutocompleteMatchType::HISTORY_KEYWORD:
@@ -184,7 +185,8 @@
       DCHECK(!is_incognito);
       return IDR_IOS_OMNIBOX_CALCULATOR;
     case AutocompleteMatchType::DOCUMENT_SUGGESTION:
-      // Document suggeestions aren't yet supported on mobile.
+    case AutocompleteMatchType::PEDAL:
+      // Document and Pedal suggestions aren't yet supported on mobile.
       NOTREACHED();
       return IDR_IOS_OMNIBOX_HTTP;
     case AutocompleteMatchType::EXTENSION_APP_DEPRECATED:
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
index 24d6b7f..7698ae65 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
@@ -48,4 +48,8 @@
 extern const CGFloat kTabGridTopToolbarHeight;
 extern const CGFloat kTabGridBottomToolbarHeight;
 
+// The delay (in milliseconds) after closing the last incognito tab and before
+// automatically scrolling to the regular tabs panel.
+extern const int64_t kTabGridScrollAnimationDelayInMilliseconds;
+
 #endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
index 6059f423..ed16470 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
@@ -51,3 +51,7 @@
 // Intrinsic heights of the tab grid toolbars.
 const CGFloat kTabGridTopToolbarHeight = 52.0f;
 const CGFloat kTabGridBottomToolbarHeight = 44.0f;
+
+// The delay (in milliseconds) after closing the last incognito tab and before
+// automatically scrolling to the regular tabs panel.
+const int64_t kTabGridScrollAnimationDelayInMilliseconds = 300;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index 7d6c701..50472e3 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -4,10 +4,12 @@
 
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
 
+#include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/task/post_task.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h"
 #import "ios/chrome/browser/ui/rtl_geometry.h"
@@ -24,9 +26,12 @@
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.h"
 #import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
+#include "ios/chrome/browser/ui/ui_util.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
+#include "ios/web/public/web_task_traits.h"
+#include "ios/web/public/web_thread.h"
 #include "ui/base/l10n/l10n_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -1173,6 +1178,16 @@
   return 12;
 }
 
+// Sets both the current page and page control's selected page to |page|.
+// Animation is used if |animated| is YES.
+- (void)setCurrentPageAndPageControlSelectedPage:(TabGridPage)page
+                                        animated:(BOOL)animated {
+  if (self.topToolbar.pageControl.selectedPage != page)
+    [self.topToolbar.pageControl setSelectedPage:page animated:animated];
+  if (self.currentPage != page)
+    [self setCurrentPage:page animated:animated];
+}
+
 #pragma mark - GridViewControllerDelegate
 
 - (void)gridViewController:(GridViewController*)gridViewController
@@ -1232,11 +1247,31 @@
   [self configureButtonsForActiveAndCurrentPage];
   if (gridViewController == self.regularTabsViewController) {
     self.topToolbar.pageControl.regularTabCount = count;
-  } else if (gridViewController == self.incognitoTabsViewController) {
-    if (count == 0) {
-      [self.topToolbar.pageControl setSelectedPage:TabGridPageRegularTabs
-                                          animated:YES];
-      [self setCurrentPage:TabGridPageRegularTabs animated:YES];
+  } else if (IsClosingLastIncognitoTabEnabled() &&
+             gridViewController == self.incognitoTabsViewController) {
+    // No assumption is made as to the state of the UI. This method can be
+    // called with an incognito view controller and a current page that is not
+    // the incognito tabs.
+    if (count == 0 && self.currentPage == TabGridPageIncognitoTabs) {
+      // Show the regular tabs to the user if the last incognito tab is closed.
+      if (self.viewLoaded && self.view.window) {
+        // Visibly scroll to the regular tabs panel after a slight delay when
+        // the user is already in the tab switcher.
+        __weak TabGridViewController* weakSelf = self;
+        base::PostDelayedTaskWithTraits(
+            FROM_HERE, {web::WebThread::UI}, base::BindOnce(^{
+              [weakSelf setCurrentPageAndPageControlSelectedPage:
+                            TabGridPageRegularTabs
+                                                        animated:YES];
+            }),
+            base::TimeDelta::FromMilliseconds(
+                kTabGridScrollAnimationDelayInMilliseconds));
+      } else {
+        // Directly show the regular tabs in tab switcher without animation if
+        // the user was not already in tab switcher.
+        [self setCurrentPageAndPageControlSelectedPage:TabGridPageRegularTabs
+                                              animated:NO];
+      }
     }
   }
 
diff --git a/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h b/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h
index ad801815..375ef80 100644
--- a/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h
+++ b/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.h
@@ -9,6 +9,12 @@
 
 namespace toolbar_container {
 
+// Used to enable the fix for crbug.com/889884.  This is a temporary flag that
+// is only going to be used as an emergency shutoff for the bug fix because it
+// was landed late in the branch.
+// TODO(crbug.com/880672): Remove this flag.
+extern const base::Feature kToolbarContainerCustomViewEnabled;
+
 // Used to move toolbar layout management to a container view.
 extern const base::Feature kToolbarContainerEnabled;
 
diff --git a/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.mm b/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.mm
index c591526..2685a41 100644
--- a/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.mm
+++ b/ios/chrome/browser/ui/toolbar_container/toolbar_container_features.mm
@@ -10,6 +10,9 @@
 
 namespace toolbar_container {
 
+const base::Feature kToolbarContainerCustomViewEnabled{
+    "ToolbarContainerCustomViewEnabled", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kToolbarContainerEnabled{"ToolbarContainerEnabled",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index bb1defa..2140fbf 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -7,6 +7,10 @@
 const base::Feature kFirstResponderKeyWindow{"FirstResponderKeyWindow",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
+// TODO(crbug.com/893314) : Remove this flag.
+const base::Feature kClosingLastIncognitoTab{"ClosingLastIncognitoTab",
+                                             base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kCopyImage{"CopyImage", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kOmniboxPopupShortcutIconsInZeroState{
diff --git a/ios/chrome/browser/ui/ui_feature_flags.h b/ios/chrome/browser/ui/ui_feature_flags.h
index abdb4c7..85ea4b12 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.h
+++ b/ios/chrome/browser/ui/ui_feature_flags.h
@@ -12,6 +12,10 @@
 // responder.
 extern const base::Feature kFirstResponderKeyWindow;
 
+// Feature to automatically switch to the regular tabs panel in tab grid after
+// closing the last incognito tab.
+extern const base::Feature kClosingLastIncognitoTab;
+
 // Feature to copy image to system pasteboard via context menu.
 extern const base::Feature kCopyImage;
 
diff --git a/ios/chrome/browser/ui/ui_util.h b/ios/chrome/browser/ui/ui_util.h
index c54a28e..ba69dcc 100644
--- a/ios/chrome/browser/ui/ui_util.h
+++ b/ios/chrome/browser/ui/ui_util.h
@@ -39,6 +39,10 @@
 // Returns true if the device is an iPhone X.
 bool IsIPhoneX();
 
+// Returns whether the flag is enabled for switching to the regular tabs panel
+// in tab switcher when the last incognito tab is closed.
+bool IsClosingLastIncognitoTabEnabled();
+
 // Returns whether the UI Refresh Location Bar will be used.
 // TODO (crbug.com/884723): Remove all use of this flag.
 bool IsRefreshLocationBarEnabled();
diff --git a/ios/chrome/browser/ui/ui_util.mm b/ios/chrome/browser/ui/ui_util.mm
index f20294c5..577440a 100644
--- a/ios/chrome/browser/ui/ui_util.mm
+++ b/ios/chrome/browser/ui/ui_util.mm
@@ -60,6 +60,11 @@
           (height == 2436 || height == 2688 || height == 1792));
 }
 
+// TODO(crbug.com/893314) : Remove this flag.
+bool IsClosingLastIncognitoTabEnabled() {
+  return base::FeatureList::IsEnabled(kClosingLastIncognitoTab);
+}
+
 bool IsRefreshLocationBarEnabled() {
   return true;
 }
diff --git a/ios/chrome/browser/web/window_open_by_dom_egtest.mm b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
index 2aeaa03..8ac3b50 100644
--- a/ios/chrome/browser/web/window_open_by_dom_egtest.mm
+++ b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
@@ -267,20 +267,6 @@
       assertWithMatcher:grey_notNil()];
 }
 
-// Tests opening a child window using the following link
-// <a href="data:text/html,<script>window.location='about:newtab';</script>"
-//    target="_blank">
-- (void)testWindowOpenWithAboutNewTabScript {
-  const char ID[] = "webScenarioWindowOpenWithAboutNewTabScript";
-  [[EarlGrey selectElementWithMatcher:WebViewInWebState(GetCurrentWebState())]
-      performAction:web::WebViewTapElement(
-                        GetCurrentWebState(),
-                        ElementSelector::ElementSelectorId(ID))];
-  [ChromeEarlGrey waitForMainTabCount:2];
-  [[EarlGrey selectElementWithMatcher:OmniboxText("about:newtab")]
-      assertWithMatcher:grey_notNil()];
-}
-
 // Tests that closing the current window using DOM fails.
 - (void)testCloseWindowNotOpenByDOM {
   GREYAssert(TapWebViewElementWithId("webScenarioWindowClose"),
diff --git a/ios/testing/data/http_server_files/window_open.html b/ios/testing/data/http_server_files/window_open.html
index 36b5e167..2ffbcaf2 100644
--- a/ios/testing/data/http_server_files/window_open.html
+++ b/ios/testing/data/http_server_files/window_open.html
@@ -139,18 +139,6 @@
     <td>about:blank opened in a new window, with an href and a preventDefault<br></td>
   </tr>
 
-  <tr id="_webScenarioWindowOpenWithAboutNewTabScript">
-    <td>
-      <a href="data:text/html,<script>window.location='about:newtab';</script>"
-         target="_blank"
-         name="webScenarioWindowOpenWithAboutNewTabScript"
-         id="webScenarioWindowOpenWithAboutNewTabScript">
-        webScenarioWindowOpenWithAboutNewTabScript
-      </a>
-    </td>
-    <td>about:blank opened in a new window, using "window.location='about:newtab" script<br></td>
-  </tr>
-
   <tr id="_webScenarioWindowOpenAndSetLocation">
     <td>
       <script>
diff --git a/ios/web/download/download_task_impl.mm b/ios/web/download/download_task_impl.mm
index 86bd8d0f..55dc87a1 100644
--- a/ios/web/download/download_task_impl.mm
+++ b/ios/web/download/download_task_impl.mm
@@ -41,10 +41,10 @@
 
 // Translates an CFNetwork error code to a net error code. Returns 0 if |error|
 // is nil.
-int GetNetErrorCodeFromNSError(NSError* error) {
+int GetNetErrorCodeFromNSError(NSError* error, NSURL* url) {
   int error_code = 0;
   if (error) {
-    if (!web::GetNetErrorFromIOSErrorCode(error.code, &error_code)) {
+    if (!web::GetNetErrorFromIOSErrorCode(error.code, &error_code, url)) {
       error_code = net::ERR_FAILED;
     }
   }
@@ -337,7 +337,8 @@
           return;
         }
 
-        error_code_ = GetNetErrorCodeFromNSError(error);
+        error_code_ =
+            GetNetErrorCodeFromNSError(error, task.currentRequest.URL);
         percent_complete_ = GetTaskPercentComplete(task);
         received_bytes_ = task.countOfBytesReceived;
         if (total_bytes_ == -1 || task.countOfBytesExpectedToReceive) {
diff --git a/ios/web/web_state/BUILD.gn b/ios/web/web_state/BUILD.gn
index 3bd3c67..ccf460c 100644
--- a/ios/web/web_state/BUILD.gn
+++ b/ios/web/web_state/BUILD.gn
@@ -103,6 +103,7 @@
   deps = [
     "//base",
     "//ios/net",
+    "//ios/web/public",
     "//net",
   ]
 
diff --git a/ios/web/web_state/error_translation_util.h b/ios/web/web_state/error_translation_util.h
index b71fbc42..6449a085 100644
--- a/ios/web/web_state/error_translation_util.h
+++ b/ios/web/web_state/error_translation_util.h
@@ -10,8 +10,11 @@
 namespace web {
 
 // Translates an CFNetwork error code to a net error code using |net_error_code|
-// as an out-parameter.  Returns true if a valid translation was found.
-bool GetNetErrorFromIOSErrorCode(NSInteger ios_error_code, int* net_error_code);
+// as an out-parameter.  |url| is URL which failed to load. Returns true if a
+// valid translation was found.
+bool GetNetErrorFromIOSErrorCode(NSInteger ios_error_code,
+                                 int* net_error_code,
+                                 NSURL* url);
 
 // Translates an iOS-specific error into its net error equivalent and returns a
 // copy of |error| with the translation as its final underlying error.  The
diff --git a/ios/web/web_state/error_translation_util.mm b/ios/web/web_state/error_translation_util.mm
index 2559072..de2f3084 100644
--- a/ios/web/web_state/error_translation_util.mm
+++ b/ios/web/web_state/error_translation_util.mm
@@ -5,10 +5,14 @@
 #import "ios/web/web_state/error_translation_util.h"
 
 #include <CFNetwork/CFNetwork.h>
+#include <Foundation/Foundation.h>
 
 #import "base/ios/ns_error_util.h"
 #import "ios/net/protocol_handler_util.h"
+#import "ios/web/public/web_client.h"
+#import "net/base/mac/url_conversions.h"
 #include "net/base/net_errors.h"
+#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -17,7 +21,8 @@
 namespace web {
 
 bool GetNetErrorFromIOSErrorCode(NSInteger ios_error_code,
-                                 int* net_error_code) {
+                                 int* net_error_code,
+                                 NSURL* url) {
   DCHECK(net_error_code);
   bool translation_success = true;
   switch (ios_error_code) {
@@ -40,7 +45,13 @@
       *net_error_code = net::ERR_CONNECTION_TIMED_OUT;
       break;
     case kCFURLErrorUnsupportedURL:
-      *net_error_code = net::ERR_UNKNOWN_URL_SCHEME;
+      if (GetWebClient()->IsAppSpecificURL(net::GURLWithNSURL(url))) {
+        // Scheme is valid, but URL is not supported.
+        *net_error_code = net::ERR_INVALID_URL;
+      } else {
+        // Scheme is not app-specific and not supported by WebState.
+        *net_error_code = net::ERR_UNKNOWN_URL_SCHEME;
+      }
       break;
     case kCFURLErrorCannotFindHost:
       *net_error_code = net::ERR_NAME_NOT_RESOLVED;
@@ -154,7 +165,9 @@
           isEqualToString:static_cast<NSString*>(kCFErrorDomainCFNetwork)]) {
     // Attempt to translate NSURL and CFNetwork error codes into their
     // corresponding net error codes.
-    GetNetErrorFromIOSErrorCode(underlying_error.code, &net_error_code);
+    NSString* url_spec = error.userInfo[NSURLErrorFailingURLStringErrorKey];
+    NSURL* url = url_spec ? [NSURL URLWithString:url_spec] : nil;
+    GetNetErrorFromIOSErrorCode(underlying_error.code, &net_error_code, url);
   }
   return NetErrorFromError(error, net_error_code);
 }
diff --git a/ios/web/web_state/error_translation_util_unittest.mm b/ios/web/web_state/error_translation_util_unittest.mm
index 45167fa4..ee150b3 100644
--- a/ios/web/web_state/error_translation_util_unittest.mm
+++ b/ios/web/web_state/error_translation_util_unittest.mm
@@ -8,10 +8,14 @@
 
 #include "base/mac/foundation_util.h"
 #import "ios/net/protocol_handler_util.h"
+#include "ios/web/test/test_url_constants.h"
+#import "net/base/mac/url_conversions.h"
 #include "net/base/net_errors.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
+#include "url/gurl.h"
+#include "url/scheme_host_port.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -24,12 +28,29 @@
 
 // Tests translation of CFNetwork error code to net error code.
 TEST_F(ErrorTranslationUtilTest, ErrorCodeTranslation) {
+  // kCFURLErrorUnknown -> net::ERR_FAILED
   int net_error_code = 0;
-  EXPECT_TRUE(GetNetErrorFromIOSErrorCode(kCFURLErrorUnknown, &net_error_code));
+  EXPECT_TRUE(GetNetErrorFromIOSErrorCode(kCFURLErrorUnknown, &net_error_code,
+                                          /*url=*/nil));
   EXPECT_EQ(net::ERR_FAILED, net_error_code);
 
+  // kCFURLErrorUnsupportedURL -> net::ERR_INVALID_URL for app specific URLs.
+  GURL web_ui_url(url::SchemeHostPort(kTestWebUIScheme, "foo", 0).Serialize());
+  EXPECT_TRUE(GetNetErrorFromIOSErrorCode(kCFURLErrorUnsupportedURL,
+                                          &net_error_code,
+                                          net::NSURLWithGURL(web_ui_url)));
+  EXPECT_EQ(net::ERR_INVALID_URL, net_error_code);
+
+  // kCFURLErrorUnsupportedURL -> net::ERR_UNKNOWN_URL_SCHEME for app with
+  // scheme that is neither supported by WebState nor app-specific scheme.
+  NSURL* unsupported_url = [NSURL URLWithString:@"fooooo:baaar"];
+  EXPECT_TRUE(GetNetErrorFromIOSErrorCode(kCFURLErrorUnsupportedURL,
+                                          &net_error_code, unsupported_url));
+  EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, net_error_code);
+
+  // kCFSOCKSErrorUnknownClientVersion -> ?
   EXPECT_FALSE(GetNetErrorFromIOSErrorCode(kCFSOCKSErrorUnknownClientVersion,
-                                           &net_error_code));
+                                           &net_error_code, /*url=*/nil));
 }
 
 // Tests translation of an error with empty domain and no underlying error.
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 859ff648..be5d2a0 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -4476,6 +4476,15 @@
   if (allowNavigation) {
     allowNavigation = self.webStateImpl->ShouldAllowResponse(
         navigationResponse.response, navigationResponse.forMainFrame);
+    if (allowNavigation && responseURL.SchemeIs(url::kDataScheme) &&
+        navigationResponse.forMainFrame) {
+      // Block rendering data URLs for renderer-initiated navigations in main
+      // frame to prevent abusive behavior (crbug.com/890558). Data URLs
+      // downloads are still allowed.
+      web::NavigationContextImpl* context =
+          [self contextForPendingMainFrameNavigationWithURL:responseURL];
+      allowNavigation = !context->IsRendererInitiated();
+    }
     if (!allowNavigation && navigationResponse.isForMainFrame) {
       [_pendingNavigationInfo setCancelled:YES];
     }
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index a28a4cc..7491c4a 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -79,6 +79,8 @@
 // Syntactically invalid URL per rfc3986.
 const char kInvalidURL[] = "http://%3";
 
+const char kTestDataURL[] = "data:text/html,";
+
 const char kTestURLString[] = "http://www.google.com/";
 const char kTestAppSpecificURL[] = "testwebui://test/";
 
@@ -658,22 +660,25 @@
 
 INSTANTIATE_TEST_CASES(CRWWebControllerJSExecutionTest);
 
-// Test fixture to test that DownloadControllerDelegate::OnDownloadCreated
-// callback is trigerred if CRWWebController can not display the response.
-class CRWWebControllerDownloadTest : public CRWWebControllerTest {
+// Test fixture to test decidePolicyForNavigationResponse:decisionHandler:
+// delegate method.
+class CRWWebControllerResponseTest : public CRWWebControllerTest {
  protected:
-  CRWWebControllerDownloadTest() : delegate_(download_controller()) {}
+  CRWWebControllerResponseTest() : download_delegate_(download_controller()) {}
 
   // Calls webView:decidePolicyForNavigationResponse:decisionHandler: callback
   // and waits for decision handler call. Returns false if decision handler call
   // times out.
   bool CallDecidePolicyForNavigationResponseWithResponse(
       NSURLResponse* response,
-      BOOL for_main_frame) WARN_UNUSED_RESULT {
+      BOOL for_main_frame,
+      BOOL can_show_mime_type,
+      WKNavigationResponsePolicy* out_policy) WARN_UNUSED_RESULT {
     CRWFakeWKNavigationResponse* navigation_response =
         [[CRWFakeWKNavigationResponse alloc] init];
     navigation_response.response = response;
     navigation_response.forMainFrame = for_main_frame;
+    navigation_response.canShowMIMEType = can_show_mime_type;
 
     // Call decidePolicyForNavigationResponse and wait for decisionHandler's
     // callback.
@@ -686,6 +691,7 @@
     [navigation_delegate_ webView:mock_web_view_
         decidePolicyForNavigationResponse:navigation_response
                           decisionHandler:^(WKNavigationResponsePolicy policy) {
+                            *out_policy = policy;
                             callback_called = true;
                           }];
     return WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
@@ -697,12 +703,93 @@
     return DownloadController::FromBrowserState(GetBrowserState());
   }
 
-  FakeDownloadControllerDelegate delegate_;
+  FakeDownloadControllerDelegate download_delegate_;
 };
 
+// Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows
+// renderer-initiated navigations in main frame for http: URLs.
+TEST_P(CRWWebControllerResponseTest, AllowRendererInitiatedResponse) {
+  // Simulate regular navigation response with text/html MIME type.
+  NSURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@(kTestURLString)]
+        statusCode:200
+       HTTPVersion:nil
+      headerFields:nil];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel;
+  ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
+      response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyAllow, policy);
+
+  // Verify that download task was not created for html response.
+  ASSERT_TRUE(download_delegate_.alive_download_tasks().empty());
+}
+
+// Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows
+// renderer-initiated navigations in iframe for data: URLs.
+TEST_P(CRWWebControllerResponseTest,
+       AllowRendererInitiatedDataUrlResponseInIFrame) {
+  // Simulate data:// url response with text/html MIME type.
+  SetWebViewURL(@(kTestDataURL));
+  NSURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@(kTestDataURL)]
+        statusCode:200
+       HTTPVersion:nil
+      headerFields:nil];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel;
+  ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
+      response, /*for_main_frame=*/NO, /*can_show_mime_type=*/YES, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyAllow, policy);
+
+  // Verify that download task was not created for html response.
+  ASSERT_TRUE(download_delegate_.alive_download_tasks().empty());
+}
+
+// Tests that webView:decidePolicyForNavigationResponse:decisionHandler: blocks
+// rendering data URLs for renderer-initiated navigations in main frame to
+// prevent abusive behavior (crbug.com/890558).
+TEST_P(CRWWebControllerResponseTest,
+       BlockRendererInitiatedDataUrlResponseInMainFrame) {
+  // Simulate data:// url response with text/html MIME type.
+  SetWebViewURL(@(kTestDataURL));
+  NSURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@(kTestDataURL)]
+        statusCode:200
+       HTTPVersion:nil
+      headerFields:nil];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow;
+  ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
+      response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyCancel, policy);
+
+  // Verify that download task was not created for html response.
+  ASSERT_TRUE(download_delegate_.alive_download_tasks().empty());
+}
+
+// Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows
+// rendering data URLs for browser-initiated navigations in main frame.
+TEST_P(CRWWebControllerResponseTest,
+       AllowBrowserInitiatedDataUrlResponseInMainFrame) {
+  // Simulate data:// url response with text/html MIME type.
+  GURL url(kTestDataURL);
+  AddPendingItem(url, ui::PAGE_TRANSITION_TYPED);
+  SetWebViewURL(@(kTestDataURL));
+  NSURLResponse* response = [[NSHTTPURLResponse alloc]
+       initWithURL:[NSURL URLWithString:@(kTestDataURL)]
+        statusCode:200
+       HTTPVersion:nil
+      headerFields:nil];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel;
+  ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
+      response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyAllow, policy);
+
+  // Verify that download task was not created for html response.
+  ASSERT_TRUE(download_delegate_.alive_download_tasks().empty());
+}
+
 // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates
 // the DownloadTask for NSURLResponse.
-TEST_P(CRWWebControllerDownloadTest, CreationWithNSURLResponse) {
+TEST_P(CRWWebControllerResponseTest, DownloadWithNSURLResponse) {
   // Simulate download response.
   int64_t content_length = 10;
   NSURLResponse* response =
@@ -710,12 +797,15 @@
                                 MIMEType:@(kTestMimeType)
                    expectedContentLength:content_length
                         textEncodingName:nil];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow;
   ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
-      response, /*for_main_frame=*/YES));
+      response, /*for_main_frame=*/YES, /*can_show_mime_type=*/NO, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyCancel, policy);
 
   // Verify that download task was created.
-  ASSERT_EQ(1U, delegate_.alive_download_tasks().size());
-  DownloadTask* task = delegate_.alive_download_tasks()[0].second.get();
+  ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size());
+  DownloadTask* task =
+      download_delegate_.alive_download_tasks()[0].second.get();
   ASSERT_TRUE(task);
   EXPECT_TRUE(task->GetIndentifier());
   EXPECT_EQ(kTestURLString, task->GetOriginalUrl());
@@ -729,7 +819,7 @@
 
 // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates
 // the DownloadTask for NSHTTPURLResponse.
-TEST_P(CRWWebControllerDownloadTest, CreationWithNSHTTPURLResponse) {
+TEST_P(CRWWebControllerResponseTest, DownloadWithNSHTTPURLResponse) {
   // Simulate download response.
   const char kContentDisposition[] = "attachment; filename=download.test";
   NSURLResponse* response = [[NSHTTPURLResponse alloc]
@@ -739,12 +829,15 @@
       headerFields:@{
         @"content-disposition" : @(kContentDisposition),
       }];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow;
   ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
-      response, /*for_main_frame=*/YES));
+      response, /*for_main_frame=*/YES, /*can_show_mime_type=*/NO, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyCancel, policy);
 
   // Verify that download task was created.
-  ASSERT_EQ(1U, delegate_.alive_download_tasks().size());
-  DownloadTask* task = delegate_.alive_download_tasks()[0].second.get();
+  ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size());
+  DownloadTask* task =
+      download_delegate_.alive_download_tasks()[0].second.get();
   ASSERT_TRUE(task);
   EXPECT_TRUE(task->GetIndentifier());
   EXPECT_EQ(kTestURLString, task->GetOriginalUrl());
@@ -758,7 +851,7 @@
 
 // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates
 // the DownloadTask for NSHTTPURLResponse and iframes.
-TEST_P(CRWWebControllerDownloadTest, IFrameCreationWithNSHTTPURLResponse) {
+TEST_P(CRWWebControllerResponseTest, IFrameDownloadWithNSHTTPURLResponse) {
   // Simulate download response.
   const char kContentDisposition[] = "attachment; filename=download.test";
   NSURLResponse* response = [[NSHTTPURLResponse alloc]
@@ -768,12 +861,15 @@
       headerFields:@{
         @"content-disposition" : @(kContentDisposition),
       }];
+  WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow;
   ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse(
-      response, /*for_main_frame=*/NO));
+      response, /*for_main_frame=*/NO, /*can_show_mime_type=*/NO, &policy));
+  EXPECT_EQ(WKNavigationResponsePolicyCancel, policy);
 
   // Verify that download task was created.
-  ASSERT_EQ(1U, delegate_.alive_download_tasks().size());
-  DownloadTask* task = delegate_.alive_download_tasks()[0].second.get();
+  ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size());
+  DownloadTask* task =
+      download_delegate_.alive_download_tasks()[0].second.get();
   ASSERT_TRUE(task);
   EXPECT_TRUE(task->GetIndentifier());
   EXPECT_EQ(kTestURLString, task->GetOriginalUrl());
@@ -809,7 +905,7 @@
   EXPECT_EQ(kAbsolute, trust_level);
 }
 
-INSTANTIATE_TEST_CASES(CRWWebControllerDownloadTest);
+INSTANTIATE_TEST_CASES(CRWWebControllerResponseTest);
 
 // Test fixture to test decidePolicyForNavigationAction:decisionHandler:
 // decisionHandler's callback result.
diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h
index a408c013..4c4c1111 100644
--- a/ipc/ipc_message_start.h
+++ b/ipc/ipc_message_start.h
@@ -68,6 +68,7 @@
   SurfaceViewManagerMsgStart,
   ExtensionWorkerMsgStart,
   SubresourceFilterMsgStart,
+  ChromeAppsMsgStart,
   LastIPCMsgStart  // Must come last.
 };
 
diff --git a/media/capture/ipc/BUILD.gn b/media/capture/ipc/BUILD.gn
deleted file mode 100644
index dc78b719..0000000
--- a/media/capture/ipc/BUILD.gn
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-source_set("ipc") {
-  sources = [
-    "capture_param_traits.cc",
-    "capture_param_traits.h",
-    "capture_param_traits_macros.h",
-  ]
-
-  public_deps = [
-    "//ipc",
-  ]
-  deps = [
-    "//base",
-    "//media",
-    "//media/base/ipc",
-    "//media/capture:capture_base",
-    "//ui/gfx/ipc",
-    "//ui/gfx/ipc/geometry",
-    "//ui/gfx/ipc/skia",
-  ]
-}
diff --git a/media/capture/ipc/DEPS b/media/capture/ipc/DEPS
deleted file mode 100644
index edcceac..0000000
--- a/media/capture/ipc/DEPS
+++ /dev/null
@@ -1,4 +0,0 @@
-include_rules = [
-  "+ipc",
-  "-media/capture/capture_export.h",
-]
diff --git a/media/capture/ipc/OWNERS b/media/capture/ipc/OWNERS
deleted file mode 100644
index 146c3c3c..0000000
--- a/media/capture/ipc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-per-file *_param_traits*.*=set noparent
-per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/media/capture/ipc/capture_param_traits.cc b/media/capture/ipc/capture_param_traits.cc
deleted file mode 100644
index b55e80f..0000000
--- a/media/capture/ipc/capture_param_traits.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/capture/ipc/capture_param_traits.h"
-
-#include "base/strings/stringprintf.h"
-#include "ipc/ipc_message_utils.h"
-#include "media/base/ipc/media_param_traits.h"
-#include "media/base/limits.h"
-#include "media/capture/video_capture_types.h"
-#include "ui/gfx/ipc/geometry/gfx_param_traits.h"
-#include "ui/gfx/ipc/gfx_param_traits.h"
-#include "ui/gfx/ipc/skia/gfx_skia_param_traits.h"
-
-using media::VideoCaptureFormat;
-
-namespace IPC {
-
-void ParamTraits<VideoCaptureFormat>::Write(base::Pickle* m,
-                                            const VideoCaptureFormat& p) {
-  WriteParam(m, p.frame_size);
-  WriteParam(m, p.frame_rate);
-  WriteParam(m, p.pixel_format);
-}
-
-bool ParamTraits<VideoCaptureFormat>::Read(const base::Pickle* m,
-                                           base::PickleIterator* iter,
-                                           VideoCaptureFormat* r) {
-  if (!ReadParam(m, iter, &r->frame_size) ||
-      !ReadParam(m, iter, &r->frame_rate) ||
-      !ReadParam(m, iter, &r->pixel_format)) {
-    return false;
-  }
-  return r->IsValid();
-}
-
-void ParamTraits<VideoCaptureFormat>::Log(const VideoCaptureFormat& p,
-                                          std::string* l) {
-  l->append(base::StringPrintf("<VideoCaptureFormat> %s",
-                               media::VideoCaptureFormat::ToString(p).c_str()));
-}
-
-}  // namespace IPC
-
-// Generate param traits write methods.
-#include "ipc/param_traits_write_macros.h"
-namespace IPC {
-#undef MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
-#include "media/capture/ipc/capture_param_traits_macros.h"
-}  // namespace IPC
-
-// Generate param traits read methods.
-#include "ipc/param_traits_read_macros.h"
-namespace IPC {
-#undef MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
-#include "media/capture/ipc/capture_param_traits_macros.h"
-}  // namespace IPC
-
-// Generate param traits log methods.
-#include "ipc/param_traits_log_macros.h"
-namespace IPC {
-#undef MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
-#include "media/capture/ipc/capture_param_traits_macros.h"
-}  // namespace IPC
diff --git a/media/capture/ipc/capture_param_traits.h b/media/capture/ipc/capture_param_traits.h
deleted file mode 100644
index 5a49960..0000000
--- a/media/capture/ipc/capture_param_traits.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_H_
-#define MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_H_
-
-#include "ipc/ipc_message.h"
-#include "ipc/ipc_param_traits.h"
-#include "media/capture/ipc/capture_param_traits_macros.h"
-
-namespace media {
-struct VideoCaptureFormat;
-}
-
-namespace IPC {
-
-template <>
-struct ParamTraits<media::VideoCaptureFormat> {
-  typedef media::VideoCaptureFormat param_type;
-  static void Write(base::Pickle* m, const param_type& p);
-  static bool Read(const base::Pickle* m,
-                   base::PickleIterator* iter,
-                   param_type* r);
-  static void Log(const param_type& p, std::string* l);
-};
-
-}  // namespace IPC
-
-#endif  // MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_H_
diff --git a/media/capture/ipc/capture_param_traits_macros.h b/media/capture/ipc/capture_param_traits_macros.h
deleted file mode 100644
index 30d8f93..0000000
--- a/media/capture/ipc/capture_param_traits_macros.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
-#define MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
-
-#include "ipc/ipc_message_macros.h"
-#include "media/capture/video/video_capture_device_descriptor.h"
-#include "media/capture/video_capture_types.h"
-
-IPC_STRUCT_TRAITS_BEGIN(media::VideoCaptureDeviceDescriptor::CameraCalibration)
-  IPC_STRUCT_TRAITS_MEMBER(focal_length_x)
-  IPC_STRUCT_TRAITS_MEMBER(focal_length_y)
-  IPC_STRUCT_TRAITS_MEMBER(depth_near)
-  IPC_STRUCT_TRAITS_MEMBER(depth_far)
-IPC_STRUCT_TRAITS_END()
-
-#endif  // MEDIA_CAPTURE_IPC_CAPTURE_PARAM_TRAITS_MACROS_H_
diff --git a/media/capture/mojom/video_capture_types.typemap b/media/capture/mojom/video_capture_types.typemap
index e54002d..146a77b 100644
--- a/media/capture/mojom/video_capture_types.typemap
+++ b/media/capture/mojom/video_capture_types.typemap
@@ -10,11 +10,7 @@
   "//media/capture/video/video_capture_device_info.h",
 ]
 
-traits_headers = [
-  "//media/capture/ipc/capture_param_traits_macros.h",
-  "//media/capture/ipc/capture_param_traits.h",
-  "//media/capture/mojom/video_capture_types_mojom_traits.h",
-]
+traits_headers = [ "//media/capture/mojom/video_capture_types_mojom_traits.h" ]
 
 sources = [
   "//media/capture/mojom/video_capture_types_mojom_traits.cc",
@@ -24,7 +20,6 @@
   "//media",
   "//media/base/ipc",
   "//media/capture:capture_base",
-  "//media/capture/ipc",
   "//media/mojo/interfaces",
   "//ui/gfx/geometry/mojo:struct_traits",
 ]
diff --git a/media/formats/webm/webm_tracks_parser.cc b/media/formats/webm/webm_tracks_parser.cc
index 2379ac5..e3941ae 100644
--- a/media/formats/webm/webm_tracks_parser.cc
+++ b/media/formats/webm/webm_tracks_parser.cc
@@ -116,7 +116,11 @@
 
 WebMParserClient* WebMTracksParser::OnListStart(int id) {
   if (id == kWebMIdContentEncodings) {
-    DCHECK(!track_content_encodings_client_.get());
+    if (track_content_encodings_client_) {
+      MEDIA_LOG(ERROR, media_log_) << "Multiple ContentEncodings lists";
+      return NULL;
+    }
+
     track_content_encodings_client_.reset(
         new WebMContentEncodingsClient(media_log_));
     return track_content_encodings_client_->OnListStart(id);
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index 8c3d681..5cb08e3 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -94,6 +94,9 @@
     impl_.reset();
   else
     impl_task_runner_->DeleteSoon(FROM_HERE, std::move(impl_));
+
+  // Explicitly destroy the decoder, since it can reference picture buffers.
+  accelerated_video_decoder_.reset();
 }
 
 std::string D3D11VideoDecoder::GetDisplayName() const {
diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h
index 8e1e0b88..cc789df 100644
--- a/mojo/public/cpp/system/data_pipe.h
+++ b/mojo/public/cpp/system/data_pipe.h
@@ -149,7 +149,6 @@
 inline DataPipe::DataPipe() {
   MojoResult result =
       CreateDataPipe(nullptr, &producer_handle, &consumer_handle);
-  ALLOW_UNUSED_LOCAL(result);
   CHECK_EQ(MOJO_RESULT_OK, result);
 }
 
@@ -159,17 +158,14 @@
   options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
   options.element_num_bytes = 1;
   options.capacity_num_bytes = capacity_num_bytes;
-  mojo::DataPipe data_pipe(options);
   MojoResult result =
       CreateDataPipe(&options, &producer_handle, &consumer_handle);
-  ALLOW_UNUSED_LOCAL(result);
   CHECK_EQ(MOJO_RESULT_OK, result);
 }
 
 inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) {
   MojoResult result =
       CreateDataPipe(&options, &producer_handle, &consumer_handle);
-  ALLOW_UNUSED_LOCAL(result);
   CHECK_EQ(MOJO_RESULT_OK, result);
 }
 
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index 0c03953..4a6503c 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -528,6 +528,18 @@
   dict = "//net/data/fuzzer_dictionaries/net_dns_record_fuzzer.dict"
 }
 
+fuzzer_test("net_dns_response_fuzzer") {
+  sources = [
+    "dns_response_fuzzer.cc",
+  ]
+  deps = [
+    "//base",
+    "//net",
+    "//net:net_fuzzer_test_support",
+  ]
+  dict = "//net/data/fuzzer_dictionaries/net_dns_record_fuzzer.dict"
+}
+
 fuzzer_test("net_host_resolver_impl_fuzzer") {
   sources = [
     "host_resolver_impl_fuzzer.cc",
diff --git a/net/dns/dns_query.h b/net/dns/dns_query.h
index c01909e..8e77ac5 100644
--- a/net/dns/dns_query.h
+++ b/net/dns/dns_query.h
@@ -65,6 +65,12 @@
   // response.
   base::StringPiece question() const;
 
+  // Returns the size of the question section.
+  size_t question_size() const {
+    // QNAME + QTYPE + QCLASS
+    return qname_size_ + sizeof(uint16_t) + sizeof(uint16_t);
+  }
+
   // IOBuffer accessor to be used for writing out the query. The buffer has
   // the same byte layout as the DNS query wire format.
   IOBufferWithSize* io_buffer() const { return io_buffer_.get(); }
@@ -80,12 +86,6 @@
   // convert to the dotted format "www.chromium.com" with no trailing dot.
   bool ReadName(base::BigEndianReader* reader, std::string* out);
 
-  // Returns the size of the question section.
-  size_t question_size() const {
-    // QNAME + QTYPE + QCLASS
-    return qname_size_ + sizeof(uint16_t) + sizeof(uint16_t);
-  }
-
   // Size of the DNS name (*NOT* hostname) we are trying to resolve; used
   // to calculate offsets.
   size_t qname_size_ = 0;
diff --git a/net/dns/dns_query_parse_fuzzer.cc b/net/dns/dns_query_parse_fuzzer.cc
index dceb703..f936218 100644
--- a/net/dns/dns_query_parse_fuzzer.cc
+++ b/net/dns/dns_query_parse_fuzzer.cc
@@ -14,7 +14,7 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   auto packet = base::MakeRefCounted<net::IOBufferWithSize>(size);
   memcpy(packet->data(), data, size);
-  auto out = std::make_unique<net::DnsQuery>(packet.get());
+  auto out = std::make_unique<net::DnsQuery>(packet);
   out->Parse();
   return 0;
 }
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index 809ca0fb6..7177dcaf 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -159,10 +159,12 @@
   return true;
 }
 
-DnsResponse::DnsResponse(uint16_t id,
-                         bool is_authoritative,
-                         const std::vector<DnsResourceRecord>& answers,
-                         const base::Optional<DnsQuery>& query) {
+DnsResponse::DnsResponse(
+    uint16_t id,
+    bool is_authoritative,
+    const std::vector<DnsResourceRecord>& answers,
+    const std::vector<DnsResourceRecord>& additional_records,
+    const base::Optional<DnsQuery>& query) {
   bool has_query = query.has_value();
   dns_protocol::Header header;
   header.id = id;
@@ -178,23 +180,28 @@
     header.flags |= dns_protocol::kFlagAA;
   }
   header.ancount = answers.size();
+  header.arcount = additional_records.size();
 
   // Response starts with the header and the question section (if any).
-  size_t response_size =
-      has_query ? query.value().io_buffer()->size() : sizeof(header);
-  // Add the size of all answers.
-  response_size = std::accumulate(
-      answers.begin(), answers.end(), response_size,
-      [](size_t cur_size, const DnsResourceRecord& answer) {
-        bool has_final_dot = answer.name.back() == '.';
-        // Depending on if answer.name in the dotted format has the final dot
-        // for the root domain or not, the corresponding DNS domain name format
-        // to be written to rdata is 1 byte (with dot) or 2 bytes larger in
-        // size. See RFC 1035, Section 3.1 and DNSDomainFromDot.
-        return cur_size + answer.name.size() + (has_final_dot ? 1 : 2) +
-               kResourceRecordSizeInBytesWithoutNameAndRData +
-               answer.rdata.size();
-      });
+  size_t response_size = has_query
+                             ? sizeof(header) + query.value().question_size()
+                             : sizeof(header);
+  // Add the size of all answers and additional records.
+  auto do_accumulation = [](size_t cur_size, const DnsResourceRecord& answer) {
+    bool has_final_dot = answer.name.back() == '.';
+    // Depending on if answer.name in the dotted format has the final dot
+    // for the root domain or not, the corresponding DNS domain name format
+    // to be written to rdata is 1 byte (with dot) or 2 bytes larger in
+    // size. See RFC 1035, Section 3.1 and DNSDomainFromDot.
+    return cur_size + answer.name.size() + (has_final_dot ? 1 : 2) +
+           kResourceRecordSizeInBytesWithoutNameAndRData + answer.rdata.size();
+  };
+  response_size = std::accumulate(answers.begin(), answers.end(), response_size,
+                                  do_accumulation);
+
+  response_size =
+      std::accumulate(additional_records.begin(), additional_records.end(),
+                      response_size, do_accumulation);
 
   io_buffer_ = base::MakeRefCounted<IOBuffer>(response_size);
   io_buffer_size_ = response_size;
@@ -205,10 +212,16 @@
     success &= WriteQuestion(&writer, query.value());
     DCHECK(success);
   }
+  // Start the Answer section.
   for (const auto& answer : answers) {
     success &= WriteAnswer(&writer, answer, query);
     DCHECK(success);
   }
+  // Start the Additional section.
+  for (const auto& record : additional_records) {
+    success &= WriteRecord(&writer, record);
+    DCHECK(success);
+  }
   if (!success) {
     io_buffer_.reset();
     io_buffer_size_ = 0;
@@ -434,6 +447,24 @@
   return writer->WriteBytes(question.data(), question.size());
 }
 
+bool DnsResponse::WriteRecord(base::BigEndianWriter* writer,
+                              const DnsResourceRecord& record) {
+  if (!RecordRdata::HasValidSize(record.rdata, record.type)) {
+    VLOG(1) << "Invalid RDATA size for a record.";
+    return false;
+  }
+  std::string domain_name;
+  if (!DNSDomainFromDot(record.name, &domain_name)) {
+    VLOG(1) << "Invalid dotted name.";
+    return false;
+  }
+  return writer->WriteBytes(domain_name.data(), domain_name.size()) &&
+         writer->WriteU16(record.type) && writer->WriteU16(record.klass) &&
+         writer->WriteU32(record.ttl) &&
+         writer->WriteU16(record.rdata.size()) &&
+         writer->WriteBytes(record.rdata.data(), record.rdata.size());
+}
+
 bool DnsResponse::WriteAnswer(base::BigEndianWriter* writer,
                               const DnsResourceRecord& answer,
                               const base::Optional<DnsQuery>& query) {
@@ -441,20 +472,7 @@
     VLOG(1) << "Mismatched answer resource record type and qtype.";
     return false;
   }
-  if (!RecordRdata::HasValidSize(answer.rdata, answer.type)) {
-    VLOG(1) << "Invalid RDATA size for an answer.";
-    return false;
-  }
-  std::string domain_name;
-  if (!DNSDomainFromDot(answer.name, &domain_name)) {
-    VLOG(1) << "Invalid dotted name.";
-    return false;
-  }
-  return writer->WriteBytes(domain_name.data(), domain_name.size()) &&
-         writer->WriteU16(answer.type) && writer->WriteU16(answer.klass) &&
-         writer->WriteU32(answer.ttl) &&
-         writer->WriteU16(answer.rdata.size()) &&
-         writer->WriteBytes(answer.rdata.data(), answer.rdata.size());
+  return WriteRecord(writer, answer);
 }
 
 }  // namespace net
diff --git a/net/dns/dns_response.h b/net/dns/dns_response.h
index 1f8aefbf..c796295 100644
--- a/net/dns/dns_response.h
+++ b/net/dns/dns_response.h
@@ -116,6 +116,7 @@
   DnsResponse(uint16_t id,
               bool is_authoritative,
               const std::vector<DnsResourceRecord>& answers,
+              const std::vector<DnsResourceRecord>& additional_records,
               const base::Optional<DnsQuery>& query);
 
   // Constructs a response buffer of given length. Used for TCP transactions.
@@ -179,6 +180,8 @@
   bool WriteHeader(base::BigEndianWriter* writer,
                    const dns_protocol::Header& header);
   bool WriteQuestion(base::BigEndianWriter* writer, const DnsQuery& query);
+  bool WriteRecord(base::BigEndianWriter* wirter,
+                   const DnsResourceRecord& record);
   bool WriteAnswer(base::BigEndianWriter* wirter,
                    const DnsResourceRecord& answer,
                    const base::Optional<DnsQuery>& query);
diff --git a/net/dns/dns_response_fuzzer.cc b/net/dns/dns_response_fuzzer.cc
new file mode 100644
index 0000000..c53e179a
--- /dev/null
+++ b/net/dns/dns_response_fuzzer.cc
@@ -0,0 +1,30 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "net/base/io_buffer.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  auto packet = base::MakeRefCounted<net::IOBufferWithSize>(size);
+  memcpy(packet->data(), data, size);
+  base::Optional<net::DnsQuery> query;
+  query.emplace(packet);
+  if (!query->Parse()) {
+    return 0;
+  }
+  net::DnsResponse response(query->id(), true /* is_authoritative */,
+                            {} /* answers */, {} /* additional records */,
+                            query);
+  std::string out =
+      base::HexEncode(response.io_buffer()->data(), response.io_buffer_size());
+  return 0;
+}
diff --git a/net/dns/dns_response_unittest.cc b/net/dns/dns_response_unittest.cc
index 163f28c..7ec9410d 100644
--- a/net/dns/dns_response_unittest.cc
+++ b/net/dns/dns_response_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "net/dns/dns_response.h"
 
+#include "base/big_endian.h"
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "net/base/address_list.h"
@@ -12,6 +13,7 @@
 #include "net/dns/dns_query.h"
 #include "net/dns/dns_test_util.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/record_rdata.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
@@ -596,7 +598,7 @@
   answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
   std::vector<DnsResourceRecord> answers(1, answer);
   DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
-                       answers, base::nullopt);
+                       answers, {} /* additional records */, base::nullopt);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
   std::string expected_response(response_data, sizeof(response_data));
@@ -630,7 +632,7 @@
   answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
   std::vector<DnsResourceRecord> answers(1, answer);
   DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
-                       answers, base::nullopt);
+                       answers, {} /* additional records */, base::nullopt);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
   std::string expected_response(response_data, sizeof(response_data));
@@ -664,8 +666,10 @@
   std::string dotted_name("www.example.com");
   std::string dns_name;
   ASSERT_TRUE(DNSDomainFromDot(dotted_name, &dns_name));
+  OptRecordRdata opt_rdata;
+  opt_rdata.AddOpt(OptRecordRdata::Opt(255, "\xde\xad\xbe\xef"));
   base::Optional<DnsQuery> query;
-  query.emplace(0x1234 /* id */, dns_name, dns_protocol::kTypeA);
+  query.emplace(0x1234 /* id */, dns_name, dns_protocol::kTypeA, &opt_rdata);
   net::DnsResourceRecord answer;
   answer.name = dotted_name;
   answer.type = dns_protocol::kTypeA;
@@ -674,7 +678,70 @@
   answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
   std::vector<DnsResourceRecord> answers(1, answer);
   DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
-                       query);
+                       {} /* additional records */, query);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest,
+     SingleAnswerWithQuestionConstructedFromSizeInflatedQuery) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x01,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,        // null label
+      0x00, 0x01,  // type A Record
+      0x00, 0x01,  // class IN
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+  };
+  std::string dotted_name("www.example.com");
+  std::string dns_name;
+  ASSERT_TRUE(DNSDomainFromDot(dotted_name, &dns_name));
+  size_t buf_size =
+      sizeof(dns_protocol::Header) + dns_name.size() + 2 /* qtype */ +
+      2 /* qclass */ +
+      10 /* extra bytes that inflate the internal buffer of a query */;
+  auto buf = base::MakeRefCounted<IOBufferWithSize>(buf_size);
+  memset(buf->data(), 0, buf->size());
+  base::BigEndianWriter writer(buf->data(), buf_size);
+  writer.WriteU16(0x1234);                              // id
+  writer.WriteU16(0);                                   // flags, is query
+  writer.WriteU16(1);                                   // qdcount
+  writer.WriteU16(0);                                   // ancount
+  writer.WriteU16(0);                                   // nscount
+  writer.WriteU16(0);                                   // arcount
+  writer.WriteBytes(dns_name.data(), dns_name.size());  // qname
+  writer.WriteU16(dns_protocol::kTypeA);                // qtype
+  writer.WriteU16(dns_protocol::kClassIN);              // qclass
+  // buf contains 10 extra zero bytes.
+  base::Optional<DnsQuery> query;
+  query.emplace(buf);
+  query->Parse();
+  net::DnsResourceRecord answer;
+  answer.name = dotted_name;
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
+                       {} /* additional records */, query);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
   std::string expected_response(response_data, sizeof(response_data));
@@ -710,7 +777,70 @@
       "\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01", 16);
   std::vector<DnsResourceRecord> answers(1, answer);
   DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
-                       base::nullopt);
+                       {} /* additional records */, base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest,
+     SingleARecordAnswerWithQuestionAndNsecAdditionalRecord) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x01,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x01,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,        // null label
+      0x00, 0x01,  // type A Record
+      0x00, 0x01,  // class IN
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x2f,              // type NSEC Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x05,              // rdlength, 5 bytes
+      0xc0, 0x0c,              // pointer to the previous "www.example.com"
+      0x00, 0x01, 0x40,        // type bit map of type A: window block 0, bitmap
+                               // length 1, bitmap with bit 1 set
+  };
+  std::string dotted_name("www.example.com");
+  std::string dns_name;
+  ASSERT_TRUE(DNSDomainFromDot(dotted_name, &dns_name));
+  base::Optional<DnsQuery> query;
+  query.emplace(0x1234 /* id */, dns_name, dns_protocol::kTypeA);
+  net::DnsResourceRecord answer;
+  answer.name = dotted_name;
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  net::DnsResourceRecord additional_record;
+  additional_record.name = dotted_name;
+  additional_record.type = dns_protocol::kTypeNSEC;
+  additional_record.klass = dns_protocol::kClassIN;
+  additional_record.ttl = 120;  // 120 seconds.
+  // Bitmap for "www.example.com" with type A set.
+  additional_record.rdata = base::StringPiece("\xc0\x0c\x00\x01\x40", 5);
+  std::vector<DnsResourceRecord> additional_records(1, additional_record);
+  DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
+                       additional_records, query);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
   std::string expected_response(response_data, sizeof(response_data));
@@ -761,7 +891,7 @@
   answers[0] = answer1;
   answers[1] = answer2;
   DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
-                       base::nullopt);
+                       {} /* additional records */, base::nullopt);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
   std::string expected_response(response_data, sizeof(response_data));
@@ -771,25 +901,43 @@
 }
 
 TEST(DnsResponseWriteTest, WrittenResponseCanBeParsed) {
+  std::string dotted_name("www.example.com");
   net::DnsResourceRecord answer;
-  answer.name = "www.example.com";
+  answer.name = dotted_name;
   answer.type = dns_protocol::kTypeA;
   answer.klass = dns_protocol::kClassIN;
   answer.ttl = 120;  // 120 seconds.
   answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
   std::vector<DnsResourceRecord> answers(1, answer);
+  net::DnsResourceRecord additional_record;
+  additional_record.name = dotted_name;
+  additional_record.type = dns_protocol::kTypeNSEC;
+  additional_record.klass = dns_protocol::kClassIN;
+  additional_record.ttl = 120;  // 120 seconds.
+  additional_record.rdata = base::StringPiece("\xc0\x0c\x00\x01\x04", 5);
+  std::vector<DnsResourceRecord> additional_records(1, additional_record);
   DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
-                       answers, base::nullopt);
+                       answers, additional_records, base::nullopt);
   ASSERT_NE(nullptr, response.io_buffer());
   EXPECT_TRUE(response.IsValid());
+  EXPECT_EQ(1u, response.answer_count());
+  EXPECT_EQ(1u, response.additional_answer_count());
   auto parser = response.Parser();
   net::DnsResourceRecord parsed_record;
   EXPECT_TRUE(parser.ReadRecord(&parsed_record));
+  // Answer with an A record.
   EXPECT_EQ(answer.name, parsed_record.name);
   EXPECT_EQ(answer.type, parsed_record.type);
   EXPECT_EQ(answer.klass, parsed_record.klass);
   EXPECT_EQ(answer.ttl, parsed_record.ttl);
   EXPECT_EQ(answer.rdata, parsed_record.rdata);
+  // Additional NSEC record.
+  EXPECT_TRUE(parser.ReadRecord(&parsed_record));
+  EXPECT_EQ(additional_record.name, parsed_record.name);
+  EXPECT_EQ(additional_record.type, parsed_record.type);
+  EXPECT_EQ(additional_record.klass, parsed_record.klass);
+  EXPECT_EQ(additional_record.ttl, parsed_record.ttl);
+  EXPECT_EQ(additional_record.rdata, parsed_record.rdata);
 }
 
 }  // namespace
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index e5530f1..9a20ccb 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -54,6 +54,18 @@
 
 namespace {
 
+// Creates an address from ip address and port and writes it to |*address|.
+bool CreateUDPAddress(const std::string& ip_str,
+                      uint16_t port,
+                      IPEndPoint* address) {
+  IPAddress ip_address;
+  if (!ip_address.AssignFromIPLiteral(ip_str))
+    return false;
+
+  *address = IPEndPoint(ip_address, port);
+  return true;
+}
+
 class UDPSocketTest : public PlatformTest, public WithScopedTaskEnvironment {
  public:
   UDPSocketTest() : buffer_(base::MakeRefCounted<IOBufferWithSize>(kMaxRead)) {}
@@ -114,14 +126,15 @@
     WriteSocket(socket, msg);
   }
 
-  // Creates an address from ip address and port and writes it to |*address|.
-  void CreateUDPAddress(const std::string& ip_str,
-                        uint16_t port,
-                        IPEndPoint* address) {
-    IPAddress ip_address;
-    if (!ip_address.AssignFromIPLiteral(ip_str))
-      return;
-    *address = IPEndPoint(ip_address, port);
+  // And again for a bare socket
+  int SendToSocket(UDPSocket* socket,
+                   std::string msg,
+                   const IPEndPoint& address) {
+    scoped_refptr<StringIOBuffer> io_buffer = new StringIOBuffer(msg);
+    TestCompletionCallback callback;
+    int rv = socket->SendTo(io_buffer.get(), io_buffer->size(), address,
+                            callback.callback());
+    return callback.GetResult(rv);
   }
 
   // Run unit test for a connection test.
@@ -310,9 +323,9 @@
   std::string first_message("first message"), second_message("second message");
 
   IPEndPoint broadcast_address;
-  CreateUDPAddress("127.255.255.255", kPort, &broadcast_address);
+  ASSERT_TRUE(CreateUDPAddress("127.255.255.255", kPort, &broadcast_address));
   IPEndPoint listen_address;
-  CreateUDPAddress("0.0.0.0", kPort, &listen_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &listen_address));
 
   TestNetLog server1_log, server2_log;
   std::unique_ptr<UDPServerSocket> server1(
@@ -581,7 +594,7 @@
 TEST_F(UDPSocketTest, ServerSetDoNotFragment) {
   for (std::string ip : {"127.0.0.1", "::1"}) {
     IPEndPoint bind_address;
-    CreateUDPAddress(ip, 0, &bind_address);
+    ASSERT_TRUE(CreateUDPAddress(ip, 0, &bind_address));
     UDPServerSocket server(nullptr, NetLogSource());
     int rv = server.Listen(bind_address);
     // May fail on IPv6 is IPv6 is not configure
@@ -630,7 +643,7 @@
   const char kGroup[] = "237.132.100.17";
 
   IPEndPoint bind_address;
-  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
   IPAddress group_ip;
   EXPECT_TRUE(group_ip.AssignFromIPLiteral(kGroup));
 
@@ -662,7 +675,7 @@
 TEST_F(UDPSocketTest, MulticastOptions) {
   const uint16_t kPort = 9999;
   IPEndPoint bind_address;
-  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
 
   UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // Before binding.
@@ -690,7 +703,7 @@
   IPEndPoint bind_address;
   UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // We need a real IP, but we won't actually send anything to it.
-  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
   int rv = client.Open(bind_address.GetFamily());
   EXPECT_THAT(rv, IsOk());
 
@@ -768,120 +781,256 @@
 namespace {
 
 const HANDLE kFakeHandle = (HANDLE)19;
-const QOS_FLOWID kFakeFlowId = (QOS_FLOWID)27;
+const QOS_FLOWID kFakeFlowId1 = (QOS_FLOWID)27;
+const QOS_FLOWID kFakeFlowId2 = (QOS_FLOWID)38;
 
-BOOL WINAPI FakeQOSCreateHandleFAIL(PQOS_VERSION version, PHANDLE handle) {
-  EXPECT_EQ(0, version->MinorVersion);
-  EXPECT_EQ(1, version->MajorVersion);
-  SetLastError(ERROR_OPEN_FAILED);
-  return false;
+class TestUDPSocketWin : public UDPSocketWin {
+ public:
+  TestUDPSocketWin(QwaveAPI& qos,
+                   DatagramSocket::BindType bind_type,
+                   net::NetLog* net_log,
+                   const net::NetLogSource& source)
+      : UDPSocketWin(bind_type, net_log, source), qos_(qos) {}
+
+  // Overriding GetQwaveAPI causes the test class to use the injected mock
+  // QwaveAPI instance instead of the singleton.  Ensure close is called in the
+  // child destructor before our mock CloseHandle is uninstalled.
+  ~TestUDPSocketWin() override { UDPSocketWin::Close(); }
+
+  QwaveAPI& GetQwaveAPI() override { return qos_; }
+
+ private:
+  QwaveAPI& qos_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUDPSocketWin);
+};
+
+class MockQwaveAPI : public QwaveAPI {
+ public:
+  bool qwave_supported() const override { return true; }
+  MOCK_METHOD2(CreateHandle, BOOL(PQOS_VERSION version, PHANDLE handle));
+  MOCK_METHOD1(CloseHandle, BOOL(HANDLE handle));
+
+  MOCK_METHOD6(AddSocketToFlow,
+               BOOL(HANDLE handle,
+                    SOCKET socket,
+                    PSOCKADDR addr,
+                    QOS_TRAFFIC_TYPE traffic_type,
+                    DWORD flags,
+                    PQOS_FLOWID flow_id));
+
+  MOCK_METHOD4(
+      RemoveSocketFromFlow,
+      BOOL(HANDLE handle, SOCKET socket, QOS_FLOWID flow_id, DWORD reserved));
+  MOCK_METHOD7(SetFlow,
+               BOOL(HANDLE handle,
+                    QOS_FLOWID flow_id,
+                    QOS_SET_FLOW op,
+                    ULONG size,
+                    PVOID data,
+                    DWORD reserved,
+                    LPOVERLAPPED overlapped));
+};
+
+std::unique_ptr<UDPSocket> OpenedDscpTestClient(QwaveAPI& qos,
+                                                IPEndPoint bind_address) {
+  auto client = std::make_unique<TestUDPSocketWin>(
+      qos, DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
+  int rv = client->Open(bind_address.GetFamily());
+  EXPECT_THAT(rv, IsOk());
+
+  return client;
 }
 
-BOOL WINAPI FakeQOSCreateHandle(PQOS_VERSION version, PHANDLE handle) {
-  EXPECT_EQ(0, version->MinorVersion);
-  EXPECT_EQ(1, version->MajorVersion);
-  *handle = kFakeHandle;
-  return true;
+std::unique_ptr<UDPSocket> ConnectedDscpTestClient(QwaveAPI& qos) {
+  IPEndPoint bind_address;
+  // We need a real IP, but we won't actually send anything to it.
+  EXPECT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
+  auto client = OpenedDscpTestClient(qos, bind_address);
+  EXPECT_THAT(client->Connect(bind_address), IsOk());
+  return client;
 }
 
-BOOL WINAPI FakeQOSCloseHandle(HANDLE handle) {
-  EXPECT_EQ(kFakeHandle, handle);
-  return true;
-}
-
-QOS_TRAFFIC_TYPE g_expected_traffic_type;
-
-BOOL WINAPI FakeQOSAddSocketToFlow(HANDLE handle,
-                                   SOCKET socket,
-                                   PSOCKADDR addr,
-                                   QOS_TRAFFIC_TYPE traffic_type,
-                                   DWORD flags,
-                                   PQOS_FLOWID flow_id) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(NULL, addr);
-  EXPECT_EQ(static_cast<DWORD>(QOS_NON_ADAPTIVE_FLOW), flags);
-  EXPECT_EQ(0u, *flow_id);
-  *flow_id = kFakeFlowId;
-  return true;
-}
-
-BOOL WINAPI FakeQOSRemoveSocketFromFlow(HANDLE handle,
-                                        SOCKET socket,
-                                        QOS_FLOWID flowid,
-                                        DWORD reserved) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(0u, socket);
-  EXPECT_EQ(kFakeFlowId, flowid);
-  EXPECT_EQ(0u, reserved);
-  return true;
-}
-
-DWORD g_expected_dscp;
-
-BOOL WINAPI FakeQOSSetFlow(HANDLE handle,
-                           QOS_FLOWID flow_id,
-                           QOS_SET_FLOW op,
-                           ULONG size,
-                           PVOID data,
-                           DWORD reserved,
-                           LPOVERLAPPED overlapped) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(QOSSetOutgoingDSCPValue, op);
-  EXPECT_EQ(sizeof(DWORD), size);
-  EXPECT_EQ(g_expected_dscp, *reinterpret_cast<DWORD*>(data));
-  EXPECT_EQ(kFakeFlowId, flow_id);
-  EXPECT_EQ(0u, reserved);
-  EXPECT_EQ(NULL, overlapped);
-  return true;
+std::unique_ptr<UDPSocket> UnconnectedDscpTestClient(QwaveAPI& qos) {
+  IPEndPoint bind_address;
+  EXPECT_TRUE(CreateUDPAddress("0.0.0.0", 9999, &bind_address));
+  auto client = OpenedDscpTestClient(qos, bind_address);
+  EXPECT_THAT(client->Bind(bind_address), IsOk());
+  return client;
 }
 
 }  // namespace
 
-// Mock out the Qwave functions and make sure they are
-// called correctly. Must be in net namespace for friendship
-// reasons.
-TEST_F(UDPSocketTest, SetDSCPFake) {
-  // Setup the server to listen.
-  IPEndPoint bind_address;
-  // We need a real IP, but we won't actually send anything to it.
-  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
-  UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
-  int rv = client.SetDiffServCodePoint(DSCP_AF41);
-  EXPECT_THAT(rv, IsError(ERR_SOCKET_NOT_CONNECTED));
+using ::testing::_;
+using ::testing::Return;
+using ::testing::SetArgPointee;
 
-  rv = client.Open(bind_address.GetFamily());
-  EXPECT_THAT(rv, IsOk());
+TEST_F(UDPSocketTest, SetDSCPNoopIfPassedNoChange) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
+}
 
-  rv = client.Connect(bind_address);
-  EXPECT_THAT(rv, IsOk());
+TEST_F(UDPSocketTest, SetDSCPFailsIfQOSHandleCanNotBeCreated) {
+  MockQwaveAPI qos;
+  EXPECT_CALL(qos, CreateHandle(_, _)).WillOnce(Return(false));
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
 
-  QwaveAPI& qos(QwaveAPI::Get());
-  qos.create_handle_func_ = FakeQOSCreateHandleFAIL;
-  qos.close_handle_func_ = FakeQOSCloseHandle;
-  qos.add_socket_to_flow_func_ = FakeQOSAddSocketToFlow;
-  qos.remove_socket_from_flow_func_ = FakeQOSRemoveSocketFromFlow;
-  qos.set_flow_func_ = FakeQOSSetFlow;
-  qos.qwave_supported_ = true;
+  EXPECT_EQ(ERR_NOT_IMPLEMENTED, client->SetDiffServCodePoint(DSCP_AF41));
+}
 
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
-  EXPECT_EQ(ERROR_NOT_SUPPORTED, client.SetDiffServCodePoint(DSCP_AF41));
-  qos.create_handle_func_ = FakeQOSCreateHandle;
-  g_expected_dscp = DSCP_AF41;
-  g_expected_traffic_type = QOSTrafficTypeAudioVideo;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_AF41), IsOk());
-  g_expected_dscp = DSCP_DEFAULT;
-  g_expected_traffic_type = QOSTrafficTypeBestEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
-  g_expected_dscp = DSCP_CS2;
-  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_CS2), IsOk());
-  g_expected_dscp = DSCP_CS3;
-  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
-  g_expected_dscp = DSCP_DEFAULT;
-  g_expected_traffic_type = QOSTrafficTypeBestEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
-  client.Close();
+MATCHER_P(DscpPointee, dscp, "") {
+  return *(DWORD*)arg == (DWORD)dscp;
+}
+
+TEST_F(UDPSocketTest, SetDSCPCallsQwaveFunctions) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+  // AddSocketToFlow also sets flow_id, but we don't use that here
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeAudioVideo, _, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
+                           DscpPointee(DSCP_AF41), _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+TEST_F(UDPSocketTest, SecondSetDSCPCallsQwaveFunctions) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+
+  // New dscp value should reset the flow.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeBestEffort, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
+                           DscpPointee(DSCP_DEFAULT), _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+// TODO(zstein): Mocking out DscpManager might be simpler here
+// (just verify that DscpManager::Set and DscpManager::PrepareForSend are
+// called).
+TEST_F(UDPSocketTest, SendToCallsQwaveApis) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = UnconnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
+
+  std::string simple_message("hello world");
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 9438);
+  int rv = SendToSocket(client.get(), simple_message, server_address);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // TODO(zstein): Move to second test case (Qwave APIs called once per address)
+  rv = SendToSocket(client.get(), simple_message, server_address);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // TODO(zstein): Move to third test case (Qwave APIs called for each
+  // destination address).
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _)).WillOnce(Return(true));
+  IPEndPoint server_address2(IPAddress::IPv4Localhost(), 9439);
+
+  rv = SendToSocket(client.get(), simple_message, server_address2);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+class DscpManagerTest : public testing::Test {
+ protected:
+  DscpManagerTest() : dscp_manager_(qos_, INVALID_SOCKET, (HANDLE)0) {
+    CreateUDPAddress("1.2.3.4", 9001, &address1_);
+    CreateUDPAddress("1234:5678:90ab:cdef:1234:5678:90ab:cdef", 9002,
+                     &address2_);
+  }
+
+  MockQwaveAPI qos_;
+  DscpManager dscp_manager_;
+
+  IPEndPoint address1_;
+  IPEndPoint address2_;
+};
+
+TEST_F(DscpManagerTest, PrepareForSendIsNoopIfNoSet) {
+  dscp_manager_.PrepareForSend(address1_);
+}
+
+TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisAfterSet) {
+  dscp_manager_.Set(DSCP_CS2);
+
+  // AddSocketToFlow should be called for each address.
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)))
+      .WillOnce(Return(true));
+  // SetFlow should only be called when the flow is first created.
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
+  dscp_manager_.PrepareForSend(address2_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
+}
+
+TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisOncePerAddress) {
+  dscp_manager_.Set(DSCP_CS2);
+
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
+}
+
+TEST_F(DscpManagerTest, SetDestroysExistingFlowAndResetsPrepareState) {
+  dscp_manager_.Set(DSCP_CS2);
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Calling Set should destroy the existing flow.
+  // TODO(zstein): Verify that RemoveSocketFromFlow with no address
+  // destroys the flow for all destinations.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, NULL, kFakeFlowId1, _));
+  dscp_manager_.Set(DSCP_CS5);
+
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, kFakeFlowId2, _));
 }
 #endif
 
diff --git a/net/socket/udp_socket_win.cc b/net/socket/udp_socket_win.cc
index 945d9e8b..d7c01f7 100644
--- a/net/socket/udp_socket_win.cc
+++ b/net/socket/udp_socket_win.cc
@@ -56,7 +56,7 @@
   void WatchForWrite();
 
   // The UDPSocketWin is going away.
-  void Detach() { socket_ = NULL; }
+  void Detach() { socket_ = nullptr; }
 
   // The separate OVERLAPPED variables for asynchronous operation.
   OVERLAPPED read_overlapped_;
@@ -260,7 +260,6 @@
       recv_from_address_(nullptr),
       net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::UDP_SOCKET)),
       qos_handle_(nullptr),
-      qos_flow_id_(0),
       event_pending_(this) {
   EnsureWinsockInit();
   net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE,
@@ -296,12 +295,15 @@
   if (socket_ == INVALID_SOCKET)
     return;
 
-  if (qos_handle_)
-    QwaveAPI::Get().CloseHandle(qos_handle_);
+  if (qos_handle_) {
+    GetQwaveAPI().CloseHandle(qos_handle_);
+    dscp_manager_ = nullptr;
+    qos_handle_ = NULL;
+  }
 
   // Zero out any pending read/write callback state.
   read_callback_.Reset();
-  recv_from_address_ = NULL;
+  recv_from_address_ = nullptr;
   write_callback_.Reset();
 
   base::TimeTicks start_time = base::TimeTicks::Now();
@@ -325,7 +327,7 @@
 
   if (core_) {
     core_->Detach();
-    core_ = NULL;
+    core_ = nullptr;
   }
 }
 
@@ -378,7 +380,7 @@
 int UDPSocketWin::Read(IOBuffer* buf,
                        int buf_len,
                        CompletionOnceCallback callback) {
-  return RecvFrom(buf, buf_len, NULL, std::move(callback));
+  return RecvFrom(buf, buf_len, nullptr, std::move(callback));
 }
 
 int UDPSocketWin::RecvFrom(IOBuffer* buf,
@@ -415,6 +417,13 @@
                          int buf_len,
                          const IPEndPoint& address,
                          CompletionOnceCallback callback) {
+  if (dscp_manager_) {
+    // Alert DscpManager in case this is a new remote address.  Failure to
+    // apply Dscp code is never fatal.
+    int rv = dscp_manager_->PrepareForSend(address);
+    if (rv != OK)
+      net_log_.AddEventWithNetErrorCode(NetLogEventType::UDP_SEND_ERROR, rv);
+  }
   return SendToOrWrite(buf, buf_len, &address, std::move(callback));
 }
 
@@ -481,6 +490,10 @@
     return MapSystemError(WSAGetLastError());
 
   remote_address_.reset(new IPEndPoint(address));
+
+  if (dscp_manager_)
+    dscp_manager_->PrepareForSend(*remote_address_.get());
+
   return rv;
 }
 
@@ -611,7 +624,7 @@
   int result = ok ? num_bytes : MapSystemError(WSAGetLastError());
   // Convert address.
   IPEndPoint address;
-  IPEndPoint* address_to_log = NULL;
+  IPEndPoint* address_to_log = nullptr;
   if (result >= 0) {
     if (address.FromSockAddr(core_->recv_addr_storage_.addr,
                              core_->recv_addr_storage_.addr_len)) {
@@ -623,8 +636,8 @@
     }
   }
   LogRead(result, core_->read_iobuffer_->data(), address_to_log);
-  core_->read_iobuffer_ = NULL;
-  recv_from_address_ = NULL;
+  core_->read_iobuffer_ = nullptr;
+  recv_from_address_ = nullptr;
   DoReadCallback(result);
 }
 
@@ -637,7 +650,7 @@
   LogWrite(result, core_->write_iobuffer_->data(), send_to_address_.get());
 
   send_to_address_.reset();
-  core_->write_iobuffer_ = NULL;
+  core_->write_iobuffer_ = nullptr;
   DoWriteCallback(result);
 }
 
@@ -691,9 +704,9 @@
                                        recv_from_address_);
   if (rv == ERR_IO_PENDING)
     return;
-  read_iobuffer_ = NULL;
+  read_iobuffer_ = nullptr;
   read_iobuffer_len_ = 0;
-  recv_from_address_ = NULL;
+  recv_from_address_ = nullptr;
   DoReadCallback(rv);
 }
 
@@ -702,7 +715,7 @@
                                      send_to_address_.get());
   if (rv == ERR_IO_PENDING)
     return;
-  write_iobuffer_ = NULL;
+  write_iobuffer_ = nullptr;
   write_iobuffer_len_ = 0;
   send_to_address_.reset();
   DoWriteCallback(rv);
@@ -767,13 +780,13 @@
   CHECK_NE(INVALID_SOCKET, socket_);
   AssertEventNotSignaled(core_->read_overlapped_.hEvent);
   int rv = WSARecvFrom(socket_, &read_buffer, 1, &num, &flags, storage.addr,
-                       &storage.addr_len, &core_->read_overlapped_, NULL);
+                       &storage.addr_len, &core_->read_overlapped_, nullptr);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
       int result = num;
       // Convert address.
       IPEndPoint address_storage;
-      IPEndPoint* address_to_log = NULL;
+      IPEndPoint* address_to_log = nullptr;
       if (result >= 0) {
         if (address_storage.FromSockAddr(core_->recv_addr_storage_.addr,
                                          core_->recv_addr_storage_.addr_len)) {
@@ -791,7 +804,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogRead(result, NULL, NULL);
+      LogRead(result, nullptr, nullptr);
       return result;
     }
   }
@@ -808,12 +821,12 @@
   struct sockaddr* addr = storage.addr;
   // Convert address.
   if (!address) {
-    addr = NULL;
+    addr = nullptr;
     storage.addr_len = 0;
   } else {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   }
@@ -825,8 +838,8 @@
   DWORD flags = 0;
   DWORD num;
   AssertEventNotSignaled(core_->write_overlapped_.hEvent);
-  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags,
-                     addr, storage.addr_len, &core_->write_overlapped_, NULL);
+  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags, addr,
+                     storage.addr_len, &core_->write_overlapped_, nullptr);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
       int result = num;
@@ -837,7 +850,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   }
@@ -866,11 +879,11 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogRead(rv, NULL, NULL);
+    LogRead(rv, nullptr, nullptr);
     return rv;
   }
   IPEndPoint address_storage;
-  IPEndPoint* address_to_log = NULL;
+  IPEndPoint* address_to_log = nullptr;
   if (rv >= 0) {
     if (address_storage.FromSockAddr(storage.addr, storage.addr_len)) {
       if (address)
@@ -894,11 +907,11 @@
   if (address) {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   } else {
-    addr = NULL;
+    addr = nullptr;
     storage.addr_len = 0;
   }
 
@@ -912,7 +925,7 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogWrite(rv, NULL, NULL);
+    LogWrite(rv, nullptr, nullptr);
     return rv;
   }
   LogWrite(rv, buf->data(), address);
@@ -1002,6 +1015,10 @@
   return DoBind(IPEndPoint(address, 0));
 }
 
+QwaveAPI& UDPSocketWin::GetQwaveAPI() {
+  return QwaveAPI::Get();
+}
+
 int UDPSocketWin::JoinGroup(const IPAddress& group_address) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!is_connected())
@@ -1111,28 +1128,7 @@
   return OK;
 }
 
-int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
-  if (dscp == DSCP_NO_CHANGE) {
-    return OK;
-  }
-
-  if (!is_connected())
-    return ERR_SOCKET_NOT_CONNECTED;
-
-  QwaveAPI& qos(QwaveAPI::Get());
-
-  if (!qos.qwave_supported())
-    return ERROR_NOT_SUPPORTED;
-
-  if (qos_handle_ == NULL) {
-    QOS_VERSION version;
-    version.MajorVersion = 1;
-    version.MinorVersion = 0;
-    qos.CreateHandle(&version, &qos_handle_);
-    if (qos_handle_ == NULL)
-      return ERROR_NOT_SUPPORTED;
-  }
-
+QOS_TRAFFIC_TYPE DscpToTrafficType(DiffServCodePoint dscp) {
   QOS_TRAFFIC_TYPE traffic_type = QOSTrafficTypeBestEffort;
   switch (dscp) {
     case DSCP_CS0:
@@ -1172,34 +1168,36 @@
       NOTREACHED();
       break;
   }
-  if (qos_flow_id_ != 0) {
-    qos.RemoveSocketFromFlow(qos_handle_, NULL, qos_flow_id_, 0);
-    qos_flow_id_ = 0;
+  return traffic_type;
+}
+
+int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
+  if (dscp == DSCP_NO_CHANGE)
+    return OK;
+
+  if (!is_connected())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  QwaveAPI& qos(GetQwaveAPI());
+
+  if (!qos.qwave_supported())
+    return ERR_NOT_IMPLEMENTED;
+
+  if (!qos_handle_) {
+    QOS_VERSION version;
+    version.MajorVersion = 1;
+    version.MinorVersion = 0;
+    qos.CreateHandle(&version, &qos_handle_);
+    if (!qos_handle_)
+      return ERR_NOT_IMPLEMENTED;
   }
-  if (!qos.AddSocketToFlow(qos_handle_,
-                           socket_,
-                           NULL,
-                           traffic_type,
-                           QOS_NON_ADAPTIVE_FLOW,
-                           &qos_flow_id_)) {
-    DWORD err = GetLastError();
-    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
-      qos.CloseHandle(qos_handle_);
-      qos_flow_id_ = 0;
-      qos_handle_ = 0;
-    }
-    return MapSystemError(err);
-  }
-  // This requires admin rights, and may fail, if so we ignore it
-  // as AddSocketToFlow should still do *approximately* the right thing.
-  DWORD buf = dscp;
-  qos.SetFlow(qos_handle_,
-              qos_flow_id_,
-              QOSSetOutgoingDSCPValue,
-              sizeof(buf),
-              &buf,
-              0,
-              NULL);
+
+  if (!dscp_manager_)
+    dscp_manager_ = std::make_unique<DscpManager>(qos, socket_, qos_handle_);
+
+  dscp_manager_->Set(dscp);
+  if (remote_address_)
+    return dscp_manager_->PrepareForSend(*remote_address_.get());
 
   return OK;
 }
@@ -1250,5 +1248,70 @@
   NOTIMPLEMENTED();
   return result;
 }
+DscpManager::DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle)
+    : qos_(qos), socket_(socket), qos_handle_(qos_handle) {}
+
+DscpManager::~DscpManager() {
+  if (flow_id_ != 0)
+    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
+}
+
+void DscpManager::Set(DiffServCodePoint dscp) {
+  if (dscp == DSCP_NO_CHANGE || dscp == dscp_value_)
+    return;
+
+  dscp_value_ = dscp;
+  // TODO(zstein): We could reuse the flow when the value changes
+  // by calling QOSSetFlow with the new traffic type and dscp value.
+  if (flow_id_ != 0) {
+    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
+    configured_.clear();
+    flow_id_ = 0;
+  }
+}
+
+int DscpManager::PrepareForSend(const IPEndPoint& remote_address) {
+  if (dscp_value_ == DSCP_NO_CHANGE) {
+    // No DSCP value has been set.
+    return OK;
+  }
+
+  if (configured_.find(remote_address) != configured_.end())
+    return OK;
+
+  SockaddrStorage storage;
+  if (!remote_address.ToSockAddr(storage.addr, &storage.addr_len))
+    return ERR_ADDRESS_INVALID;
+
+  // We won't try again if we get an error.
+  configured_.emplace(remote_address);
+
+  // We don't need to call SetFlow if we already have a qos flow.
+  bool new_flow = flow_id_ == 0;
+
+  const QOS_TRAFFIC_TYPE traffic_type = DscpToTrafficType(dscp_value_);
+
+  if (!qos_.AddSocketToFlow(qos_handle_, socket_, storage.addr, traffic_type,
+                            QOS_NON_ADAPTIVE_FLOW, &flow_id_)) {
+    DWORD err = GetLastError();
+    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
+      qos_.CloseHandle(qos_handle_);
+      flow_id_ = 0;
+      qos_handle_ = 0;
+      dscp_value_ = DSCP_NO_CHANGE;
+    }
+    return MapSystemError(err);
+  }
+
+  if (new_flow) {
+    DWORD buf = dscp_value_;
+    // This requires admin rights, and may fail, if so we ignore it
+    // as AddSocketToFlow should still do *approximately* the right thing.
+    qos_.SetFlow(qos_handle_, flow_id_, QOSSetOutgoingDSCPValue, sizeof(buf),
+                 &buf, 0, nullptr);
+  }
+
+  return OK;
+}
 
 }  // namespace net
diff --git a/net/socket/udp_socket_win.h b/net/socket/udp_socket_win.h
index 81cd245f..8240fc86 100644
--- a/net/socket/udp_socket_win.h
+++ b/net/socket/udp_socket_win.h
@@ -37,6 +37,110 @@
 struct NetLogSource;
 class SocketTag;
 
+// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
+// library for setting packet priorities (and other things). Unfortunately,
+// Microsoft has decided that setting the DSCP bits with setsockopt() no
+// longer works, so we have to use this API instead.
+// This class is meant to be used as a singleton. It exposes a few dynamically
+// loaded functions and a bool called "qwave_supported".
+class NET_EXPORT QwaveAPI {
+  typedef BOOL(WINAPI* CreateHandleFn)(PQOS_VERSION, PHANDLE);
+  typedef BOOL(WINAPI* CloseHandleFn)(HANDLE);
+  typedef BOOL(WINAPI* AddSocketToFlowFn)(HANDLE,
+                                          SOCKET,
+                                          PSOCKADDR,
+                                          QOS_TRAFFIC_TYPE,
+                                          DWORD,
+                                          PQOS_FLOWID);
+  typedef BOOL(WINAPI* RemoveSocketFromFlowFn)(HANDLE,
+                                               SOCKET,
+                                               QOS_FLOWID,
+                                               DWORD);
+  typedef BOOL(WINAPI* SetFlowFn)(HANDLE,
+                                  QOS_FLOWID,
+                                  QOS_SET_FLOW,
+                                  ULONG,
+                                  PVOID,
+                                  DWORD,
+                                  LPOVERLAPPED);
+
+ public:
+  QwaveAPI();
+  virtual ~QwaveAPI() = default;
+
+  static QwaveAPI& Get();
+
+  virtual bool qwave_supported() const;
+  virtual BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
+  virtual BOOL CloseHandle(HANDLE handle);
+  virtual BOOL AddSocketToFlow(HANDLE handle,
+                               SOCKET socket,
+                               PSOCKADDR addr,
+                               QOS_TRAFFIC_TYPE traffic_type,
+                               DWORD flags,
+                               PQOS_FLOWID flow_id);
+  virtual BOOL RemoveSocketFromFlow(HANDLE handle,
+                                    SOCKET socket,
+                                    QOS_FLOWID flow_id,
+                                    DWORD reserved);
+  virtual BOOL SetFlow(HANDLE handle,
+                       QOS_FLOWID flow_id,
+                       QOS_SET_FLOW op,
+                       ULONG size,
+                       PVOID data,
+                       DWORD reserved,
+                       LPOVERLAPPED overlapped);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
+
+  bool qwave_supported_;
+  CreateHandleFn create_handle_func_;
+  CloseHandleFn close_handle_func_;
+  AddSocketToFlowFn add_socket_to_flow_func_;
+  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
+  SetFlowFn set_flow_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
+};
+
+//-----------------------------------------------------------------------------
+
+// Helper for maintaining the state that (unlike a blanket socket option), DSCP
+// values are set per-remote endpoint instead of just per-socket on Windows.
+// The implementation creates a single QWAVE 'flow' for the socket, and adds
+// all encountered remote addresses to that flow.  Flows are the minimum
+// manageable unit within the QWAVE API.  See
+// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/qos2/
+// for Microsoft's documentation.
+class NET_EXPORT DscpManager {
+ public:
+  DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle);
+  ~DscpManager();
+
+  // Remembers the latest |dscp| so PrepareToSend can add remote addresses to
+  // the qos flow. Destroys the old flow if it exists and |dscp| changes.
+  void Set(DiffServCodePoint dscp);
+
+  // Constructs a qos flow for the latest set DSCP value if we don't already
+  // have one. Adds |remote_address| to the qos flow if it hasn't been added
+  // already. Does nothing if no DSCP value has been Set.
+  int PrepareForSend(const IPEndPoint& remote_address);
+
+ private:
+  QwaveAPI& qos_;
+  SOCKET socket_;
+  HANDLE qos_handle_;
+
+  DiffServCodePoint dscp_value_ = DSCP_NO_CHANGE;
+  // The remote addresses currently in the flow.
+  std::set<IPEndPoint> configured_;
+  // 0 means no flow has been constructed.
+  QOS_FLOWID flow_id_ = 0;
+};
+
+//-----------------------------------------------------------------------------
+
 class NET_EXPORT UDPSocketWin : public base::win::ObjectWatcher::Delegate {
  public:
   UDPSocketWin(DatagramSocket::BindType bind_type,
@@ -285,6 +389,11 @@
   // Binds to a random port on |address|.
   int RandomBind(const IPAddress& address);
 
+  // This is provided to allow QwaveAPI mocking in tests. |UDPSocketWin| method
+  // implementations should call |GetQwaveAPI()| instead of |QwaveAPI::Get()|
+  // directly.
+  virtual QwaveAPI& GetQwaveAPI();
+
   SOCKET socket_;
   int addr_family_;
   bool is_connected_;
@@ -346,7 +455,9 @@
 
   // QWAVE data. Used to set DSCP bits on outgoing packets.
   HANDLE qos_handle_;
-  QOS_FLOWID qos_flow_id_;
+
+  // Maintains remote addresses for QWAVE qos management.
+  std::unique_ptr<DscpManager> dscp_manager_;
 
   THREAD_CHECKER(thread_checker_);
 
@@ -359,60 +470,6 @@
 
 //-----------------------------------------------------------------------------
 
-// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
-// library for setting packet priorities (and other things). Unfortunately,
-// Microsoft has decided that setting the DSCP bits with setsockopt() no
-// longer works, so we have to use this API instead.
-// This class is meant to be used as a singleton. It exposes a few dynamically
-// loaded functions and a bool called "qwave_supported".
-class NET_EXPORT QwaveAPI {
-  typedef BOOL (WINAPI *CreateHandleFn)(PQOS_VERSION, PHANDLE);
-  typedef BOOL (WINAPI *CloseHandleFn)(HANDLE);
-  typedef BOOL (WINAPI *AddSocketToFlowFn)(
-      HANDLE, SOCKET, PSOCKADDR, QOS_TRAFFIC_TYPE, DWORD, PQOS_FLOWID);
-  typedef BOOL (WINAPI *RemoveSocketFromFlowFn)(
-      HANDLE, SOCKET, QOS_FLOWID, DWORD);
-  typedef BOOL (WINAPI *SetFlowFn)(
-      HANDLE, QOS_FLOWID, QOS_SET_FLOW, ULONG, PVOID, DWORD, LPOVERLAPPED);
-
- public:
-  QwaveAPI();
-
-  static QwaveAPI& Get();
-
-  bool qwave_supported() const;
-  BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
-  BOOL CloseHandle(HANDLE handle);
-  BOOL AddSocketToFlow(HANDLE handle,
-                       SOCKET socket,
-                       PSOCKADDR addr,
-                       QOS_TRAFFIC_TYPE traffic_type,
-                       DWORD flags,
-                       PQOS_FLOWID flow_id);
-  BOOL RemoveSocketFromFlow(HANDLE handle,
-                            SOCKET socket,
-                            QOS_FLOWID flow_id,
-                            DWORD reserved);
-  BOOL SetFlow(HANDLE handle,
-               QOS_FLOWID flow_id,
-               QOS_SET_FLOW op,
-               ULONG size,
-               PVOID data,
-               DWORD reserved,
-               LPOVERLAPPED overlapped);
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
-
-  bool qwave_supported_;
-  CreateHandleFn create_handle_func_;
-  CloseHandleFn close_handle_func_;
-  AddSocketToFlowFn add_socket_to_flow_func_;
-  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
-  SetFlowFn set_flow_func_;
-
-  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
-};
 
 
 }  // namespace net
diff --git a/ppapi/api/OWNERS b/ppapi/api/OWNERS
deleted file mode 100644
index df227dd98..0000000
--- a/ppapi/api/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# Changing the PPAPI requires sending an explicit design doc discussing
-# the features missing from the open web platform, and the transition plan.
-set noparent
-
-file://ENG_REVIEW_OWNERS
-
-# COMPONENT: Internals>Plugins>Pepper
diff --git a/ppapi/shared_impl/ppb_tcp_socket_shared.cc b/ppapi/shared_impl/ppb_tcp_socket_shared.cc
index 94fb45e..d4588cd 100644
--- a/ppapi/shared_impl/ppb_tcp_socket_shared.cc
+++ b/ppapi/shared_impl/ppb_tcp_socket_shared.cc
@@ -36,8 +36,7 @@
       state_ = success ? SSL_CONNECTED : CLOSED;
       break;
     case LISTEN:
-      if (success)
-        state_ = LISTENING;
+      state_ = success ? LISTENING : CLOSED;
       break;
     case CLOSE:
       state_ = CLOSED;
diff --git a/ppapi/tests/test_tcp_server_socket_private.cc b/ppapi/tests/test_tcp_server_socket_private.cc
index 64b5402..7d4e7dd 100644
--- a/ppapi/tests/test_tcp_server_socket_private.cc
+++ b/ppapi/tests/test_tcp_server_socket_private.cc
@@ -56,6 +56,9 @@
 void TestTCPServerSocketPrivate::RunTests(const std::string& filter) {
   RUN_CALLBACK_TEST(TestTCPServerSocketPrivate, Listen, filter);
   RUN_CALLBACK_TEST(TestTCPServerSocketPrivate, Backlog, filter);
+
+  RUN_CALLBACK_TEST(TestTCPServerSocketPrivate, ListenFails, filter);
+  RUN_CALLBACK_TEST(TestTCPServerSocketPrivate, AcceptFails, filter);
 }
 
 std::string TestTCPServerSocketPrivate::GetLocalAddress(
@@ -122,6 +125,30 @@
   } while (!error_message.empty());
 }
 
+std::string TestTCPServerSocketPrivate::SyncListenFails(
+    pp::TCPServerSocketPrivate* socket) {
+  uint8_t localhost_ip[4] = {127, 0, 0, 1};
+  PP_NetAddress_Private ipv4;
+  ASSERT_TRUE(
+      NetAddressPrivate::CreateFromIPv4Address(localhost_ip, 80, &ipv4));
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  callback.WaitForResult(
+      socket->Listen(&ipv4, 2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+  PASS();
+}
+
+std::string TestTCPServerSocketPrivate::SyncAcceptFails(
+    pp::TCPServerSocketPrivate* socket) {
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  PP_Resource resource;
+  callback.WaitForResult(socket->Accept(&resource, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+  PASS();
+}
+
 std::string TestTCPServerSocketPrivate::SyncListen(
     TCPServerSocketPrivate* socket,
     PP_NetAddress_Private* address,
@@ -262,3 +289,39 @@
   server_socket.StopListening();
   PASS();
 }
+
+std::string TestTCPServerSocketPrivate::TestListenFails() {
+  TCPServerSocketPrivate socket(instance_);
+  ASSERT_SUBTEST_SUCCESS(SyncListenFails(&socket));
+
+  // After a listen failure, accept should fail, too.
+  ASSERT_SUBTEST_SUCCESS(SyncAcceptFails(&socket));
+
+  // Listening again fails, just because of the test fixture simulating another
+  // failure.
+  ASSERT_SUBTEST_SUCCESS(SyncListenFails(&socket));
+
+  PASS();
+}
+
+std::string TestTCPServerSocketPrivate::TestAcceptFails() {
+  TCPServerSocketPrivate socket(instance_);
+  uint8_t localhost_ip[4] = {127, 0, 0, 1};
+  PP_NetAddress_Private ipv4;
+  ASSERT_TRUE(
+      NetAddressPrivate::CreateFromIPv4Address(localhost_ip, 80, &ipv4));
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  callback.WaitForResult(
+      socket.Listen(&ipv4, 2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  // Accept calls should fail.
+  ASSERT_SUBTEST_SUCCESS(SyncAcceptFails(&socket));
+  ASSERT_SUBTEST_SUCCESS(SyncAcceptFails(&socket));
+
+  // Listening again fails, since the socket is already listening.
+  ASSERT_SUBTEST_SUCCESS(SyncListenFails(&socket));
+
+  PASS();
+}
diff --git a/ppapi/tests/test_tcp_server_socket_private.h b/ppapi/tests/test_tcp_server_socket_private.h
index e54c788..b371389 100644
--- a/ppapi/tests/test_tcp_server_socket_private.h
+++ b/ppapi/tests/test_tcp_server_socket_private.h
@@ -46,9 +46,18 @@
                          PP_NetAddress_Private* address,
                          int32_t backlog);
 
+  // Checks that a listen/accept attempt on |socket| fails.
+  std::string SyncListenFails(pp::TCPServerSocketPrivate* socket);
+  std::string SyncAcceptFails(pp::TCPServerSocketPrivate* socket);
+
   std::string TestListen();
   std::string TestBacklog();
 
+  // The higher level test fixture must configure the corresponding events to
+  // fail with PP_ERROR_FAILED.
+  std::string TestListenFails();
+  std::string TestAcceptFails();
+
   std::string host_;
   uint16_t port_;
 };
diff --git a/ppapi/tests/test_tcp_socket.cc b/ppapi/tests/test_tcp_socket.cc
index 620bd4e..cd227677 100644
--- a/ppapi/tests/test_tcp_socket.cc
+++ b/ppapi/tests/test_tcp_socket.cc
@@ -63,6 +63,20 @@
   RUN_CALLBACK_TEST(TestTCPSocket, Backlog, filter);
   RUN_CALLBACK_TEST(TestTCPSocket, Interface_1_0, filter);
   RUN_CALLBACK_TEST(TestTCPSocket, UnexpectedCalls, filter);
+
+  RUN_CALLBACK_TEST(TestTCPSocket, ConnectFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, WriteFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, ReadFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, SetSendBufferSizeFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, SetReceiveBufferSizeFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, SetNoDelayFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, BindFailsConnectSucceeds, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, BindFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, ListenFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, AcceptFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, AcceptedSocketWriteFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, AcceptedSocketReadFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocket, BindConnectFails, filter);
 }
 
 std::string TestTCPSocket::TestConnect() {
@@ -444,14 +458,17 @@
     pp::TCPSocket client_socket(instance_);
     TestCompletionCallback connect_callback(instance_->pp_instance(),
                                             callback_type());
-    client_socket.Connect(server_socket.GetLocalAddress(),
-                          connect_callback.GetCallback());
+    int connect_result = client_socket.Connect(server_socket.GetLocalAddress(),
+                                               connect_callback.GetCallback());
     TestCompletionCallbackWithOutput<pp::TCPSocket> accept_callback(
         instance_->pp_instance(), callback_type());
     accept_callback.WaitForResult(
         server_socket.Accept(accept_callback.GetCallback()));
     CHECK_CALLBACK_BEHAVIOR(accept_callback);
     ASSERT_EQ(PP_OK, accept_callback.result());
+    connect_callback.WaitForResult(connect_result);
+    CHECK_CALLBACK_BEHAVIOR(connect_callback);
+    ASSERT_EQ(PP_OK, connect_callback.result());
     pp::TCPSocket accepted_socket(accept_callback.output());
     ASSERT_SUBTEST_SUCCESS(RunCommandsExpendingFailures(
         &server_socket, kBind | kListen | kConnect | kReadWrite));
@@ -464,6 +481,295 @@
   PASS();
 }
 
+std::string TestTCPSocket::TestConnectFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  // All subsequent calls on the socket should fail.
+  ASSERT_SUBTEST_SUCCESS(RunCommandsExpendingFailures(&socket, kAllCommands));
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestWriteFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  // Write to the socket until there's an error. Some writes may succeed, since
+  // Mojo writes complete before the socket tries to send data. As with the read
+  // case, wait for two errors.
+  char write_data[32 * 1024] = {0};
+  int failures = 0;
+  while (true) {
+    TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+    cb.WaitForResult(socket.Write(write_data,
+                                  static_cast<int32_t>(sizeof(write_data)),
+                                  cb.GetCallback()));
+    CHECK_CALLBACK_BEHAVIOR(cb);
+    if (cb.result() > 0) {
+      ASSERT_EQ(0, failures);
+      continue;
+    }
+
+    ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+    ++failures;
+
+    if (failures == 2)
+      break;
+  }
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestReadFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  std::string read_data;
+  int read_error;
+  // Read should fail with no data received.
+  ASSERT_SUBTEST_SUCCESS(
+      ReadFromSocketUntilError(&socket, &read_data, &read_error));
+  ASSERT_EQ(PP_ERROR_FAILED, read_error);
+  ASSERT_EQ(0, read_data.size());
+
+  // Reading again after the socket has been closed should also fail.
+  ASSERT_SUBTEST_SUCCESS(
+      ReadFromSocketUntilError(&socket, &read_data, &read_error));
+  ASSERT_EQ(PP_ERROR_FAILED, read_error);
+  ASSERT_EQ(0, read_data.size());
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestSetSendBufferSizeFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.SetOption(PP_TCPSOCKET_OPTION_SEND_BUFFER_SIZE, 256,
+                                    cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+  PASS();
+}
+
+std::string TestTCPSocket::TestSetReceiveBufferSizeFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.SetOption(PP_TCPSOCKET_OPTION_RECV_BUFFER_SIZE, 256,
+                                    cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+  PASS();
+}
+
+std::string TestTCPSocket::TestSetNoDelayFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(
+      socket.SetOption(PP_TCPSOCKET_OPTION_NO_DELAY, true, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+  PASS();
+}
+
+std::string TestTCPSocket::TestBindFailsConnectSucceeds() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+
+  callback.WaitForResult(
+      socket.Connect(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestBindFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestListenFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  callback.WaitForResult(
+      socket.Listen(2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+
+  // All subsequent calls on the socket should fail.
+  ASSERT_SUBTEST_SUCCESS(RunCommandsExpendingFailures(&socket, kAllCommands));
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestAcceptFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  callback.WaitForResult(
+      socket.Listen(2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  TestCompletionCallbackWithOutput<pp::TCPSocket> accept_callback(
+      instance_->pp_instance(), callback_type());
+  accept_callback.WaitForResult(socket.Accept(accept_callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(accept_callback);
+  ASSERT_EQ(PP_ERROR_FAILED, accept_callback.result());
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestAcceptedSocketWriteFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  callback.WaitForResult(
+      socket.Listen(2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  TestCompletionCallbackWithOutput<pp::TCPSocket> accept_callback(
+      instance_->pp_instance(), callback_type());
+  accept_callback.WaitForResult(socket.Accept(accept_callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(accept_callback);
+  ASSERT_EQ(PP_OK, accept_callback.result());
+
+  pp::TCPSocket accepted_socket(accept_callback.output());
+
+  // Write to the socket until there's an error. Some writes may succeed, since
+  // Mojo writes complete before the socket tries to send data. As with the read
+  // case, wait for two errors.
+  char write_data[32 * 1024] = {0};
+  int failures = 0;
+  while (true) {
+    callback.WaitForResult(accepted_socket.Write(
+        write_data, static_cast<int32_t>(sizeof(write_data)),
+        callback.GetCallback()));
+    CHECK_CALLBACK_BEHAVIOR(callback);
+    if (callback.result() > 0) {
+      ASSERT_EQ(0, failures);
+      continue;
+    }
+
+    ASSERT_EQ(PP_ERROR_FAILED, callback.result());
+    ++failures;
+
+    if (failures == 2)
+      break;
+  }
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestAcceptedSocketReadFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  // The address doesn't matter here, other than that it should be valid.
+  callback.WaitForResult(
+      socket.Bind(test_server_addr_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  callback.WaitForResult(
+      socket.Listen(2 /* backlog */, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+
+  TestCompletionCallbackWithOutput<pp::TCPSocket> accept_callback(
+      instance_->pp_instance(), callback_type());
+  accept_callback.WaitForResult(socket.Accept(accept_callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(accept_callback);
+  ASSERT_EQ(PP_OK, accept_callback.result());
+
+  pp::TCPSocket accepted_socket(accept_callback.output());
+
+  std::string read_data;
+  int read_error;
+  // Read should fail with no data received.
+  ASSERT_SUBTEST_SUCCESS(
+      ReadFromSocketUntilError(&accepted_socket, &read_data, &read_error));
+  ASSERT_EQ(PP_ERROR_FAILED, read_error);
+  ASSERT_EQ(0, read_data.size());
+
+  // Reading again after the socket has been closed should also fail.
+  ASSERT_SUBTEST_SUCCESS(
+      ReadFromSocketUntilError(&accepted_socket, &read_data, &read_error));
+  ASSERT_EQ(PP_ERROR_FAILED, read_error);
+  ASSERT_EQ(0, read_data.size());
+
+  PASS();
+}
+
+std::string TestTCPSocket::TestBindConnectFails() {
+  pp::TCPSocket socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+
+  cb.WaitForResult(socket.Bind(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.Connect(test_server_addr_, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  // All subsequent calls on the socket should fail.
+  ASSERT_SUBTEST_SUCCESS(RunCommandsExpendingFailures(&socket, kAllCommands));
+
+  PASS();
+}
+
 std::string TestTCPSocket::ReadFirstLineFromSocket(pp::TCPSocket* socket,
                                                    std::string* s) {
   char buffer[1000];
@@ -627,7 +933,8 @@
   if (commands & kBind) {
     TestCompletionCallback cb(instance_->pp_instance(), callback_type());
     pp::NetAddress any_port_address;
-    ASSERT_SUBTEST_SUCCESS(GetAddressToBind(&any_port_address));
+    ASSERT_TRUE(ReplacePort(instance_->pp_instance(), test_server_addr_, 0,
+                            &any_port_address));
     cb.WaitForResult(socket->Bind(any_port_address, cb.GetCallback()));
     CHECK_CALLBACK_BEHAVIOR(cb);
     ASSERT_EQ(PP_ERROR_FAILED, cb.result());
diff --git a/ppapi/tests/test_tcp_socket.h b/ppapi/tests/test_tcp_socket.h
index 80d3742..0a0e206 100644
--- a/ppapi/tests/test_tcp_socket.h
+++ b/ppapi/tests/test_tcp_socket.h
@@ -35,6 +35,26 @@
   std::string TestInterface_1_0();
   std::string TestUnexpectedCalls();
 
+  // The higher level test fixture is responsible for making the appropriate
+  // call in these tests fail with PP_ERROR_FAILED, and all prior events
+  // succeed.
+  std::string TestConnectFails();
+  std::string TestWriteFails();
+  std::string TestReadFails();
+  std::string TestSetSendBufferSizeFails();
+  std::string TestSetReceiveBufferSizeFails();
+  std::string TestSetNoDelayFails();
+  // When a bind call fails, normally the socket is reuseable.
+  std::string TestBindFailsConnectSucceeds();
+  // This is needed in addition to the above test in the case where a bind
+  // failure is simulated in a way that also closes the NetworkContext pipe.
+  std::string TestBindFails();
+  std::string TestListenFails();
+  std::string TestAcceptFails();
+  std::string TestAcceptedSocketWriteFails();
+  std::string TestAcceptedSocketReadFails();
+  std::string TestBindConnectFails();
+
   std::string ReadFirstLineFromSocket(pp::TCPSocket* socket, std::string* s);
   std::string ReadFirstLineFromSocket_1_0(PP_Resource socket,
                                           std::string* s);
@@ -65,6 +85,7 @@
     kAccept = 0x4,
     kConnect = 0x8,
     kReadWrite = 0x10,
+    kAllCommands = -1,
   };
   // Runs |commands|, consisting of one or more Command values on |socket|,
   // expecting all of them to fail with PP_ERROR_FAILED. Useful for testing
diff --git a/ppapi/tests/test_tcp_socket_private.cc b/ppapi/tests/test_tcp_socket_private.cc
index ae7b472..5037d0b 100644
--- a/ppapi/tests/test_tcp_socket_private.cc
+++ b/ppapi/tests/test_tcp_socket_private.cc
@@ -56,6 +56,10 @@
   RUN_CALLBACK_TEST(TestTCPSocketPrivate, ConnectAddress, filter);
   RUN_CALLBACK_TEST(TestTCPSocketPrivate, SetOption, filter);
   RUN_CALLBACK_TEST(TestTCPSocketPrivate, LargeRead, filter);
+
+  RUN_CALLBACK_TEST(TestTCPSocketPrivate, SSLHandshakeFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocketPrivate, SSLWriteFails, filter);
+  RUN_CALLBACK_TEST(TestTCPSocketPrivate, SSLReadFails, filter);
 }
 
 std::string TestTCPSocketPrivate::TestBasic() {
@@ -214,6 +218,81 @@
   PASS();
 }
 
+std::string TestTCPSocketPrivate::TestSSLHandshakeFails() {
+  pp::TCPSocketPrivate socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+
+  cb.WaitForResult(socket.Connect("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.SSLHandshake("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  // Writes and reads should both fail after an SSL handshake fails.
+
+  char byte = 'a';
+  cb.WaitForResult(socket.Write(&byte, 1, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  cb.WaitForResult(socket.Read(&byte, 1, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  PASS();
+}
+
+std::string TestTCPSocketPrivate::TestSSLWriteFails() {
+  pp::TCPSocketPrivate socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+
+  cb.WaitForResult(socket.Connect("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.SSLHandshake("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  // Write to the socket until there's an error. Some writes may succeed, since
+  // Mojo writes complete before the socket tries to send data.
+  char write_data[32 * 1024] = {0};
+  while (true) {
+    TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+    cb.WaitForResult(socket.Write(write_data,
+                                  static_cast<int32_t>(sizeof(write_data)),
+                                  cb.GetCallback()));
+    CHECK_CALLBACK_BEHAVIOR(cb);
+    if (cb.result() > 0)
+      continue;
+
+    ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+    PASS();
+  }
+}
+
+std::string TestTCPSocketPrivate::TestSSLReadFails() {
+  pp::TCPSocketPrivate socket(instance_);
+  TestCompletionCallback cb(instance_->pp_instance(), callback_type());
+
+  cb.WaitForResult(socket.Connect("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  cb.WaitForResult(socket.SSLHandshake("foo.test", 443, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_OK, cb.result());
+
+  char byte;
+  cb.WaitForResult(socket.Read(&byte, 1, cb.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(cb);
+  ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+
+  PASS();
+}
+
 int32_t TestTCPSocketPrivate::ReadFirstLineFromSocket(
     pp::TCPSocketPrivate* socket,
     std::string* s) {
diff --git a/ppapi/tests/test_tcp_socket_private.h b/ppapi/tests/test_tcp_socket_private.h
index 4396cfc..13fa1cf 100644
--- a/ppapi/tests/test_tcp_socket_private.h
+++ b/ppapi/tests/test_tcp_socket_private.h
@@ -30,6 +30,13 @@
   std::string TestSetOption();
   std::string TestLargeRead();
 
+  // The higher level test fixture is responsible for making the appropriate
+  // call in these tests fail with PP_ERROR_FAILED, and all prior events
+  // succeed.
+  std::string TestSSLHandshakeFails();
+  std::string TestSSLWriteFails();
+  std::string TestSSLReadFails();
+
   int32_t ReadFirstLineFromSocket(pp::TCPSocketPrivate* socket, std::string* s);
   int32_t WriteStringToSocket(pp::TCPSocketPrivate* socket,
                               const std::string& s);
diff --git a/remoting/host/sas_injector_win.cc b/remoting/host/sas_injector_win.cc
index 70fa7e9d..d4b7182b 100644
--- a/remoting/host/sas_injector_win.cc
+++ b/remoting/host/sas_injector_win.cc
@@ -7,9 +7,11 @@
 #include <windows.h>
 #include <sas.h>
 
+#include <memory>
+#include <utility>
+
 #include "base/logging.h"
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/win/registry.h"
 
 namespace remoting {
@@ -38,14 +40,12 @@
   base::win::RegKey system_policy_;
 
   // True if the policy needs to be restored.
-  bool restore_policy_;
+  bool restore_policy_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedSoftwareSasPolicy);
 };
 
-ScopedSoftwareSasPolicy::ScopedSoftwareSasPolicy()
-    : restore_policy_(false) {
-}
+ScopedSoftwareSasPolicy::ScopedSoftwareSasPolicy() = default;
 
 ScopedSoftwareSasPolicy::~ScopedSoftwareSasPolicy() {
   // Restore the default policy by deleting the value that we have set.
@@ -103,10 +103,9 @@
   DISALLOW_COPY_AND_ASSIGN(SasInjectorWin);
 };
 
-SasInjectorWin::SasInjectorWin() {}
+SasInjectorWin::SasInjectorWin() = default;
 
-SasInjectorWin::~SasInjectorWin() {
-}
+SasInjectorWin::~SasInjectorWin() = default;
 
 bool SasInjectorWin::InjectSas() {
   // Enable software SAS generation by services and send SAS. SAS can still fail
@@ -121,7 +120,7 @@
 }
 
 std::unique_ptr<SasInjector> SasInjector::Create() {
-  return base::WrapUnique(new SasInjectorWin());
+  return std::make_unique<SasInjectorWin>();
 }
 
 } // namespace remoting
diff --git a/remoting/host/win/session_input_injector.cc b/remoting/host/win/session_input_injector.cc
index 7b61dc5..a52725e 100644
--- a/remoting/host/win/session_input_injector.cc
+++ b/remoting/host/win/session_input_injector.cc
@@ -94,9 +94,6 @@
   // Used to lock the current session on non-home SKUs of Windows.
   base::Closure lock_workstation_;
 
-  // Used to inject Secure Attention Sequence on XP.
-  std::unique_ptr<SasInjector> sas_injector_;
-
   // Keys currently pressed by the client, used to detect key sequences.
   std::set<ui::DomCode> pressed_keys_;
 
diff --git a/services/device/bluetooth/bluetooth_system.cc b/services/device/bluetooth/bluetooth_system.cc
index 4153f35..d9583e2 100644
--- a/services/device/bluetooth/bluetooth_system.cc
+++ b/services/device/bluetooth/bluetooth_system.cc
@@ -24,27 +24,48 @@
 BluetoothSystem::BluetoothSystem(mojom::BluetoothSystemClientPtr client) {
   client_ptr_ = std::move(client);
   GetBluetoothAdapterClient()->AddObserver(this);
-  UpdateActiveAdapter();
+
+  std::vector<dbus::ObjectPath> object_paths =
+      GetBluetoothAdapterClient()->GetAdapters();
+  if (!object_paths.empty())
+    active_adapter_ = object_paths[0];
 }
 
 BluetoothSystem::~BluetoothSystem() = default;
 
 void BluetoothSystem::AdapterAdded(const dbus::ObjectPath& object_path) {
-  UpdateActiveAdapter();
+  if (!active_adapter_)
+    active_adapter_ = object_path;
 }
 
 void BluetoothSystem::AdapterRemoved(const dbus::ObjectPath& object_path) {
-  UpdateActiveAdapter();
+  DCHECK(active_adapter_);
+
+  if (active_adapter_.value() != object_path)
+    return;
+
+  active_adapter_ = base::nullopt;
+
+  std::vector<dbus::ObjectPath> object_paths =
+      GetBluetoothAdapterClient()->GetAdapters();
+  for (const auto& new_object_path : object_paths) {
+    // The removed adapter is still included in GetAdapters().
+    if (new_object_path == object_path)
+      continue;
+
+    active_adapter_ = new_object_path;
+    break;
+  }
 }
 
 void BluetoothSystem::GetState(GetStateCallback callback) {
-  if (active_adapter_.value().empty()) {
+  if (!active_adapter_) {
     std::move(callback).Run(State::kUnavailable);
     return;
   }
 
   auto* properties =
-      GetBluetoothAdapterClient()->GetProperties(active_adapter_);
+      GetBluetoothAdapterClient()->GetProperties(active_adapter_.value());
   std::move(callback).Run(properties->powered.value() ? State::kPoweredOn
                                                       : State::kPoweredOff);
 }
@@ -55,18 +76,4 @@
   return bluez::BluezDBusManager::Get()->GetAlternateBluetoothAdapterClient();
 }
 
-void BluetoothSystem::UpdateActiveAdapter() {
-  std::vector<dbus::ObjectPath> object_paths =
-      GetBluetoothAdapterClient()->GetAdapters();
-  if (object_paths.empty()) {
-    active_adapter_ = dbus::ObjectPath("");
-    return;
-  }
-
-  if (base::ContainsValue(object_paths, active_adapter_))
-    return;
-
-  active_adapter_ = object_paths[0];
-}
-
 }  // namespace device
diff --git a/services/device/bluetooth/bluetooth_system.h b/services/device/bluetooth/bluetooth_system.h
index 21d033d..b44d76f 100644
--- a/services/device/bluetooth/bluetooth_system.h
+++ b/services/device/bluetooth/bluetooth_system.h
@@ -6,6 +6,7 @@
 #define SERVICES_DEVICE_BLUETOOTH_BLUETOOTH_SYSTEM_H_
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "dbus/object_path.h"
 #include "device/bluetooth/dbus/bluetooth_adapter_client.h"
 #include "services/device/public/mojom/bluetooth_system.mojom.h"
@@ -35,11 +36,11 @@
  private:
   bluez::BluetoothAdapterClient* GetBluetoothAdapterClient();
 
-  void UpdateActiveAdapter();
-
   mojom::BluetoothSystemClientPtr client_ptr_;
 
-  dbus::ObjectPath active_adapter_;
+  // The ObjectPath of the adapter being used. Updated as BT adapters are
+  // added and removed. nullopt if there is no adapter.
+  base::Optional<dbus::ObjectPath> active_adapter_;
 
   DISALLOW_COPY_AND_ASSIGN(BluetoothSystem);
 };
diff --git a/services/device/bluetooth/bluetooth_system_unittest.cc b/services/device/bluetooth/bluetooth_system_unittest.cc
index db393e37..e4b54d11 100644
--- a/services/device/bluetooth/bluetooth_system_unittest.cc
+++ b/services/device/bluetooth/bluetooth_system_unittest.cc
@@ -85,11 +85,14 @@
   // Simulates the adapter at |object_path_str| being removed.
   void SimulateAdapterRemoved(const std::string& object_path_str) {
     dbus::ObjectPath object_path(object_path_str);
-    size_t removed = adapter_object_paths_to_properties_.erase(object_path);
-    DCHECK_EQ(1u, removed);
 
+    // When BlueZ calls into AdapterRemoved, the adapter is still exposed
+    // through GetAdapters() and its properties are still accessible.
     for (auto& observer : observers_)
       observer.AdapterRemoved(object_path);
+
+    size_t removed = adapter_object_paths_to_properties_.erase(object_path);
+    DCHECK_EQ(1u, removed);
   }
 
   // Simulates adapter at |object_path_str| changing its powered state to
diff --git a/services/media_session/audio_focus_manager.cc b/services/media_session/audio_focus_manager.cc
index b0e72d2..f40dec4 100644
--- a/services/media_session/audio_focus_manager.cc
+++ b/services/media_session/audio_focus_manager.cc
@@ -7,9 +7,9 @@
 #include <iterator>
 #include <utility>
 
-#include "base/atomic_sequence_num.h"
 #include "base/containers/adapters.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/unguessable_token.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/media_session/audio_focus_manager_metrics_helper.h"
 #include "services/media_session/public/cpp/switches.h"
@@ -17,17 +17,6 @@
 
 namespace media_session {
 
-namespace {
-
-// Generate a unique audio focus request ID for the audio focus request. The IDs
-// are only handed out by the audio focus manager.
-int GenerateAudioFocusRequestId() {
-  static base::AtomicSequenceNumber request_id;
-  return request_id.GetNext();
-}
-
-}  // namespace
-
 class AudioFocusManager::StackRow : public mojom::AudioFocusRequestClient {
  public:
   StackRow(AudioFocusManager* owner,
@@ -172,7 +161,7 @@
   RequestAudioFocusInternal(
       std::make_unique<StackRow>(
           this, std::move(request), std::move(media_session),
-          std::move(session_info), type, GenerateAudioFocusRequestId(),
+          std::move(session_info), type, base::UnguessableToken::Create(),
           GetBindingSourceName()),
       type, std::move(callback));
 }
@@ -193,7 +182,7 @@
 }
 
 void AudioFocusManager::GetDebugInfoForRequest(
-    uint64_t request_id,
+    const RequestId& request_id,
     GetDebugInfoForRequestCallback callback) {
   for (auto& row : audio_focus_stack_) {
     if (row->id() != request_id)
diff --git a/services/media_session/audio_focus_manager.h b/services/media_session/audio_focus_manager.h
index 18b8526..cd9f845 100644
--- a/services/media_session/audio_focus_manager.h
+++ b/services/media_session/audio_focus_manager.h
@@ -17,12 +17,17 @@
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
+namespace base {
+class UnguessableToken;
+}  // namespace base
+
 namespace media_session {
 
 class AudioFocusManager : public mojom::AudioFocusManager,
                           public mojom::AudioFocusManagerDebug {
  public:
-  using RequestId = uint64_t;
+  // TODO(beccahughes): Remove this.
+  using RequestId = base::UnguessableToken;
 
   // Returns Chromium's internal AudioFocusManager.
   static AudioFocusManager* GetInstance();
@@ -38,7 +43,7 @@
   void SetSourceName(const std::string& name) override;
 
   // mojom::AudioFocusManagerDebug.
-  void GetDebugInfoForRequest(uint64_t request_id,
+  void GetDebugInfoForRequest(const RequestId& request_id,
                               GetDebugInfoForRequestCallback callback) override;
 
   // Bind to a mojom::AudioFocusManagerRequest.
diff --git a/services/media_session/audio_focus_manager_unittest.cc b/services/media_session/audio_focus_manager_unittest.cc
index 6068ac5..3f441ab0 100644
--- a/services/media_session/audio_focus_manager_unittest.cc
+++ b/services/media_session/audio_focus_manager_unittest.cc
@@ -30,8 +30,6 @@
 
 namespace {
 
-const AudioFocusManager::RequestId kNoFocusedSession = -1;
-
 const char kExampleDebugInfoName[] = "name";
 const char kExampleDebugInfoOwner[] = "owner";
 const char kExampleDebugInfoState[] = "state";
@@ -181,9 +179,9 @@
     for (auto iter = audio_focus_requests.rbegin();
          iter != audio_focus_requests.rend(); ++iter) {
       if ((*iter)->audio_focus_type == mojom::AudioFocusType::kGain)
-        return (*iter)->request_id;
+        return (*iter)->request_id.value();
     }
-    return kNoFocusedSession;
+    return base::UnguessableToken::Null();
   }
 
   int GetTransientCount() {
@@ -357,15 +355,15 @@
   AudioFocusManager::RequestId GetRequestIdForSession(
       MockMediaSession* session) {
     DCHECK(session->HasAudioFocusRequest());
-    AudioFocusManager::RequestId id = kNoFocusedSession;
+    AudioFocusManager::RequestId id = base::UnguessableToken::Null();
 
-    session->audio_focus_request()->GetRequestId(
-        base::BindOnce([](AudioFocusManager::RequestId* id,
-                          uint64_t received_id) { *id = received_id; },
-                       &id));
+    session->audio_focus_request()->GetRequestId(base::BindOnce(
+        [](AudioFocusManager::RequestId* id,
+           const base::UnguessableToken& received_id) { *id = received_id; },
+        &id));
 
     session->FlushForTesting();
-    EXPECT_NE(kNoFocusedSession, id);
+    EXPECT_NE(base::UnguessableToken::Null(), id);
     return id;
   }
 
@@ -413,7 +411,7 @@
   MockMediaSession media_session_2;
   MockMediaSession media_session_3;
 
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
             GetState(&media_session_1));
   EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
@@ -445,7 +443,7 @@
 TEST_P(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
   MockMediaSession media_session;
 
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
 
   AudioFocusManager::RequestId request_id =
       RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
@@ -460,7 +458,7 @@
 
   AudioFocusManager::RequestId request_id =
       RequestAudioFocus(&media_session, mojom::AudioFocusType::kGainTransient);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_EQ(1, GetTransientCount());
 
   RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
@@ -473,7 +471,7 @@
 
   AudioFocusManager::RequestId request_id = RequestAudioFocus(
       &media_session, mojom::AudioFocusType::kGainTransientMayDuck);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_EQ(1, GetTransientMaybeDuckCount());
 
   RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
@@ -491,7 +489,7 @@
   EXPECT_EQ(0, GetTransientCount());
 
   RequestAudioFocus(&media_session, mojom::AudioFocusType::kGainTransient);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_EQ(1, GetTransientCount());
   EXPECT_NE(mojom::MediaSessionInfo::SessionState::kSuspended,
             GetState(&media_session));
@@ -508,7 +506,7 @@
 
   RequestAudioFocus(&media_session,
                     mojom::AudioFocusType::kGainTransientMayDuck);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_EQ(1, GetTransientMaybeDuckCount());
   EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
             GetState(&media_session));
@@ -568,7 +566,7 @@
   EXPECT_EQ(request_id, GetAudioFocusedSession());
 
   AbandonAudioFocus(&media_session);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
 }
 
 TEST_P(AudioFocusManagerTest, AbandonAudioFocus_MultipleCalls) {
@@ -583,7 +581,7 @@
   std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
   AbandonAudioFocus(&media_session);
 
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
   EXPECT_TRUE(observer->focus_lost_session_.is_null());
 }
 
@@ -782,7 +780,7 @@
   MockMediaSession media_session;
   RequestAudioFocus(&media_session,
                     mojom::AudioFocusType::kGainTransientMayDuck);
-  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(base::UnguessableToken::Null(), GetAudioFocusedSession());
 }
 
 TEST_P(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesTransient) {
@@ -912,7 +910,8 @@
 }
 
 TEST_P(AudioFocusManagerTest, GetDebugInfo_BadRequestId) {
-  mojom::MediaSessionDebugInfoPtr debug_info = GetDebugInfo(kNoFocusedSession);
+  mojom::MediaSessionDebugInfoPtr debug_info =
+      GetDebugInfo(base::UnguessableToken::Create());
   EXPECT_TRUE(debug_info->name.empty());
 }
 
diff --git a/services/media_session/public/mojom/audio_focus.mojom b/services/media_session/public/mojom/audio_focus.mojom
index 6f9454b..9ad4b27a 100644
--- a/services/media_session/public/mojom/audio_focus.mojom
+++ b/services/media_session/public/mojom/audio_focus.mojom
@@ -4,9 +4,10 @@
 
 module media_session.mojom;
 
+import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/media_session/public/mojom/media_session.mojom";
 
-// Next MinVersion: 2
+// Next MinVersion: 4
 
 // These are the different types of audio focus that can be requested.
 [Extensible]
@@ -31,8 +32,9 @@
 struct AudioFocusRequestState {
   MediaSessionInfo session_info;
   AudioFocusType audio_focus_type;
-  uint64 request_id;
+
   [MinVersion=2] string? source_name;
+  [MinVersion=3] mojo_base.mojom.UnguessableToken? request_id;
 };
 
 // The observer for audio focus events.
@@ -46,7 +48,8 @@
 };
 
 // Controls audio focus for an associated request.
-// Next Method ID: 4
+// Next Method ID: 5
+// Deprecated method IDs: 3
 interface AudioFocusRequestClient {
   // Requests updated audio focus for this request. If the request was granted
   // then the callback will resolve.
@@ -59,7 +62,8 @@
   MediaSessionInfoChanged@2(MediaSessionInfo session_info);
 
   // Retrieve a unique ID for this request.
-  GetRequestId@3() => (uint64 request_id);
+  [MinVersion=3] GetRequestId@4()
+      => (mojo_base.mojom.UnguessableToken request_id);
 };
 
 // Controls audio focus across the entire system.
@@ -91,6 +95,6 @@
 // Provides debug information about audio focus requests.
 interface AudioFocusManagerDebug {
   // Gets debugging information for a |MediaSession| with |request_id|.
-  GetDebugInfoForRequest(uint64 request_id)
+  GetDebugInfoForRequest(mojo_base.mojom.UnguessableToken request_id)
       => (MediaSessionDebugInfo debug_info);
 };
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index fd192f1..6bb2296 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -67,6 +67,8 @@
     "network_service.h",
     "network_service_network_delegate.cc",
     "network_service_network_delegate.h",
+    "network_service_proxy_delegate.cc",
+    "network_service_proxy_delegate.h",
     "network_usage_accumulator.cc",
     "network_usage_accumulator.h",
     "p2p/socket.cc",
@@ -187,6 +189,7 @@
     "//services/service_manager/sandbox:sandbox",
     "//third_party/webrtc/media:rtc_media_base",
     "//third_party/webrtc/rtc_base",
+    "//third_party/webrtc/rtc_base:timeutils",
     "//third_party/webrtc_overrides",
     "//third_party/webrtc_overrides:init_webrtc",
     "//url",
@@ -245,6 +248,7 @@
     "network_change_manager_unittest.cc",
     "network_context_unittest.cc",
     "network_quality_estimator_manager_unittest.cc",
+    "network_service_proxy_delegate_unittest.cc",
     "network_service_unittest.cc",
     "network_usage_accumulator_unittest.cc",
     "p2p/socket_tcp_server_unittest.cc",
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 56fff6b..642fa57 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -74,6 +74,7 @@
 #include "services/network/mojo_net_log.h"
 #include "services/network/network_service.h"
 #include "services/network/network_service_network_delegate.h"
+#include "services/network/network_service_proxy_delegate.h"
 #include "services/network/p2p/socket_manager.h"
 #include "services/network/proxy_config_service_mojo.h"
 #include "services/network/proxy_lookup_request.h"
@@ -1480,6 +1481,12 @@
       std::make_unique<NetworkServiceNetworkDelegate>(this);
   builder.set_network_delegate(std::move(network_delegate));
 
+  if (params_->custom_proxy_config_client_request) {
+    proxy_delegate_ = std::make_unique<NetworkServiceProxyDelegate>(
+        std::move(params_->custom_proxy_config_client_request));
+    builder.set_shared_proxy_delegate(proxy_delegate_.get());
+  }
+
   // |network_service_| may be nullptr in tests.
   auto result = ApplyContextParamsToBuilder(&builder);
 
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 87ffad9f9..3bcf928 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -61,6 +61,7 @@
 class ExpectCTReporter;
 class HostResolver;
 class NetworkService;
+class NetworkServiceProxyDelegate;
 class P2PSocketManager;
 class ProxyLookupRequest;
 class ResourceScheduler;
@@ -279,6 +280,10 @@
     return proxy_lookup_requests_.size();
   }
 
+  NetworkServiceProxyDelegate* proxy_delegate() const {
+    return proxy_delegate_.get();
+  }
+
  private:
   class ContextNetworkDelegate;
 
@@ -394,6 +399,8 @@
   std::set<std::unique_ptr<HostResolver>, base::UniquePtrComparator>
       host_resolvers_;
 
+  std::unique_ptr<NetworkServiceProxyDelegate> proxy_delegate_;
+
   // Used for Signed Exchange certificate verification.
   int next_cert_verify_id_ = 0;
   struct PendingCertVerify {
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 818a624..dab6b0d6 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
@@ -69,6 +70,7 @@
 #include "net/ssl/channel_id_store.h"
 #include "net/test/cert_test_util.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
 #include "net/test/gtest_util.h"
@@ -108,6 +110,7 @@
 
 const GURL kURL("http://foo.com");
 const GURL kOtherURL("http://other.com");
+constexpr char kMockHost[] = "mock.host";
 
 // Sends an HttpResponse for requests for "/" that result in sending an HPKP
 // report.  Ignores other paths to avoid catching the subsequent favicon
@@ -159,6 +162,27 @@
                                    false)});
 }
 
+std::unique_ptr<TestURLLoaderClient> FetchRequest(
+    const ResourceRequest& request,
+    NetworkContext* network_context) {
+  mojom::URLLoaderFactoryPtr loader_factory;
+  auto params = mojom::URLLoaderFactoryParams::New();
+  params->process_id = mojom::kBrowserProcessId;
+  params->is_corb_enabled = false;
+  network_context->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory),
+                                          std::move(params));
+
+  auto client = std::make_unique<TestURLLoaderClient>();
+  mojom::URLLoaderPtr loader;
+  loader_factory->CreateLoaderAndStart(
+      mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */,
+      0 /* options */, request, client->CreateInterfacePtr(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+
+  client->RunUntilComplete();
+  return client;
+}
+
 // ProxyLookupClient that drives proxy lookups and can wait for the responses to
 // be received.
 class TestProxyLookupClient : public mojom::ProxyLookupClient {
@@ -3826,6 +3850,320 @@
   }
 }
 
+// Custom proxy does not apply to localhost, so resolve kMockHost to localhost,
+// and use that instead.
+class NetworkContextMockHostTest : public NetworkContextTest {
+ public:
+  NetworkContextMockHostTest() {
+    auto host_resolver = std::make_unique<net::MockHostResolver>();
+    host_resolver->rules()->AddRule(kMockHost, "127.0.0.1");
+    network_service_->SetHostResolver(std::move(host_resolver));
+  }
+
+ protected:
+  GURL GetURLWithMockHost(const net::EmbeddedTestServer& server,
+                          const std::string& relative_url) {
+    GURL server_base_url = server.base_url();
+    GURL base_url =
+        GURL(base::StrCat({server_base_url.scheme(), "://", kMockHost, ":",
+                           server_base_url.port()}));
+    EXPECT_TRUE(base_url.is_valid()) << base_url.possibly_invalid_spec();
+    return base_url.Resolve(relative_url);
+  }
+};
+
+TEST_F(NetworkContextMockHostTest, CustomProxyAddsHeaders) {
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  net::EmbeddedTestServer proxy_test_server;
+  net::test_server::RegisterDefaultHandlers(&proxy_test_server);
+  ASSERT_TRUE(proxy_test_server.Start());
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  std::string base_url = proxy_test_server.base_url().spec();
+  // Remove slash from URL.
+  base_url.pop_back();
+  config->rules.ParseFromString("http=" + base_url);
+  config->pre_cache_headers.SetHeader("pre_foo", "pre_foo_value");
+  config->post_cache_headers.SetHeader("post_foo", "post_foo_value");
+  proxy_config_client->OnCustomProxyConfigUpdated(std::move(config));
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.custom_proxy_pre_cache_headers.SetHeader("pre_bar", "pre_bar_value");
+  request.custom_proxy_post_cache_headers.SetHeader("post_bar",
+                                                    "post_bar_value");
+  request.url = GetURLWithMockHost(
+      test_server, "/echoheader?pre_foo&post_foo&pre_bar&post_bar");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"post_bar_value", "post_foo_value",
+                                        "pre_bar_value", "pre_foo_value"},
+                                       "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+}
+
+TEST_F(NetworkContextMockHostTest,
+       CustomProxyRequestHeadersOverrideConfigHeaders) {
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  net::EmbeddedTestServer proxy_test_server;
+  net::test_server::RegisterDefaultHandlers(&proxy_test_server);
+  ASSERT_TRUE(proxy_test_server.Start());
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  std::string base_url = proxy_test_server.base_url().spec();
+  // Remove slash from URL.
+  base_url.pop_back();
+  config->rules.ParseFromString("http=" + base_url);
+  config->pre_cache_headers.SetHeader("foo", "bad");
+  config->post_cache_headers.SetHeader("bar", "bad");
+  proxy_config_client->OnCustomProxyConfigUpdated(std::move(config));
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.custom_proxy_pre_cache_headers.SetHeader("foo", "foo_value");
+  request.custom_proxy_post_cache_headers.SetHeader("bar", "bar_value");
+  request.url = GetURLWithMockHost(test_server, "/echoheader?foo&bar");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+}
+
+TEST_F(NetworkContextMockHostTest, CustomProxyConfigHeadersAddedBeforeCache) {
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  net::EmbeddedTestServer proxy_test_server;
+  net::test_server::RegisterDefaultHandlers(&proxy_test_server);
+  ASSERT_TRUE(proxy_test_server.Start());
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  std::string base_url = proxy_test_server.base_url().spec();
+  // Remove slash from URL.
+  base_url.pop_back();
+  config->rules.ParseFromString("http=" + base_url);
+  config->pre_cache_headers.SetHeader("foo", "foo_value");
+  config->post_cache_headers.SetHeader("bar", "bar_value");
+  proxy_config_client->OnCustomProxyConfigUpdated(config->Clone());
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.url = GetURLWithMockHost(test_server, "/echoheadercache?foo&bar");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(client->response_head().was_fetched_via_cache);
+
+  // post_cache_headers should not break caching.
+  config->post_cache_headers.SetHeader("bar", "new_bar");
+  proxy_config_client->OnCustomProxyConfigUpdated(config->Clone());
+  scoped_task_environment_.RunUntilIdle();
+
+  client = FetchRequest(request, network_context.get());
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n"));
+  EXPECT_TRUE(client->response_head().was_fetched_via_cache);
+
+  // pre_cache_headers should invalidate cache.
+  config->pre_cache_headers.SetHeader("foo", "new_foo");
+  proxy_config_client->OnCustomProxyConfigUpdated(config->Clone());
+  scoped_task_environment_.RunUntilIdle();
+
+  client = FetchRequest(request, network_context.get());
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"new_bar", "new_foo"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(client->response_head().was_fetched_via_cache);
+}
+
+TEST_F(NetworkContextMockHostTest, CustomProxyRequestHeadersAddedBeforeCache) {
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  net::EmbeddedTestServer proxy_test_server;
+  net::test_server::RegisterDefaultHandlers(&proxy_test_server);
+  ASSERT_TRUE(proxy_test_server.Start());
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  std::string base_url = proxy_test_server.base_url().spec();
+  // Remove slash from URL.
+  base_url.pop_back();
+  config->rules.ParseFromString("http=" + base_url);
+  proxy_config_client->OnCustomProxyConfigUpdated(std::move(config));
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.url = GetURLWithMockHost(test_server, "/echoheadercache?foo&bar");
+  request.custom_proxy_pre_cache_headers.SetHeader("foo", "foo_value");
+  request.custom_proxy_post_cache_headers.SetHeader("bar", "bar_value");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(client->response_head().was_fetched_via_cache);
+
+  // custom_proxy_post_cache_headers should not break caching.
+  request.custom_proxy_post_cache_headers.SetHeader("bar", "new_bar");
+
+  client = FetchRequest(request, network_context.get());
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n"));
+  EXPECT_TRUE(client->response_head().was_fetched_via_cache);
+
+  // custom_proxy_pre_cache_headers should invalidate cache.
+  request.custom_proxy_pre_cache_headers.SetHeader("foo", "new_foo");
+
+  client = FetchRequest(request, network_context.get());
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"new_bar", "new_foo"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(client->response_head().was_fetched_via_cache);
+}
+
+TEST_F(NetworkContextMockHostTest,
+       CustomProxyDoesNotAddHeadersWhenNoProxyUsed) {
+  net::EmbeddedTestServer test_server;
+  net::test_server::RegisterDefaultHandlers(&test_server);
+  ASSERT_TRUE(test_server.Start());
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  config->pre_cache_headers.SetHeader("pre_foo", "bad");
+  config->post_cache_headers.SetHeader("post_foo", "bad");
+  proxy_config_client->OnCustomProxyConfigUpdated(std::move(config));
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.custom_proxy_pre_cache_headers.SetHeader("pre_bar", "bad");
+  request.custom_proxy_post_cache_headers.SetHeader("post_bar", "bad");
+  request.url = GetURLWithMockHost(
+      test_server, "/echoheader?pre_foo&post_foo&pre_bar&post_bar");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"None", "None", "None", "None"}, "\n"));
+  EXPECT_TRUE(client->response_head().proxy_server.is_direct());
+}
+
+TEST_F(NetworkContextMockHostTest,
+       CustomProxyDoesNotAddHeadersWhenOtherProxyUsed) {
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  net::EmbeddedTestServer proxy_test_server;
+  net::test_server::RegisterDefaultHandlers(&proxy_test_server);
+  ASSERT_TRUE(proxy_test_server.Start());
+
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  // Set up a proxy to be used by the proxy config service.
+  net::ProxyConfig proxy_config;
+  std::string base_url = proxy_test_server.base_url().spec();
+  // Remove slash from URL.
+  base_url.pop_back();
+  proxy_config.proxy_rules().ParseFromString("http=" + base_url);
+  context_params->initial_proxy_config = net::ProxyConfigWithAnnotation(
+      proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS);
+
+  mojom::CustomProxyConfigClientPtr proxy_config_client;
+  context_params->custom_proxy_config_client_request =
+      mojo::MakeRequest(&proxy_config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  auto config = mojom::CustomProxyConfig::New();
+  config->pre_cache_headers.SetHeader("pre_foo", "bad");
+  config->post_cache_headers.SetHeader("post_foo", "bad");
+  proxy_config_client->OnCustomProxyConfigUpdated(std::move(config));
+  scoped_task_environment_.RunUntilIdle();
+
+  ResourceRequest request;
+  request.custom_proxy_pre_cache_headers.SetHeader("pre_bar", "bad");
+  request.custom_proxy_post_cache_headers.SetHeader("post_bar", "bad");
+  request.url = GetURLWithMockHost(
+      test_server, "/echoheader?pre_foo&post_foo&pre_bar&post_bar");
+  std::unique_ptr<TestURLLoaderClient> client =
+      FetchRequest(request, network_context.get());
+  std::string response;
+  EXPECT_TRUE(
+      mojo::BlockingCopyToString(client->response_body_release(), &response));
+
+  EXPECT_EQ(response, base::JoinString({"None", "None", "None", "None"}, "\n"));
+  EXPECT_EQ(client->response_head().proxy_server,
+            net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP));
+}
+
 }  // namespace
 
 }  // namespace network
diff --git a/services/network/network_service_network_delegate.cc b/services/network/network_service_network_delegate.cc
index b07d2ce..b9f1a1b 100644
--- a/services/network/network_service_network_delegate.cc
+++ b/services/network/network_service_network_delegate.cc
@@ -7,6 +7,7 @@
 #include "services/network/cookie_manager.h"
 #include "services/network/network_context.h"
 #include "services/network/network_service.h"
+#include "services/network/network_service_proxy_delegate.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/url_loader.h"
 
@@ -24,6 +25,28 @@
 
 NetworkServiceNetworkDelegate::~NetworkServiceNetworkDelegate() = default;
 
+int NetworkServiceNetworkDelegate::OnBeforeStartTransaction(
+    net::URLRequest* request,
+    net::CompletionOnceCallback callback,
+    net::HttpRequestHeaders* headers) {
+  if (network_context_->proxy_delegate()) {
+    network_context_->proxy_delegate()->OnBeforeStartTransaction(request,
+                                                                 headers);
+  }
+  return net::OK;
+}
+
+void NetworkServiceNetworkDelegate::OnBeforeSendHeaders(
+    net::URLRequest* request,
+    const net::ProxyInfo& proxy_info,
+    const net::ProxyRetryInfoMap& proxy_retry_info,
+    net::HttpRequestHeaders* headers) {
+  if (network_context_->proxy_delegate()) {
+    network_context_->proxy_delegate()->OnBeforeSendHeaders(request, proxy_info,
+                                                            headers);
+  }
+}
+
 int NetworkServiceNetworkDelegate::OnHeadersReceived(
     net::URLRequest* request,
     net::CompletionOnceCallback callback,
diff --git a/services/network/network_service_network_delegate.h b/services/network/network_service_network_delegate.h
index 74a5a0d..b4b1bbc0 100644
--- a/services/network/network_service_network_delegate.h
+++ b/services/network/network_service_network_delegate.h
@@ -22,6 +22,13 @@
 
  private:
   // net::NetworkDelegateImpl implementation.
+  void OnBeforeSendHeaders(net::URLRequest* request,
+                           const net::ProxyInfo& proxy_info,
+                           const net::ProxyRetryInfoMap& proxy_retry_info,
+                           net::HttpRequestHeaders* headers) override;
+  int OnBeforeStartTransaction(net::URLRequest* request,
+                               net::CompletionOnceCallback callback,
+                               net::HttpRequestHeaders* headers) override;
   int OnHeadersReceived(
       net::URLRequest* request,
       net::CompletionOnceCallback callback,
diff --git a/services/network/network_service_proxy_delegate.cc b/services/network/network_service_proxy_delegate.cc
new file mode 100644
index 0000000..105a070
--- /dev/null
+++ b/services/network/network_service_proxy_delegate.cc
@@ -0,0 +1,157 @@
+// 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 "services/network/network_service_proxy_delegate.h"
+#include "net/base/url_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "services/network/url_loader.h"
+
+namespace network {
+namespace {
+
+void GetAlternativeProxy(const GURL& url,
+                         const net::ProxyRetryInfoMap& proxy_retry_info,
+                         net::ProxyInfo* result) {
+  net::ProxyServer resolved_proxy_server = result->proxy_server();
+  DCHECK(resolved_proxy_server.is_valid());
+
+  // Right now, HTTPS proxies are assumed to support quic. If this needs to
+  // change, add a setting in CustomProxyConfig to control this behavior.
+  if (!resolved_proxy_server.is_https())
+    return;
+
+  net::ProxyInfo alternative_proxy_info;
+  alternative_proxy_info.UseProxyServer(net::ProxyServer(
+      net::ProxyServer::SCHEME_QUIC, resolved_proxy_server.host_port_pair()));
+  alternative_proxy_info.DeprioritizeBadProxies(proxy_retry_info);
+
+  if (alternative_proxy_info.is_empty())
+    return;
+
+  result->SetAlternativeProxy(alternative_proxy_info.proxy_server());
+}
+
+bool ApplyProxyConfigToProxyInfo(const net::ProxyConfig::ProxyRules& rules,
+                                 const net::ProxyRetryInfoMap& proxy_retry_info,
+                                 const GURL& url,
+                                 net::ProxyInfo* proxy_info) {
+  DCHECK(proxy_info);
+  if (rules.empty())
+    return false;
+
+  rules.Apply(url, proxy_info);
+  proxy_info->DeprioritizeBadProxies(proxy_retry_info);
+  return !proxy_info->proxy_server().is_direct();
+}
+
+// Checks if |target_proxy| is in |proxy_list|.
+bool CheckProxyList(const net::ProxyList& proxy_list,
+                    const net::ProxyServer& target_proxy) {
+  for (const auto& proxy : proxy_list.GetAll()) {
+    if (proxy.host_port_pair().Equals(target_proxy.host_port_pair()))
+      return true;
+  }
+  return false;
+}
+
+}  // namespace
+
+NetworkServiceProxyDelegate::NetworkServiceProxyDelegate(
+    mojom::CustomProxyConfigClientRequest config_client_request)
+    : binding_(this, std::move(config_client_request)) {}
+
+void NetworkServiceProxyDelegate::OnBeforeStartTransaction(
+    net::URLRequest* request,
+    net::HttpRequestHeaders* headers) {
+  if (!MayProxyURL(request->url()))
+    return;
+
+  headers->MergeFrom(proxy_config_->pre_cache_headers);
+
+  auto* url_loader = URLLoader::ForRequest(*request);
+  if (url_loader) {
+    headers->MergeFrom(url_loader->custom_proxy_pre_cache_headers());
+  }
+}
+
+void NetworkServiceProxyDelegate::OnBeforeSendHeaders(
+    net::URLRequest* request,
+    const net::ProxyInfo& proxy_info,
+    net::HttpRequestHeaders* headers) {
+  auto* url_loader = URLLoader::ForRequest(*request);
+  if (IsInProxyConfig(proxy_info.proxy_server())) {
+    headers->MergeFrom(proxy_config_->post_cache_headers);
+
+    if (url_loader) {
+      headers->MergeFrom(url_loader->custom_proxy_post_cache_headers());
+    }
+    // TODO(crbug.com/721403): This check may be incorrect if a new proxy config
+    // is set between OnBeforeStartTransaction and here.
+  } else if (MayProxyURL(request->url())) {
+    for (const auto& kv : proxy_config_->pre_cache_headers.GetHeaderVector()) {
+      headers->RemoveHeader(kv.key);
+    }
+
+    if (url_loader) {
+      for (const auto& kv :
+           url_loader->custom_proxy_pre_cache_headers().GetHeaderVector()) {
+        headers->RemoveHeader(kv.key);
+      }
+    }
+  }
+}
+
+NetworkServiceProxyDelegate::~NetworkServiceProxyDelegate() {}
+
+void NetworkServiceProxyDelegate::OnResolveProxy(
+    const GURL& url,
+    const std::string& method,
+    const net::ProxyRetryInfoMap& proxy_retry_info,
+    net::ProxyInfo* result) {
+  if (!EligibleForProxy(*result, url, method))
+    return;
+
+  net::ProxyInfo proxy_info;
+  if (ApplyProxyConfigToProxyInfo(proxy_config_->rules, proxy_retry_info, url,
+                                  &proxy_info)) {
+    DCHECK(!proxy_info.is_empty() && !proxy_info.is_direct());
+    result->OverrideProxyList(proxy_info.proxy_list());
+    GetAlternativeProxy(url, proxy_retry_info, result);
+  }
+}
+
+void NetworkServiceProxyDelegate::OnFallback(const net::ProxyServer& bad_proxy,
+                                             int net_error) {}
+
+void NetworkServiceProxyDelegate::OnCustomProxyConfigUpdated(
+    mojom::CustomProxyConfigPtr proxy_config) {
+  DCHECK(proxy_config->rules.empty() ||
+         !proxy_config->rules.proxies_for_http.IsEmpty());
+  proxy_config_ = std::move(proxy_config);
+}
+
+bool NetworkServiceProxyDelegate::IsInProxyConfig(
+    const net::ProxyServer& proxy_server) const {
+  if (!proxy_server.is_valid() || proxy_server.is_direct())
+    return false;
+
+  return CheckProxyList(proxy_config_->rules.proxies_for_http, proxy_server);
+}
+
+bool NetworkServiceProxyDelegate::MayProxyURL(const GURL& url) const {
+  return url.SchemeIs(url::kHttpScheme) && !proxy_config_->rules.empty() &&
+         !net::IsLocalhost(url);
+}
+
+bool NetworkServiceProxyDelegate::EligibleForProxy(
+    const net::ProxyInfo& proxy_info,
+    const GURL& url,
+    const std::string& method) const {
+  return proxy_info.is_direct() && proxy_info.proxy_list().size() == 1 &&
+         MayProxyURL(url) && net::HttpUtil::IsMethodIdempotent(method);
+}
+
+}  // namespace network
diff --git a/services/network/network_service_proxy_delegate.h b/services/network/network_service_proxy_delegate.h
new file mode 100644
index 0000000..1d8fc1c
--- /dev/null
+++ b/services/network/network_service_proxy_delegate.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 SERVICES_NETWORK_NETWORK_SERVICE_PROXY_DELEGATE_H_
+#define SERVICES_NETWORK_NETWORK_SERVICE_PROXY_DELEGATE_H_
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "net/base/proxy_delegate.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+namespace net {
+class HttpRequestHeaders;
+class URLRequest;
+}  // namespace net
+
+namespace network {
+
+// NetworkServiceProxyDelegate is used to support the custom proxy
+// configuration, which can be set in
+// NetworkContextParams.custom_proxy_config_client_request.
+class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceProxyDelegate
+    : public net::ProxyDelegate,
+      public mojom::CustomProxyConfigClient {
+ public:
+  explicit NetworkServiceProxyDelegate(
+      mojom::CustomProxyConfigClientRequest config_client_request);
+  ~NetworkServiceProxyDelegate() override;
+
+  // These methods are forwarded from the NetworkDelegate.
+  void OnBeforeStartTransaction(net::URLRequest* request,
+                                net::HttpRequestHeaders* headers);
+  void OnBeforeSendHeaders(net::URLRequest* request,
+                           const net::ProxyInfo& proxy_info,
+                           net::HttpRequestHeaders* headers);
+
+  // net::ProxyDelegate implementation:
+  void OnResolveProxy(const GURL& url,
+                      const std::string& method,
+                      const net::ProxyRetryInfoMap& proxy_retry_info,
+                      net::ProxyInfo* result) override;
+  void OnFallback(const net::ProxyServer& bad_proxy, int net_error) override;
+
+ private:
+  // Checks whether |proxy_server| is present in the current proxy config.
+  bool IsInProxyConfig(const net::ProxyServer& proxy_server) const;
+
+  // Whether the current config may proxy |url|.
+  bool MayProxyURL(const GURL& url) const;
+
+  // Whether the |url| with current |proxy_info| is eligible to be proxied.
+  bool EligibleForProxy(const net::ProxyInfo& proxy_info,
+                        const GURL& url,
+                        const std::string& method) const;
+
+  // mojom::CustomProxyConfigClient implementation:
+  void OnCustomProxyConfigUpdated(
+      mojom::CustomProxyConfigPtr proxy_config) override;
+
+  mojom::CustomProxyConfigPtr proxy_config_;
+  mojo::Binding<mojom::CustomProxyConfigClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkServiceProxyDelegate);
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_NETWORK_SERVICE_PROXY_DELEGATE_H_
diff --git a/services/network/network_service_proxy_delegate_unittest.cc b/services/network/network_service_proxy_delegate_unittest.cc
new file mode 100644
index 0000000..ddce4b2
--- /dev/null
+++ b/services/network/network_service_proxy_delegate_unittest.cc
@@ -0,0 +1,319 @@
+// 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 "services/network/network_service_proxy_delegate.h"
+#include "base/test/scoped_task_environment.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+namespace {
+
+constexpr char kHttpUrl[] = "http://example.com";
+constexpr char kLocalhost[] = "http://localhost";
+constexpr char kHttpsUrl[] = "https://example.com";
+constexpr char kWebsocketUrl[] = "ws://example.com";
+
+}  // namespace
+
+class NetworkServiceProxyDelegateTest : public testing::Test {
+ public:
+  NetworkServiceProxyDelegateTest() {}
+
+  void SetUp() override {
+    context_ = std::make_unique<net::TestURLRequestContext>(true);
+    context_->Init();
+  }
+
+ protected:
+  std::unique_ptr<NetworkServiceProxyDelegate> CreateDelegate(
+      mojom::CustomProxyConfigPtr config) {
+    mojom::CustomProxyConfigClientPtr client;
+    auto delegate = std::make_unique<NetworkServiceProxyDelegate>(
+        mojo::MakeRequest(&client));
+    client->OnCustomProxyConfigUpdated(std::move(config));
+    scoped_task_environment_.RunUntilIdle();
+    return delegate;
+  }
+
+  std::unique_ptr<net::URLRequest> CreateRequest(const GURL& url) {
+    return context_->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr);
+  }
+
+ private:
+  std::unique_ptr<net::TestURLRequestContext> context_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(NetworkServiceProxyDelegateTest, AddsHeadersBeforeCache) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kHttpUrl));
+  delegate->OnBeforeStartTransaction(request.get(), &headers);
+
+  std::string value;
+  EXPECT_TRUE(headers.GetHeader("foo", &value));
+  EXPECT_EQ(value, "bar");
+}
+
+TEST_F(NetworkServiceProxyDelegateTest,
+       DoesNotAddHeadersBeforeCacheForLocalhost) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kLocalhost));
+  delegate->OnBeforeStartTransaction(request.get(), &headers);
+
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, DoesNotAddHeadersBeforeCacheForHttps) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kHttpsUrl));
+  delegate->OnBeforeStartTransaction(request.get(), &headers);
+
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, AddsHeadersAfterCache) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->post_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kHttpUrl));
+  net::ProxyInfo info;
+  info.UsePacString("PROXY proxy");
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  std::string value;
+  EXPECT_TRUE(headers.GetHeader("foo", &value));
+  EXPECT_EQ(value, "bar");
+}
+
+TEST_F(NetworkServiceProxyDelegateTest,
+       DoesNotAddHeadersAfterCacheForProxyNotInConfig) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->post_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kHttpUrl));
+  net::ProxyInfo info;
+  info.UsePacString("PROXY other");
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, DoesNotAddHeadersAfterCacheForDirect) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->post_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  auto request = CreateRequest(GURL(kHttpUrl));
+  net::ProxyInfo info;
+  info.UseDirect();
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest,
+       RemovesPreCacheHeadersWhenProxyNotInConfig) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  headers.SetHeader("foo", "bar");
+  auto request = CreateRequest(GURL(kHttpUrl));
+  net::ProxyInfo info;
+  info.UseDirect();
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest,
+       DoesNotRemoveHeaderForHttpsIfAlreadyExists) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bad");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  headers.SetHeader("foo", "value");
+  auto request = CreateRequest(GURL(kHttpsUrl));
+  net::ProxyInfo info;
+  info.UseDirect();
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  std::string value;
+  EXPECT_TRUE(headers.GetHeader("foo", &value));
+  EXPECT_EQ(value, "value");
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, KeepsPreCacheHeadersWhenProxyInConfig) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=proxy");
+  config->pre_cache_headers.SetHeader("foo", "bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::HttpRequestHeaders headers;
+  headers.SetHeader("foo", "bar");
+  auto request = CreateRequest(GURL(kHttpUrl));
+  net::ProxyInfo info;
+  info.UsePacString("PROXY proxy");
+  delegate->OnBeforeSendHeaders(request.get(), info, &headers);
+
+  std::string value;
+  EXPECT_TRUE(headers.GetHeader("foo", &value));
+  EXPECT_EQ(value, "bar");
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpProxy) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyServer(
+      net::ProxyServer::FromPacString("PROXY foo"));
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list));
+  // HTTP proxies are nto used as alternative QUIC proxies.
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpsProxy) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=https://foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyServer(
+      net::ProxyServer::FromPacString("HTTPS foo"));
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list));
+  EXPECT_EQ(result.alternative_proxy(),
+            net::ProxyServer::FromPacString("QUIC foo"));
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyLocalhost) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kLocalhost), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyEmptyConfig) {
+  auto delegate = CreateDelegate(mojom::CustomProxyConfig::New());
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyNonIdempotentMethod) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl), "POST", net::ProxyRetryInfoMap(),
+                           &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyWebsocketScheme) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kWebsocketUrl), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyDoesNotOverrideExisting) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UsePacString("PROXY bar");
+  delegate->OnResolveProxy(GURL(kHttpUrl), "GET", net::ProxyRetryInfoMap(),
+                           &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyServer(
+      net::ProxyServer::FromPacString("PROXY bar"));
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list));
+  EXPECT_FALSE(result.alternative_proxy().is_valid());
+}
+
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyDeprioritizesBadProxies) {
+  auto config = mojom::CustomProxyConfig::New();
+  config->rules.ParseFromString("http=foo,bar");
+  auto delegate = CreateDelegate(std::move(config));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  net::ProxyRetryInfoMap retry_map;
+  net::ProxyRetryInfo& info = retry_map["foo:80"];
+  info.try_while_bad = false;
+  info.bad_until = base::TimeTicks::Now() + base::TimeDelta::FromDays(2);
+  delegate->OnResolveProxy(GURL(kHttpUrl), "GET", retry_map, &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyServer(
+      net::ProxyServer::FromPacString("PROXY bar"));
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list));
+}
+
+}  // namespace network
diff --git a/services/network/p2p/socket.cc b/services/network/p2p/socket.cc
index 8c65a4d..8595623f 100644
--- a/services/network/p2p/socket.cc
+++ b/services/network/p2p/socket.cc
@@ -67,12 +67,7 @@
     : delegate_(delegate),
       client_(std::move(client)),
       binding_(this, std::move(socket)),
-      state_(STATE_UNINITIALIZED),
       protocol_type_(protocol_type),
-      send_packets_delayed_total_(0),
-      send_packets_total_(0),
-      send_bytes_delayed_max_(0),
-      send_bytes_delayed_cur_(0),
       weak_ptr_factory_(this) {
   binding_.set_connection_error_handler(
       base::BindOnce(&P2PSocket::OnError, base::Unretained(this)));
diff --git a/services/network/p2p/socket.h b/services/network/p2p/socket.h
index f6c42681..099d8b6b 100644
--- a/services/network/p2p/socket.h
+++ b/services/network/p2p/socket.h
@@ -150,20 +150,19 @@
   Delegate* delegate_;
   mojom::P2PSocketClientPtr client_;
   mojo::Binding<mojom::P2PSocket> binding_;
-  State state_;
 
   ProtocolType protocol_type_;
 
  private:
   // Track total delayed packets for calculating how many packets are
   // delayed by system at the end of call.
-  uint32_t send_packets_delayed_total_;
-  uint32_t send_packets_total_;
+  uint32_t send_packets_delayed_total_ = 0;
+  uint32_t send_packets_total_ = 0;
 
   // Track the maximum of consecutive delayed bytes caused by system's
   // EWOULDBLOCK.
-  int32_t send_bytes_delayed_max_;
-  int32_t send_bytes_delayed_cur_;
+  int32_t send_bytes_delayed_max_ = 0;
+  int32_t send_bytes_delayed_cur_ = 0;
 
   base::WeakPtrFactory<P2PSocket> weak_ptr_factory_;
 
diff --git a/services/network/p2p/socket_tcp.cc b/services/network/p2p/socket_tcp.cc
index 95d81a34..ba0919b9 100644
--- a/services/network/p2p/socket_tcp.cc
+++ b/services/network/p2p/socket_tcp.cc
@@ -53,7 +53,7 @@
       buffer(buffer),
       traffic_annotation(traffic_annotation) {}
 P2PSocketTcp::SendBuffer::SendBuffer(const SendBuffer& rhs) = default;
-P2PSocketTcp::SendBuffer::~SendBuffer() {}
+P2PSocketTcp::SendBuffer::~SendBuffer() = default;
 
 P2PSocketTcpBase::P2PSocketTcpBase(
     Delegate* delegate,
@@ -62,27 +62,17 @@
     P2PSocketType type,
     ProxyResolvingClientSocketFactory* proxy_resolving_socket_factory)
     : P2PSocket(delegate, std::move(client), std::move(socket), P2PSocket::TCP),
-      write_pending_(false),
-      connected_(false),
       type_(type),
       proxy_resolving_socket_factory_(proxy_resolving_socket_factory) {}
 
-P2PSocketTcpBase::~P2PSocketTcpBase() {
-  if (state_ == STATE_OPEN) {
-    DCHECK(socket_.get());
-    socket_.reset();
-  }
-}
+P2PSocketTcpBase::~P2PSocketTcpBase() = default;
 
 void P2PSocketTcpBase::InitAccepted(const net::IPEndPoint& remote_address,
                                     std::unique_ptr<net::StreamSocket> socket) {
   DCHECK(socket);
-  DCHECK_EQ(state_, STATE_UNINITIALIZED);
-
   remote_address_.ip_address = remote_address;
   // TODO(ronghuawu): Add FakeSSLServerSocket.
   socket_ = std::move(socket);
-  state_ = STATE_OPEN;
   DoRead();
 }
 
@@ -90,10 +80,9 @@
                             uint16_t min_port,
                             uint16_t max_port,
                             const P2PHostAndIPEndPoint& remote_address) {
-  DCHECK_EQ(state_, STATE_UNINITIALIZED);
+  DCHECK(!socket_);
 
   remote_address_ = remote_address;
-  state_ = STATE_CONNECTING;
 
   net::HostPortPair dest_host_port_pair;
   // If there is a domain name, let's try it first, it's required by some proxy
@@ -129,7 +118,6 @@
 }
 
 void P2PSocketTcpBase::OnConnected(int result) {
-  DCHECK_EQ(state_, STATE_CONNECTING);
   DCHECK_NE(result, net::ERR_IO_PENDING);
 
   if (result != net::OK) {
@@ -142,7 +130,6 @@
 }
 
 void P2PSocketTcpBase::OnOpen() {
-  state_ = STATE_OPEN;
   // Setting socket send and receive buffer size.
   if (net::OK != socket_->SetReceiveBufferSize(kTcpRecvSocketBufferSize)) {
     LOG(WARNING) << "Failed to set socket receive buffer size to "
@@ -157,7 +144,6 @@
   if (!DoSendSocketCreateMsg())
     return;
 
-  DCHECK_EQ(state_, STATE_OPEN);
   DoRead();
 }
 
@@ -326,8 +312,6 @@
 }
 
 bool P2PSocketTcpBase::HandleReadResult(int result) {
-  DCHECK_EQ(state_, STATE_OPEN);
-
   if (result < 0) {
     LOG(ERROR) << "Error when reading from TCP socket: " << result;
     OnError();
diff --git a/services/network/p2p/socket_tcp.h b/services/network/p2p/socket_tcp.h
index bac29f8..1683264 100644
--- a/services/network/p2p/socket_tcp.h
+++ b/services/network/p2p/socket_tcp.h
@@ -110,10 +110,10 @@
   base::queue<SendBuffer> write_queue_;
   SendBuffer write_buffer_;
 
-  bool write_pending_;
+  bool write_pending_ = false;
 
-  bool connected_;
-  P2PSocketType type_;
+  bool connected_ = false;
+  const P2PSocketType type_;
   ProxyResolvingClientSocketFactory* proxy_resolving_socket_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(P2PSocketTcpBase);
diff --git a/services/network/p2p/socket_tcp_server.cc b/services/network/p2p/socket_tcp_server.cc
index 3f9ec13..f1cd384 100644
--- a/services/network/p2p/socket_tcp_server.cc
+++ b/services/network/p2p/socket_tcp_server.cc
@@ -32,20 +32,13 @@
       accept_callback_(base::BindRepeating(&P2PSocketTcpServer::OnAccepted,
                                            base::Unretained(this))) {}
 
-P2PSocketTcpServer::~P2PSocketTcpServer() {
-  if (state_ == STATE_OPEN) {
-    DCHECK(socket_.get());
-    socket_.reset();
-  }
-}
+P2PSocketTcpServer::~P2PSocketTcpServer() = default;
 
 // TODO(guidou): Add support for port range.
 void P2PSocketTcpServer::Init(const net::IPEndPoint& local_address,
                               uint16_t min_port,
                               uint16_t max_port,
                               const P2PHostAndIPEndPoint& remote_address) {
-  DCHECK_EQ(state_, STATE_UNINITIALIZED);
-
   int result = socket_->Listen(local_address, kListenBacklog);
   if (result < 0) {
     LOG(ERROR) << "Listen() failed: " << result;
@@ -62,7 +55,6 @@
   }
   VLOG(1) << "Local address: " << local_address_.ToString();
 
-  state_ = STATE_OPEN;
   // NOTE: Remote address can be empty as socket is just listening
   // in this state.
   client_->SocketCreated(local_address_, remote_address.ip_address);
diff --git a/services/network/p2p/socket_tcp_unittest.cc b/services/network/p2p/socket_tcp_unittest.cc
index 6bb7743..0935e1d 100644
--- a/services/network/p2p/socket_tcp_unittest.cc
+++ b/services/network/p2p/socket_tcp_unittest.cc
@@ -64,7 +64,6 @@
     local_address_ = ParseAddress(kTestLocalIpAddress, kTestPort1);
 
     socket_impl_->remote_address_ = dest_;
-    socket_impl_->state_ = P2PSocket::STATE_CONNECTING;
     socket_impl_->OnConnected(net::OK);
     base::RunLoop().RunUntilIdle();
   }
diff --git a/services/network/p2p/socket_udp.cc b/services/network/p2p/socket_udp.cc
index 388b497..e7609dd6 100644
--- a/services/network/p2p/socket_udp.cc
+++ b/services/network/p2p/socket_udp.cc
@@ -66,6 +66,17 @@
   return "";
 }
 
+std::unique_ptr<net::DatagramServerSocket> DefaultSocketFactory(
+    net::NetLog* net_log) {
+  net::UDPServerSocket* socket =
+      new net::UDPServerSocket(net_log, net::NetLogSource());
+#if defined(OS_WIN)
+  socket->UseNonBlockingIO();
+#endif
+
+  return base::WrapUnique(socket);
+}
+
 }  // namespace
 
 namespace network {
@@ -87,8 +98,7 @@
 
 P2PSocketUdp::PendingPacket::PendingPacket(const PendingPacket& other) =
     default;
-
-P2PSocketUdp::PendingPacket::~PendingPacket() {}
+P2PSocketUdp::PendingPacket::~PendingPacket() = default;
 
 P2PSocketUdp::P2PSocketUdp(Delegate* Delegate,
                            mojom::P2PSocketClientPtr client,
@@ -97,9 +107,6 @@
                            net::NetLog* net_log,
                            const DatagramServerSocketFactory& socket_factory)
     : P2PSocket(Delegate, std::move(client), std::move(socket), P2PSocket::UDP),
-      socket_(socket_factory.Run(net_log)),
-      send_pending_(false),
-      last_dscp_(net::DSCP_CS0),
       throttler_(throttler),
       net_log_(net_log),
       socket_factory_(socket_factory) {}
@@ -114,23 +121,20 @@
                    std::move(socket),
                    throttler,
                    net_log,
-                   base::Bind(&P2PSocketUdp::DefaultSocketFactory)) {}
+                   base::BindRepeating(&DefaultSocketFactory)) {}
 
-P2PSocketUdp::~P2PSocketUdp() {
-  if (state_ == STATE_OPEN) {
-    DCHECK(socket_.get());
-    socket_.reset();
-  }
-}
+P2PSocketUdp::~P2PSocketUdp() = default;
 
 void P2PSocketUdp::Init(const net::IPEndPoint& local_address,
                         uint16_t min_port,
                         uint16_t max_port,
                         const P2PHostAndIPEndPoint& remote_address) {
-  DCHECK_EQ(state_, STATE_UNINITIALIZED);
+  DCHECK(!socket_);
   DCHECK((min_port == 0 && max_port == 0) || min_port > 0);
   DCHECK_LE(min_port, max_port);
 
+  socket_ = socket_factory_.Run(net_log_);
+
   int result = -1;
   if (min_port == 0) {
     result = socket_->Listen(local_address);
@@ -177,8 +181,6 @@
   }
   VLOG(1) << "Local address: " << address.ToString();
 
-  state_ = STATE_OPEN;
-
   // NOTE: Remote address will be same as what renderer provided.
   client_->SocketCreated(address, remote_address.ip_address);
 
@@ -202,8 +204,6 @@
 }
 
 bool P2PSocketUdp::HandleReadResult(int result) {
-  DCHECK_EQ(STATE_OPEN, state_);
-
   if (result > 0) {
     std::vector<int8_t> data(recv_buffer_->data(),
                              recv_buffer_->data() + result);
@@ -342,7 +342,7 @@
   }
 
   // Send next packets if we have them waiting in the buffer.
-  while (state_ == STATE_OPEN && !send_queue_.empty() && !send_pending_) {
+  while (!send_queue_.empty() && !send_pending_) {
     PendingPacket packet = send_queue_.front();
     send_queue_.pop_front();
     if (!DoSend(packet))
@@ -437,16 +437,4 @@
 #endif
 }
 
-// static
-std::unique_ptr<net::DatagramServerSocket> P2PSocketUdp::DefaultSocketFactory(
-    net::NetLog* net_log) {
-  net::UDPServerSocket* socket =
-      new net::UDPServerSocket(net_log, net::NetLogSource());
-#if defined(OS_WIN)
-  socket->UseNonBlockingIO();
-#endif
-
-  return base::WrapUnique(socket);
-}
-
 }  // namespace network
diff --git a/services/network/p2p/socket_udp.h b/services/network/p2p/socket_udp.h
index 5b65792..39c3fb0 100644
--- a/services/network/p2p/socket_udp.h
+++ b/services/network/p2p/socket_udp.h
@@ -104,16 +104,14 @@
               int result);
 
   int SetSocketDiffServCodePointInternal(net::DiffServCodePoint dscp);
-  static std::unique_ptr<net::DatagramServerSocket> DefaultSocketFactory(
-      net::NetLog* net_log);
 
   std::unique_ptr<net::DatagramServerSocket> socket_;
   scoped_refptr<net::IOBuffer> recv_buffer_;
   net::IPEndPoint recv_address_;
 
   base::circular_deque<PendingPacket> send_queue_;
-  bool send_pending_;
-  net::DiffServCodePoint last_dscp_;
+  bool send_pending_ = false;
+  net::DiffServCodePoint last_dscp_ = net::DSCP_CS0;
 
   // Set of peer for which we have received STUN binding request or
   // response or relay allocation request or response.
diff --git a/services/network/public/cpp/network_ipc_param_traits.h b/services/network/public/cpp/network_ipc_param_traits.h
index dcf4964..3e69389 100644
--- a/services/network/public/cpp/network_ipc_param_traits.h
+++ b/services/network/public/cpp/network_ipc_param_traits.h
@@ -169,6 +169,8 @@
   IPC_STRUCT_TRAITS_MEMBER(upgrade_if_insecure)
   IPC_STRUCT_TRAITS_MEMBER(is_revalidating)
   IPC_STRUCT_TRAITS_MEMBER(throttling_profile_id)
+  IPC_STRUCT_TRAITS_MEMBER(custom_proxy_pre_cache_headers)
+  IPC_STRUCT_TRAITS_MEMBER(custom_proxy_post_cache_headers)
 IPC_STRUCT_TRAITS_END()
 
 IPC_STRUCT_TRAITS_BEGIN(network::ResourceResponseInfo)
diff --git a/services/network/public/cpp/proxy_config.typemap b/services/network/public/cpp/proxy_config.typemap
index 8f3f5043..4dce13d 100644
--- a/services/network/public/cpp/proxy_config.typemap
+++ b/services/network/public/cpp/proxy_config.typemap
@@ -17,7 +17,7 @@
   "network.mojom.ProxyBypassRules=net::ProxyBypassRules",
   "network.mojom.ProxyList=net::ProxyList",
   "network.mojom.ProxyRulesType=net::ProxyConfig::ProxyRules::Type",
-  "network.mojom.ProxyRule=net::ProxyConfig::ProxyRule",
+  "network.mojom.ProxyRules=net::ProxyConfig::ProxyRules",
   "network.mojom.ProxyConfig=net::ProxyConfig",
   "network.mojom.ProxyConfigWithAnnotation=net::ProxyConfigWithAnnotation",
 ]
diff --git a/services/network/public/cpp/resource_request.h b/services/network/public/cpp/resource_request.h
index e0c40e9..c3c2494 100644
--- a/services/network/public/cpp/resource_request.h
+++ b/services/network/public/cpp/resource_request.h
@@ -224,6 +224,12 @@
 
   // The profile ID of network conditions to throttle the network request.
   base::Optional<base::UnguessableToken> throttling_profile_id;
+
+  // Headers that will be added pre and post cache if the network context uses
+  // the custom proxy for this request. The custom proxy is used for requests
+  // that match the custom proxy config, and would otherwise be made direct.
+  net::HttpRequestHeaders custom_proxy_pre_cache_headers;
+  net::HttpRequestHeaders custom_proxy_post_cache_headers;
 };
 
 }  // namespace network
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 765ac8f..64e4b7f 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -30,10 +30,45 @@
 import "services/network/public/mojom/url_loader.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "services/network/public/mojom/websocket.mojom";
+import "services/network/public/mojom/http_request_headers.mojom";
 import "services/proxy_resolver/public/mojom/proxy_resolver.mojom";
 import "url/mojom/origin.mojom";
 import "url/mojom/url.mojom";
 
+// Config for setting a custom proxy config that will be used if a request
+// matches the proxy rules and would otherwise be direct. This config allows
+// headers to be set on requests to the proxies from the config before and/or
+// after the caching layer. Currently only supports proxying http requests.
+struct CustomProxyConfig {
+  // The custom proxy rules to use. Right now this is limited to proxies for
+  // http requests.
+  ProxyRules rules;
+
+  // The custom proxy can set these headers in this config which will be added
+  // to all requests using the proxy. This allows setting headers that may be
+  // privacy/security sensitive which we don't want to send to the renderer.
+  // Headers that require per-request logic can be added through the
+  // |custom_proxy_pre_cache_headers| and |custom_proxy_post_cache_headers|
+  // fields in ResourceRequest.
+  //
+  // Headers that will be set before the cache for http requests. If the request
+  // does not use a custom proxy, these headers will be removed before sending
+  // to the network. If a request already has one of these headers set, it may
+  // be overwritten if a custom proxy is used, or removed if a custom proxy is
+  // not used.
+  HttpRequestHeaders pre_cache_headers;
+
+  // Headers that will be set after the cache for requests that are issued
+  // through a custom proxy. Headers here will overwrite matching headers on the
+  // request if a custom proxy is used.
+  HttpRequestHeaders post_cache_headers;
+};
+
+// Client to update the custom proxy config.
+interface CustomProxyConfigClient {
+  OnCustomProxyConfigUpdated(CustomProxyConfig proxy_config);
+};
+
 // Parameters for constructing a network context.
 struct NetworkContextParams {
   // Name used by memory tools to identify the context.
@@ -141,6 +176,11 @@
   ProxyConfigWithAnnotation? initial_proxy_config;
   ProxyConfigClient&? proxy_config_client_request;
 
+  // If |custom_proxy_config_client_request| is set, this context will listen
+  // for updates to the custom proxy config, and use it if applicable for
+  // requests which would otherwise be made direct.
+  CustomProxyConfigClient&? custom_proxy_config_client_request;
+
   // If |proxy_config_client_request| is non-null, this is called during
   // periods of network activity, and can be used as a signal for polling-based
   // logic to determine the proxy config.
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 1f85c070..78e3678 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -321,6 +321,8 @@
       keepalive_statistics_recorder_(std::move(keepalive_statistics_recorder)),
       network_usage_accumulator_(std::move(network_usage_accumulator)),
       first_auth_attempt_(true),
+      custom_proxy_pre_cache_headers_(request.custom_proxy_pre_cache_headers),
+      custom_proxy_post_cache_headers_(request.custom_proxy_post_cache_headers),
       weak_ptr_factory_(this) {
   DCHECK(delete_callback_);
   if (!base::FeatureList::IsEnabled(features::kNetworkService)) {
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 1b14cd7..5f30e19 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -103,6 +103,14 @@
   uint32_t GetRenderFrameId() const;
   uint32_t GetProcessId() const;
 
+  const net::HttpRequestHeaders& custom_proxy_pre_cache_headers() const {
+    return custom_proxy_pre_cache_headers_;
+  }
+
+  const net::HttpRequestHeaders& custom_proxy_post_cache_headers() const {
+    return custom_proxy_post_cache_headers_;
+  }
+
   // Gets the URLLoader associated with this request.
   static URLLoader* ForRequest(const net::URLRequest& request);
 
@@ -255,6 +263,9 @@
 
   std::unique_ptr<ScopedThrottlingToken> throttling_token_;
 
+  net::HttpRequestHeaders custom_proxy_pre_cache_headers_;
+  net::HttpRequestHeaders custom_proxy_post_cache_headers_;
+
   base::WeakPtrFactory<URLLoader> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoader);
diff --git a/services/service_manager/public/cpp/BUILD.gn b/services/service_manager/public/cpp/BUILD.gn
index 40125676e..82ae6c8 100644
--- a/services/service_manager/public/cpp/BUILD.gn
+++ b/services/service_manager/public/cpp/BUILD.gn
@@ -17,6 +17,8 @@
     "local_interface_provider.h",
     "service.cc",
     "service.h",
+    "service_binding.cc",
+    "service_binding.h",
     "service_context.cc",
     "service_context.h",
     "service_context_ref.cc",
@@ -37,7 +39,12 @@
     "//url",
   ]
 
-  defines = [ "SERVICE_MANAGER_PUBLIC_CPP_IMPL" ]
+  defines = [
+    "IS_SERVICE_MANAGER_CPP_IMPL",
+
+    # TODO: Use COMPONENT_EXPORT everywhere here and remove this.
+    "SERVICE_MANAGER_PUBLIC_CPP_IMPL",
+  ]
 }
 
 # A component for types which the public interfaces depend on for typemapping.
diff --git a/services/service_manager/public/cpp/service.cc b/services/service_manager/public/cpp/service.cc
index 2030d03..11e6996 100644
--- a/services/service_manager/public/cpp/service.cc
+++ b/services/service_manager/public/cpp/service.cc
@@ -19,6 +19,8 @@
                               const std::string& interface_name,
                               mojo::ScopedMessagePipeHandle interface_pipe) {}
 
+void Service::OnDisconnected() {}
+
 bool Service::OnServiceManagerConnectionLost() {
   return true;
 }
diff --git a/services/service_manager/public/cpp/service.h b/services/service_manager/public/cpp/service.h
index b0242905..cf17566 100644
--- a/services/service_manager/public/cpp/service.h
+++ b/services/service_manager/public/cpp/service.h
@@ -7,9 +7,9 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/macros.h"
 #include "mojo/public/cpp/system/message_pipe.h"
-#include "services/service_manager/public/cpp/export.h"
 
 namespace service_manager {
 
@@ -18,7 +18,7 @@
 
 // The primary contract between a Service and the Service Manager, receiving
 // lifecycle notifications and connection requests.
-class SERVICE_MANAGER_PUBLIC_CPP_EXPORT Service {
+class COMPONENT_EXPORT(SERVICE_MANAGER_CPP) Service {
  public:
   Service();
   virtual ~Service();
@@ -37,6 +37,14 @@
                                const std::string& interface_name,
                                mojo::ScopedMessagePipeHandle interface_pipe);
 
+  // Called when the Service Manager has stopped tracking this instance. Once
+  // invoked, no further Service interface methods will be called on this
+  // Service, and no further communication with the Service Manager is possible.
+  //
+  // The Service may continue to operate and service existing client connections
+  // as it deems appropriate.
+  virtual void OnDisconnected();
+
   // Called when the Service Manager has stopped tracking this instance. The
   // service should use this as a signal to shut down, and in fact its process
   // may be reaped shortly afterward if applicable.
@@ -49,6 +57,9 @@
   //
   // NOTE: This may be called at any time, and once it's been called, none of
   // the other public Service methods will be invoked by the ServiceContext.
+  //
+  // This is ONLY invoked when using a ServiceContext and is therefore
+  // deprecated.
   virtual bool OnServiceManagerConnectionLost();
 
  protected:
@@ -72,7 +83,7 @@
 
 // TODO(rockot): Remove this. It's here to satisfy a few remaining use cases
 // where a Service impl is owned by something other than its ServiceContext.
-class SERVICE_MANAGER_PUBLIC_CPP_EXPORT ForwardingService : public Service {
+class COMPONENT_EXPORT(SERVICE_MANAGER_CPP) ForwardingService : public Service {
  public:
   // |target| must outlive this object.
   explicit ForwardingService(Service* target);
diff --git a/services/service_manager/public/cpp/service_binding.cc b/services/service_manager/public/cpp/service_binding.cc
new file mode 100644
index 0000000..cac9eef
--- /dev/null
+++ b/services/service_manager/public/cpp/service_binding.cc
@@ -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.
+
+#include "services/service_manager/public/cpp/service_binding.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "services/service_manager/public/cpp/service.h"
+
+#include "base/debug/stack_trace.h"
+
+namespace service_manager {
+
+ServiceBinding::ServiceBinding(service_manager::Service* service)
+    : service_(service), binding_(this) {
+  DCHECK(service_);
+}
+
+ServiceBinding::ServiceBinding(service_manager::Service* service,
+                               mojom::ServiceRequest request)
+    : ServiceBinding(service) {
+  if (request.is_pending())
+    Bind(std::move(request));
+}
+
+ServiceBinding::~ServiceBinding() = default;
+
+Connector* ServiceBinding::GetConnector() {
+  if (!connector_)
+    connector_ = Connector::Create(&pending_connector_request_);
+  return connector_.get();
+}
+
+void ServiceBinding::Bind(mojom::ServiceRequest request) {
+  DCHECK(!is_bound());
+  binding_.Bind(std::move(request));
+  binding_.set_connection_error_handler(base::BindOnce(
+      &ServiceBinding::OnConnectionError, base::Unretained(this)));
+}
+
+void ServiceBinding::RequestClose() {
+  DCHECK(is_bound());
+  if (service_control_.is_bound()) {
+    service_control_->RequestQuit();
+  } else {
+    // It's possible that the service may request closure before receiving the
+    // initial |OnStart()| event, in which case there is not yet a control
+    // interface on which to request closure. In that case we defer until
+    // |OnStart()| is received.
+    request_closure_on_start_ = true;
+  }
+}
+
+void ServiceBinding::Close() {
+  DCHECK(is_bound());
+  binding_.Close();
+  service_control_.reset();
+  connector_.reset();
+}
+
+void ServiceBinding::OnConnectionError() {
+  service_->OnDisconnected();
+}
+
+void ServiceBinding::OnStart(const Identity& identity,
+                             OnStartCallback callback) {
+  identity_ = identity;
+  service_->OnStart();
+
+  if (!pending_connector_request_.is_pending())
+    connector_ = Connector::Create(&pending_connector_request_);
+  std::move(callback).Run(std::move(pending_connector_request_),
+                          mojo::MakeRequest(&service_control_));
+
+  // Execute any prior |RequestClose()| request on the service's behalf.
+  if (request_closure_on_start_)
+    service_control_->RequestQuit();
+}
+
+void ServiceBinding::OnBindInterface(
+    const BindSourceInfo& source_info,
+    const std::string& interface_name,
+    mojo::ScopedMessagePipeHandle interface_pipe,
+    OnBindInterfaceCallback callback) {
+  // Acknowledge this request.
+  std::move(callback).Run();
+
+  service_->OnBindInterface(source_info, interface_name,
+                            std::move(interface_pipe));
+}
+
+}  // namespace service_manager
diff --git a/services/service_manager/public/cpp/service_binding.h b/services/service_manager/public/cpp/service_binding.h
new file mode 100644
index 0000000..6ed9548
--- /dev/null
+++ b/services/service_manager/public/cpp/service_binding.h
@@ -0,0 +1,139 @@
+// 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 SERVICES_SERVICE_MANAGER_PUBLIC_CPP_SERVICE_BINDING_H_
+#define SERVICES_SERVICE_MANAGER_PUBLIC_CPP_SERVICE_BINDING_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/mojom/connector.mojom.h"
+#include "services/service_manager/public/mojom/service.mojom.h"
+#include "services/service_manager/public/mojom/service_control.mojom.h"
+
+namespace service_manager {
+
+class Service;
+
+// Encapsulates service-side bindings to Service Manager interfaces. Namely,
+// this helps receive and dispatch Service interface events to a service
+// implementation, while also exposing a working Connector interface the service
+// can use to make outgoing interface requests.
+//
+// A ServiceBinding is considered to be "bound" after |Bind()| is invoked with a
+// valid ServiceRequest (or the equivalent constructor is used -- see below).
+// Upon connection error or an explicit call to |Close()|, the ServiceBinding
+// will be considered "unbound" until another call to |Bind()| is made.
+//
+// NOTE: A well-behaved service should aim to always close its ServiceBinding
+// gracefully by calling |RequestClose()|. Closing a ServiceBinding abruptly
+// (by either destroying it or explicitly calling |Close()|) introduces inherent
+// flakiness into the system unless the Service's |OnDisconnected()| has already
+// been invoked, because otherwise the Service Manager may have in-flight
+// interface requests directed at your service instance and these will be
+// dropped to the dismay of the service instance which issued them. Exceptions
+// can reasonably be made for system-wide shutdown situations where even the
+// Service Manager itself will be imminently torn down.
+class COMPONENT_EXPORT(SERVICE_MANAGER_CPP) ServiceBinding
+    : public mojom::Service {
+ public:
+  // Creates a new ServiceBinding bound to |service|. The service will not
+  // receive any Service interface calls until |Bind()| is called, but its
+  // |connector()| is usable immediately upon construction.
+  //
+  // |service| is not owned and must outlive this ServiceBinding.
+  explicit ServiceBinding(service_manager::Service* service);
+
+  // Same as above, but behaves as if |Bind(request)| is also called immediately
+  // after construction. See below.
+  ServiceBinding(service_manager::Service* service,
+                 mojom::ServiceRequest request);
+
+  ~ServiceBinding() override;
+
+  bool is_bound() const { return binding_.is_bound(); }
+
+  Identity identity() const { return identity_; }
+
+  // Returns a usable Connector which can make outgoing interface requests
+  // identifying as the service to which this ServiceBinding is bound.
+  Connector* GetConnector();
+
+  // Binds this ServiceBinding to a new ServiceRequest. Once a ServiceBinding
+  // is bound, its target Service will begin receiving Service events. The
+  // order of events received is:
+  //
+  //   - OnStart() exactly once
+  //   - OnIdentityKnown() exactly once
+  //   - OnBindInterface() zero or more times
+  //
+  // The target Service will be able to receive these events until this
+  // ServiceBinding is either unbound or destroyed.
+  //
+  // If |request| is invalid, this call does nothing.
+  //
+  // Must only be called on an unbound ServiceBinding.
+  void Bind(mojom::ServiceRequest request);
+
+  // Asks the Service Manager nicely if it's OK for this service instance to
+  // disappear now. If the Service Manager thinks it's OK, it will sever the
+  // binding's connection, ultimately triggering an |OnDisconnected()| call on
+  // the bound Service object.
+  //
+  // Must only be called on a bound ServiceBinding.
+  void RequestClose();
+
+  // Immediately severs the connection to the Service Manager. No further
+  // incoming interface requests will be received until this ServiceBinding is
+  // bound again. Always prefer |RequestClose()| under normal circumstances,
+  // unless |OnDisconnected()| has already been invoked on the Service. See the
+  // note in the class documentation above regarding graceful binding closure.
+  //
+  // Must only be called on a bound ServiceBinding.
+  void Close();
+
+ private:
+  void OnConnectionError();
+
+  // mojom::Service:
+  void OnStart(const Identity& identity, OnStartCallback callback) override;
+  void OnBindInterface(const BindSourceInfo& source_info,
+                       const std::string& interface_name,
+                       mojo::ScopedMessagePipeHandle interface_pipe,
+                       OnBindInterfaceCallback callback) override;
+
+  // The Service instance to which all incoming events from the Service Manager
+  // should be directed. Typically this is the object which owns this
+  // ServiceBinding.
+  service_manager::Service* const service_;
+
+  // A pending Connector request which will eventually be passed to the Service
+  // Manager. Created preemptively by every unbound ServiceBinding so that
+  // |connector()| may begin pipelining outgoing requests even before the
+  // ServiceBinding is bound to a ServiceRequest.
+  mojom::ConnectorRequest pending_connector_request_;
+
+  mojo::Binding<mojom::Service> binding_;
+  Identity identity_;
+  std::unique_ptr<Connector> connector_;
+
+  // This instance's control interface to the service manager. Note that this
+  // is unbound and therefore invalid until OnStart() is called.
+  mojom::ServiceControlAssociatedPtr service_control_;
+
+  // Tracks whether |RequestClose()| has been called at least once prior to
+  // receiving |OnStart()| on a bound ServiceBinding. This ensures that the
+  // closure request is actually issued once |OnStart()| is invoked.
+  bool request_closure_on_start_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(ServiceBinding);
+};
+
+}  // namespace service_manager
+
+#endif  // SERVICES_SERVICE_MANAGER_PUBLIC_CPP_SERVICE_CONTEXT_H_
diff --git a/services/service_manager/tests/connect/connect_test_package.cc b/services/service_manager/tests/connect/connect_test_package.cc
index 45a4cb1..acf9cfff4 100644
--- a/services/service_manager/tests/connect/connect_test_package.cc
+++ b/services/service_manager/tests/connect/connect_test_package.cc
@@ -10,6 +10,7 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
+#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/threading/simple_thread.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
@@ -17,8 +18,7 @@
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_context.h"
-#include "services/service_manager/public/cpp/service_runner.h"
+#include "services/service_manager/public/cpp/service_binding.h"
 #include "services/service_manager/public/mojom/service_factory.mojom.h"
 #include "services/service_manager/tests/connect/connect_test.mojom.h"
 
@@ -74,6 +74,7 @@
     registry_.AddInterface<test::mojom::UserIdTest>(base::Bind(
         &ProvidedService::BindUserIdTestRequest, base::Unretained(this)));
   }
+
   void OnBindInterface(const BindSourceInfo& source_info,
                        const std::string& interface_name,
                        mojo::ScopedMessagePipeHandle interface_pipe) override {
@@ -81,6 +82,11 @@
                             source_info);
   }
 
+  void OnDisconnected() override {
+    service_binding_.Close();
+    run_loop_->Quit();
+  }
+
   void BindConnectTestServiceRequest(
       test::mojom::ConnectTestServiceRequest request,
       const BindSourceInfo& source_info) {
@@ -88,10 +94,11 @@
     test::mojom::ConnectionStatePtr state(test::mojom::ConnectionState::New());
     state->connection_remote_name = source_info.identity.name();
     state->connection_remote_userid = source_info.identity.user_id();
-    state->initialize_local_name = context()->identity().name();
-    state->initialize_userid = context()->identity().user_id();
+    state->initialize_local_name = service_binding_.identity().name();
+    state->initialize_userid = service_binding_.identity().user_id();
 
-    context()->connector()->BindInterface(source_info.identity, &caller_);
+    service_binding_.GetConnector()->BindInterface(source_info.identity,
+                                                   &caller_);
     caller_->ConnectionAccepted(std::move(state));
   }
 
@@ -111,7 +118,7 @@
   }
 
   void GetInstance(GetInstanceCallback callback) override {
-    std::move(callback).Run(context()->identity().instance());
+    std::move(callback).Run(service_binding_.identity().instance());
   }
 
   // test::mojom::BlockedInterface:
@@ -123,12 +130,12 @@
   void ConnectToClassAppAsDifferentUser(
       const service_manager::Identity& target,
       ConnectToClassAppAsDifferentUserCallback callback) override {
-    context()->connector()->StartService(target);
+    service_binding_.GetConnector()->StartService(target);
     mojom::ConnectResult result;
     Identity resolved_identity;
     {
       base::RunLoop loop(base::RunLoop::Type::kNestableTasksAllowed);
-      Connector::TestApi test_api(context()->connector());
+      Connector::TestApi test_api(service_binding_.GetConnector());
       test_api.SetStartServiceCallback(
           base::Bind(&QuitLoop, &loop, &result, &resolved_identity));
       loop.Run();
@@ -138,8 +145,13 @@
 
   // base::SimpleThread:
   void Run() override {
-    ServiceRunner(new ForwardingService(this)).Run(
-        request_.PassMessagePipe().release().value(), false);
+    base::MessageLoop message_loop;
+    base::RunLoop run_loop;
+    run_loop_ = &run_loop;
+    service_binding_.Bind(std::move(request_));
+    run_loop.Run();
+    run_loop_ = nullptr;
+
     caller_.reset();
     bindings_.CloseAllBindings();
     blocked_bindings_.CloseAllBindings();
@@ -147,10 +159,15 @@
   }
 
   void OnConnectionError() {
-    if (bindings_.empty())
-      context()->QuitNow();
+    if (bindings_.empty()) {
+      if (service_binding_.is_bound())
+        service_binding_.Close();
+      run_loop_->Quit();
+    }
   }
 
+  base::RunLoop* run_loop_;
+  service_manager::ServiceBinding service_binding_{this};
   const std::string title_;
   mojom::ServiceRequest request_;
   test::mojom::ExposedInterfacePtr caller_;
@@ -166,8 +183,11 @@
                            public mojom::ServiceFactory,
                            public test::mojom::ConnectTestService {
  public:
-  ConnectTestService() {}
-  ~ConnectTestService() override {}
+  ConnectTestService(service_manager::mojom::ServiceRequest request,
+                     base::OnceClosure quit_closure)
+      : service_binding_(this, std::move(request)),
+        quit_closure_(std::move(quit_closure)) {}
+  ~ConnectTestService() override = default;
 
  private:
   // service_manager::Service:
@@ -184,15 +204,16 @@
         base::Bind(&ConnectTestService::BindConnectTestServiceRequest,
                    base::Unretained(this)));
   }
+
   void OnBindInterface(const BindSourceInfo& source_info,
                        const std::string& interface_name,
                        mojo::ScopedMessagePipeHandle interface_pipe) override {
     registry_.BindInterface(interface_name, std::move(interface_pipe));
   }
 
-  bool OnServiceManagerConnectionLost() override {
+  void OnDisconnected() override {
     provided_services_.clear();
-    return true;
+    std::move(quit_closure_).Run();
   }
 
   void BindServiceFactoryRequest(mojom::ServiceFactoryRequest request) {
@@ -223,14 +244,16 @@
   }
 
   void GetInstance(GetInstanceCallback callback) override {
-    std::move(callback).Run(context()->identity().instance());
+    std::move(callback).Run(service_binding_.identity().instance());
   }
 
   void OnConnectionError() {
     if (bindings_.empty() && service_factory_bindings_.empty())
-      context()->CreateQuitClosure().Run();
+      service_binding_.RequestClose();
   }
 
+  service_manager::ServiceBinding service_binding_;
+  base::OnceClosure quit_closure_;
   std::vector<std::unique_ptr<Service>> delegates_;
   mojo::BindingSet<mojom::ServiceFactory> service_factory_bindings_;
   BinderRegistry registry_;
@@ -243,7 +266,12 @@
 }  // namespace service_manager
 
 MojoResult ServiceMain(MojoHandle service_request_handle) {
-  service_manager::ServiceRunner runner(
-      new service_manager::ConnectTestService);
-  return runner.Run(service_request_handle);
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  service_manager::ConnectTestService service(
+      service_manager::mojom::ServiceRequest(mojo::MakeScopedHandle(
+          mojo::MessagePipeHandle(service_request_handle))),
+      run_loop.QuitClosure());
+  run_loop.Run();
+  return MOJO_RESULT_OK;
 }
diff --git a/services/service_manager/tests/lifecycle/app.cc b/services/service_manager/tests/lifecycle/app.cc
index b30a7799..9a930dc 100644
--- a/services/service_manager/tests/lifecycle/app.cc
+++ b/services/service_manager/tests/lifecycle/app.cc
@@ -2,11 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
 #include "services/service_manager/public/c/main.h"
-#include "services/service_manager/public/cpp/service_runner.h"
+#include "services/service_manager/public/mojom/service.mojom.h"
 #include "services/service_manager/tests/lifecycle/app_client.h"
 
 MojoResult ServiceMain(MojoHandle service_request_handle) {
-  service_manager::ServiceRunner runner(new service_manager::test::AppClient);
-  return runner.Run(service_request_handle);
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  service_manager::test::AppClient app_client(
+      service_manager::mojom::ServiceRequest(mojo::MakeScopedHandle(
+          mojo::MessagePipeHandle(service_request_handle))),
+      run_loop.QuitClosure());
+  run_loop.Run();
+  return MOJO_RESULT_OK;
 }
diff --git a/services/service_manager/tests/lifecycle/app_client.cc b/services/service_manager/tests/lifecycle/app_client.cc
index 20804e8..3115341 100644
--- a/services/service_manager/tests/lifecycle/app_client.cc
+++ b/services/service_manager/tests/lifecycle/app_client.cc
@@ -6,14 +6,20 @@
 
 #include "base/macros.h"
 #include "base/run_loop.h"
-#include "services/service_manager/public/cpp/service_context.h"
+#include "services/service_manager/public/cpp/service_binding.h"
 
 namespace service_manager {
 namespace test {
 
-AppClient::AppClient() {
+AppClient::AppClient(service_manager::mojom::ServiceRequest request,
+                     base::OnceClosure quit_closure)
+    : service_binding_(this, std::move(request)),
+      quit_closure_(std::move(quit_closure)) {
+  bindings_.set_connection_error_handler(base::BindRepeating(
+      &AppClient::LifecycleControlBindingLost, base::Unretained(this)));
+
   registry_.AddInterface<mojom::LifecycleControl>(
-      base::Bind(&AppClient::Create, base::Unretained(this)));
+      base::BindRepeating(&AppClient::Create, base::Unretained(this)));
 }
 
 AppClient::~AppClient() {}
@@ -24,9 +30,12 @@
   registry_.BindInterface(interface_name, std::move(interface_pipe));
 }
 
-bool AppClient::OnServiceManagerConnectionLost() {
-  context()->QuitNow();
-  return true;
+void AppClient::OnDisconnected() {
+  DCHECK(service_binding_.is_bound());
+  service_binding_.Close();
+
+  if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 void AppClient::Create(mojom::LifecycleControlRequest request) {
@@ -38,7 +47,10 @@
 }
 
 void AppClient::GracefulQuit() {
-  context()->CreateQuitClosure().Run();
+  if (service_binding_.is_bound())
+    service_binding_.RequestClose();
+  else if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 void AppClient::Crash() {
@@ -49,14 +61,13 @@
 }
 
 void AppClient::CloseServiceManagerConnection() {
-  context()->DisconnectFromServiceManager();
-  bindings_.set_connection_error_handler(
-      base::Bind(&AppClient::BindingLost, base::Unretained(this)));
+  if (service_binding_.is_bound())
+    service_binding_.Close();
 }
 
-void AppClient::BindingLost() {
-  if (bindings_.empty())
-    OnServiceManagerConnectionLost();
+void AppClient::LifecycleControlBindingLost() {
+  if (!service_binding_.is_bound() && bindings_.empty() && quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 }  // namespace test
diff --git a/services/service_manager/tests/lifecycle/app_client.h b/services/service_manager/tests/lifecycle/app_client.h
index f425446..812a28b2 100644
--- a/services/service_manager/tests/lifecycle/app_client.h
+++ b/services/service_manager/tests/lifecycle/app_client.h
@@ -8,11 +8,12 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_runner.h"
+#include "services/service_manager/public/cpp/service_binding.h"
 #include "services/service_manager/public/mojom/service.mojom.h"
 #include "services/service_manager/tests/lifecycle/lifecycle_unittest.mojom.h"
 
@@ -22,16 +23,15 @@
 class AppClient : public Service,
                   public mojom::LifecycleControl {
  public:
-  AppClient();
+  explicit AppClient(service_manager::mojom::ServiceRequest request,
+                     base::OnceClosure quit_closure);
   ~AppClient() override;
 
-  void set_runner(ServiceRunner* runner) { runner_ = runner; }
-
   // Service:
   void OnBindInterface(const BindSourceInfo& source_info,
                        const std::string& interface_name,
                        mojo::ScopedMessagePipeHandle interface_pipe) override;
-  bool OnServiceManagerConnectionLost() override;
+  void OnDisconnected() override;
 
   void Create(mojom::LifecycleControlRequest request);
 
@@ -44,9 +44,11 @@
  private:
   class ServiceImpl;
 
-  void BindingLost();
+  void LifecycleControlBindingLost();
 
-  ServiceRunner* runner_ = nullptr;
+  ServiceBinding service_binding_;
+  base::OnceClosure quit_closure_;
+
   BinderRegistry registry_;
   mojo::BindingSet<mojom::LifecycleControl> bindings_;
 
diff --git a/services/service_manager/tests/lifecycle/lifecycle_exe.cc b/services/service_manager/tests/lifecycle/lifecycle_exe.cc
index f5ab4cd..92f409f 100644
--- a/services/service_manager/tests/lifecycle/lifecycle_exe.cc
+++ b/services/service_manager/tests/lifecycle/lifecycle_exe.cc
@@ -2,11 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
 #include "services/service_manager/public/c/main.h"
-#include "services/service_manager/public/cpp/service_runner.h"
+#include "services/service_manager/public/mojom/service.mojom.h"
 #include "services/service_manager/tests/lifecycle/app_client.h"
 
 MojoResult ServiceMain(MojoHandle service_request_handle) {
-  service_manager::ServiceRunner runner(new service_manager::test::AppClient);
-  return runner.Run(service_request_handle);
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  service_manager::test::AppClient app_client(
+      service_manager::mojom::ServiceRequest(mojo::MakeScopedHandle(
+          mojo::MessagePipeHandle(service_request_handle))),
+      run_loop.QuitClosure());
+  run_loop.Run();
+  return MOJO_RESULT_OK;
 }
diff --git a/services/service_manager/tests/lifecycle/lifecycle_unittest.cc b/services/service_manager/tests/lifecycle/lifecycle_unittest.cc
index f43e7435..44e6afa 100644
--- a/services/service_manager/tests/lifecycle/lifecycle_unittest.cc
+++ b/services/service_manager/tests/lifecycle/lifecycle_unittest.cc
@@ -135,7 +135,6 @@
   // test::ServiceTest:
   void SetUp() override {
     test::ServiceTest::SetUp();
-    InitPackage();
     instances_ = TrackInstances();
   }
   void TearDown() override {
@@ -147,14 +146,6 @@
     return !base::CommandLine::ForCurrentProcess()->HasSwitch("single-process");
   }
 
-  void InitPackage() {
-    test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageName);
-    base::RunLoop loop;
-    lifecycle.set_connection_error_handler(loop.QuitClosure());
-    lifecycle->GracefulQuit();
-    loop.Run();
-  }
-
   test::mojom::LifecycleControlPtr ConnectTo(const std::string& name) {
     test::mojom::LifecycleControlPtr lifecycle;
     connector()->BindInterface(name, &lifecycle);
diff --git a/services/service_manager/tests/lifecycle/package.cc b/services/service_manager/tests/lifecycle/package.cc
index ad17e1fb..2d942d3 100644
--- a/services/service_manager/tests/lifecycle/package.cc
+++ b/services/service_manager/tests/lifecycle/package.cc
@@ -7,12 +7,12 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
+#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/service_manager/public/c/main.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/cpp/service_context.h"
-#include "services/service_manager/public/cpp/service_runner.h"
+#include "services/service_manager/public/cpp/service_binding.h"
 #include "services/service_manager/public/mojom/service_factory.mojom.h"
 #include "services/service_manager/tests/lifecycle/app_client.h"
 #include "services/service_manager/tests/lifecycle/lifecycle_unittest.mojom.h"
@@ -22,19 +22,20 @@
 class PackagedApp : public service_manager::Service,
                     public service_manager::test::mojom::LifecycleControl {
  public:
-  PackagedApp(
-      const base::Closure& service_manager_connection_closed_callback,
-      const base::Closure& destruct_callback)
-      : service_manager_connection_closed_callback_(
-            service_manager_connection_closed_callback),
-        destruct_callback_(destruct_callback) {
-    bindings_.set_connection_error_handler(base::Bind(&PackagedApp::BindingLost,
-                                                      base::Unretained(this)));
+  PackagedApp(service_manager::mojom::ServiceRequest request,
+              base::OnceClosure service_manager_connection_closed_callback,
+              base::OnceClosure destruct_callback)
+      : service_binding_(this, std::move(request)),
+        service_manager_connection_closed_callback_(
+            std::move(service_manager_connection_closed_callback)),
+        destruct_callback_(std::move(destruct_callback)) {
+    bindings_.set_connection_error_handler(
+        base::BindRepeating(&PackagedApp::MaybeQuit, base::Unretained(this)));
     registry_.AddInterface<service_manager::test::mojom::LifecycleControl>(
         base::Bind(&PackagedApp::Create, base::Unretained(this)));
   }
 
-  ~PackagedApp() override {}
+  ~PackagedApp() override = default;
 
  private:
   // service_manager::Service:
@@ -44,6 +45,11 @@
     registry_.BindInterface(interface_name, std::move(interface_pipe));
   }
 
+  void OnDisconnected() override {
+    std::move(service_manager_connection_closed_callback_).Run();
+    std::move(destruct_callback_).Run();
+  }
+
   void Create(service_manager::test::mojom::LifecycleControlRequest request) {
     bindings_.AddBinding(this, std::move(request));
   }
@@ -51,12 +57,7 @@
   // LifecycleControl:
   void Ping(PingCallback callback) override { std::move(callback).Run(); }
 
-  void GracefulQuit() override {
-    service_manager_connection_closed_callback_.Run();
-
-    // Deletes |this|.
-    destruct_callback_.Run();
-  }
+  void GracefulQuit() override { service_binding_.RequestClose(); }
 
   void Crash() override {
     // When multiple instances are vended from the same package instance, this
@@ -65,50 +66,68 @@
   }
 
   void CloseServiceManagerConnection() override {
-    service_manager_connection_closed_callback_.Run();
-    context()->QuitNow();
+    std::move(service_manager_connection_closed_callback_).Run();
+
+    if (service_binding_.is_bound())
+      service_binding_.Close();
+
     // This only closed our relationship with the service manager, existing
     // |bindings_| remain active.
+    MaybeQuit();
   }
 
-  void BindingLost() {
-    if (bindings_.empty()) {
-      // Deletes |this|.
-      destruct_callback_.Run();
-    }
+  void MaybeQuit() {
+    if (service_binding_.is_bound() || !bindings_.empty())
+      return;
+
+    // Deletes |this|.
+    std::move(destruct_callback_).Run();
   }
 
+  service_manager::ServiceBinding service_binding_;
+
   service_manager::BinderRegistry registry_;
   mojo::BindingSet<service_manager::test::mojom::LifecycleControl> bindings_;
 
   // Run when this object's connection to the service manager is closed.
-  base::Closure service_manager_connection_closed_callback_;
+  base::OnceClosure service_manager_connection_closed_callback_;
   // Run when this object is destructed.
-  base::Closure destruct_callback_;
+  base::OnceClosure destruct_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(PackagedApp);
 };
 
-class Package : public service_manager::ForwardingService,
+class Package : public service_manager::Service,
                 public service_manager::mojom::ServiceFactory {
  public:
-  Package() : ForwardingService(&app_client_) {
+  explicit Package(service_manager::mojom::ServiceRequest request,
+                   base::OnceClosure quit_closure)
+      : service_binding_(this, std::move(request)),
+        quit_closure_(std::move(quit_closure)),
+        app_client_(service_manager::mojom::ServiceRequest(),
+                    base::BindOnce(&Package::QuitFromAppClient,
+                                   base::Unretained(this))) {
     registry_.AddInterface<service_manager::mojom::ServiceFactory>(
-        base::Bind(&Package::Create, base::Unretained(this)));
+        base::BindRepeating(&Package::Create, base::Unretained(this)));
   }
-  ~Package() override {}
+
+  ~Package() override = default;
 
  private:
-  // ForwardingService:
+  void QuitFromAppClient() { std::move(quit_closure_).Run(); }
+
+  // service_manager::Service:
   void OnBindInterface(const service_manager::BindSourceInfo& source_info,
                        const std::string& interface_name,
                        mojo::ScopedMessagePipeHandle interface_pipe) override {
     if (!registry_.TryBindInterface(interface_name, &interface_pipe)) {
-      ForwardingService::OnBindInterface(source_info, interface_name,
-                                         std::move(interface_pipe));
+      app_client_.OnBindInterface(source_info, interface_name,
+                                  std::move(interface_pipe));
     }
   }
 
+  void OnDisconnected() override { std::move(quit_closure_).Run(); }
+
   void Create(service_manager::mojom::ServiceFactoryRequest request) {
     bindings_.AddBinding(this, std::move(request));
   }
@@ -120,48 +139,35 @@
       service_manager::mojom::PIDReceiverPtr pid_receiver) override {
     ++service_manager_connection_refcount_;
     int id = next_id_++;
-    std::unique_ptr<service_manager::ServiceContext> context =
-        std::make_unique<service_manager::ServiceContext>(
-            std::make_unique<PackagedApp>(
-                base::Bind(&Package::AppServiceManagerConnectionClosed,
-                           base::Unretained(this)),
-                base::Bind(&Package::DestroyService, base::Unretained(this),
-                           id)),
-            std::move(request));
-    service_manager::ServiceContext* raw_context = context.get();
-    contexts_.insert(std::make_pair(raw_context, std::move(context)));
-    id_to_context_.insert(std::make_pair(id, raw_context));
+    auto app = std::make_unique<PackagedApp>(
+        std::move(request),
+        base::BindOnce(&Package::OnAppInstanceDisconnected,
+                       base::Unretained(this)),
+        base::BindOnce(&Package::DestroyAppInstance, base::Unretained(this),
+                       id));
+    app_instances_.emplace(id, std::move(app));
   }
 
-  void AppServiceManagerConnectionClosed() {
-    if (!--service_manager_connection_refcount_)
-      app_client_.CloseServiceManagerConnection();
+  void OnAppInstanceDisconnected() {
+    if (--service_manager_connection_refcount_ == 0)
+      service_binding_.RequestClose();
   }
 
-  void DestroyService(int id) {
-    auto id_it = id_to_context_.find(id);
-    DCHECK(id_it != id_to_context_.end());
-
-    auto it = contexts_.find(id_it->second);
-    DCHECK(it != contexts_.end());
-    contexts_.erase(it);
-    id_to_context_.erase(id_it);
-    if (contexts_.empty() && base::RunLoop::IsRunningOnCurrentThread())
-      context()->QuitNow();
+  void DestroyAppInstance(int id) {
+    app_instances_.erase(id);
+    if (app_instances_.empty())
+      std::move(quit_closure_).Run();
   }
 
+  service_manager::ServiceBinding service_binding_;
+  base::OnceClosure quit_closure_;
   service_manager::test::AppClient app_client_;
   int service_manager_connection_refcount_ = 0;
   service_manager::BinderRegistry registry_;
   mojo::BindingSet<service_manager::mojom::ServiceFactory> bindings_;
 
-  using ServiceContextMap =
-      std::map<service_manager::ServiceContext*,
-               std::unique_ptr<service_manager::ServiceContext>>;
-  ServiceContextMap contexts_;
-
   int next_id_ = 0;
-  std::map<int, service_manager::ServiceContext*> id_to_context_;
+  std::map<int, std::unique_ptr<PackagedApp>> app_instances_;
 
   DISALLOW_COPY_AND_ASSIGN(Package);
 };
@@ -169,6 +175,11 @@
 }  // namespace
 
 MojoResult ServiceMain(MojoHandle service_request_handle) {
-  service_manager::ServiceRunner runner(new Package);
-  return runner.Run(service_request_handle);
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+  Package package(service_manager::mojom::ServiceRequest(mojo::MakeScopedHandle(
+                      mojo::MessagePipeHandle(service_request_handle))),
+                  run_loop.QuitClosure());
+  run_loop.Run();
+  return MOJO_RESULT_OK;
 }
diff --git a/services/viz/public/cpp/compositing/BUILD.gn b/services/viz/public/cpp/compositing/BUILD.gn
index 4f61f0a..4b9c524 100644
--- a/services/viz/public/cpp/compositing/BUILD.gn
+++ b/services/viz/public/cpp/compositing/BUILD.gn
@@ -47,5 +47,6 @@
     "//ui/gfx",
     "//ui/gfx:test_support",
     "//ui/gfx/geometry",
+    "//ui/gfx/geometry/mojo:struct_traits",
   ]
 }
diff --git a/services/viz/public/cpp/compositing/transferable_resource.typemap b/services/viz/public/cpp/compositing/transferable_resource.typemap
index d286183..e41b212 100644
--- a/services/viz/public/cpp/compositing/transferable_resource.typemap
+++ b/services/viz/public/cpp/compositing/transferable_resource.typemap
@@ -14,4 +14,5 @@
 type_mappings = [ "viz.mojom.TransferableResource=viz::TransferableResource" ]
 deps = [
   "//gpu/ipc/common:struct_traits",
+  "//ui/gfx/geometry/mojo:struct_traits",
 ]
diff --git a/services/ws/gpu_host/BUILD.gn b/services/ws/gpu_host/BUILD.gn
index 51401a3..a7cb7eb 100644
--- a/services/ws/gpu_host/BUILD.gn
+++ b/services/ws/gpu_host/BUILD.gn
@@ -4,8 +4,6 @@
 
 source_set("gpu_host") {
   sources = [
-    "gpu_client.cc",
-    "gpu_client.h",
     "gpu_host.cc",
     "gpu_host.h",
     "gpu_host_delegate.h",
@@ -19,8 +17,10 @@
     "//components/viz/service/main",  # TODO(sad): Temporary until GPU process split.
     "//gpu/command_buffer/client",
     "//gpu/command_buffer/client:gles2_interface",
+    "//gpu/command_buffer/service",
     "//gpu/ipc/client",
     "//gpu/ipc/common",
+    "//gpu/ipc/host",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//services/service_manager/public/cpp",
@@ -51,6 +51,23 @@
   ]
 }
 
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "gpu_host_test_api.cc",
+    "gpu_host_test_api.h",
+  ]
+
+  deps = [
+    ":gpu_host",
+    "//base",
+    "//components/viz/host",
+    "//components/viz/test:test_support",
+    "//services/viz/privileged/interfaces",
+  ]
+}
+
 source_set("tests") {
   testonly = true
 
@@ -60,6 +77,7 @@
 
   deps = [
     ":gpu_host",
+    ":test_support",
     "//base",
     "//base/test:test_config",
     "//base/test:test_support",
diff --git a/services/ws/gpu_host/DEPS b/services/ws/gpu_host/DEPS
index e9b718d3..988a583 100644
--- a/services/ws/gpu_host/DEPS
+++ b/services/ws/gpu_host/DEPS
@@ -2,10 +2,13 @@
   "+base",
   "+components/viz/common",
   "+components/viz/host",
+  "+components/viz/test",
   "+gpu/command_buffer/client",
+  "+gpu/command_buffer/service/gpu_switches.h",
   "+gpu/config",
   "+gpu/ipc/client",
   "+gpu/ipc/common",
+  "+gpu/ipc/host",
   "+mojo/public",
   "+services/viz/privileged/interfaces",
   "+services/viz/public/interfaces",
diff --git a/services/ws/gpu_host/gpu_client.cc b/services/ws/gpu_host/gpu_client.cc
deleted file mode 100644
index 0c9d9190..0000000
--- a/services/ws/gpu_host/gpu_client.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/ws/gpu_host/gpu_client.h"
-
-#include "components/viz/host/host_gpu_memory_buffer_manager.h"
-#include "services/viz/privileged/interfaces/gl/gpu_service.mojom.h"
-
-namespace ws {
-namespace gpu_host {
-
-GpuClient::GpuClient(int client_id,
-                     gpu::GPUInfo* gpu_info,
-                     gpu::GpuFeatureInfo* gpu_feature_info,
-                     viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager,
-                     viz::mojom::GpuService* gpu_service)
-    : client_id_(client_id),
-      gpu_info_(gpu_info),
-      gpu_feature_info_(gpu_feature_info),
-      gpu_memory_buffer_manager_(gpu_memory_buffer_manager),
-      gpu_service_(gpu_service),
-      weak_factory_(this) {
-  DCHECK(gpu_memory_buffer_manager_);
-  DCHECK(gpu_service_);
-}
-
-GpuClient::~GpuClient() {
-  gpu_memory_buffer_manager_->DestroyAllGpuMemoryBufferForClient(client_id_);
-  if (!establish_callback_.is_null()) {
-    std::move(establish_callback_)
-        .Run(client_id_, mojo::ScopedMessagePipeHandle(), gpu::GPUInfo(),
-             gpu::GpuFeatureInfo());
-  }
-}
-
-void GpuClient::OnGpuChannelEstablished(
-    mojo::ScopedMessagePipeHandle channel_handle) {
-  base::ResetAndReturn(&establish_callback_)
-      .Run(client_id_, std::move(channel_handle), *gpu_info_,
-           *gpu_feature_info_);
-}
-
-// mojom::Gpu overrides:
-void GpuClient::EstablishGpuChannel(EstablishGpuChannelCallback callback) {
-  // TODO(sad): https://crbug.com/617415 figure out how to generate a meaningful
-  // tracing id.
-  const uint64_t client_tracing_id = 0;
-  constexpr bool is_gpu_host = false;
-  if (!establish_callback_.is_null()) {
-    std::move(establish_callback_)
-        .Run(client_id_, mojo::ScopedMessagePipeHandle(), gpu::GPUInfo(),
-             gpu::GpuFeatureInfo());
-  }
-  establish_callback_ = std::move(callback);
-  const bool cache_shaders_on_disk = true;
-  gpu_service_->EstablishGpuChannel(
-      client_id_, client_tracing_id, is_gpu_host, cache_shaders_on_disk,
-      base::Bind(&GpuClient::OnGpuChannelEstablished,
-                 weak_factory_.GetWeakPtr()));
-}
-
-void GpuClient::CreateJpegDecodeAccelerator(
-    media::mojom::JpegDecodeAcceleratorRequest jda_request) {
-  gpu_service_->CreateJpegDecodeAccelerator(std::move(jda_request));
-}
-
-void GpuClient::CreateVideoEncodeAcceleratorProvider(
-    media::mojom::VideoEncodeAcceleratorProviderRequest request) {
-  gpu_service_->CreateVideoEncodeAcceleratorProvider(std::move(request));
-}
-
-void GpuClient::CreateGpuMemoryBuffer(
-    gfx::GpuMemoryBufferId id,
-    const gfx::Size& size,
-    gfx::BufferFormat format,
-    gfx::BufferUsage usage,
-    mojom::GpuMemoryBufferFactory::CreateGpuMemoryBufferCallback callback) {
-  gpu_memory_buffer_manager_->AllocateGpuMemoryBuffer(
-      id, client_id_, size, format, usage, gpu::kNullSurfaceHandle,
-      std::move(callback));
-}
-
-void GpuClient::DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
-                                       const gpu::SyncToken& sync_token) {
-  gpu_memory_buffer_manager_->DestroyGpuMemoryBuffer(id, client_id_,
-                                                     sync_token);
-}
-
-void GpuClient::CreateGpuMemoryBufferFactory(
-    mojom::GpuMemoryBufferFactoryRequest request) {
-  gpu_memory_buffer_factory_bindings_.AddBinding(this, std::move(request));
-}
-
-}  // namespace gpu_host
-}  // namespace ws
diff --git a/services/ws/gpu_host/gpu_client.h b/services/ws/gpu_host/gpu_client.h
deleted file mode 100644
index a4507d08..0000000
--- a/services/ws/gpu_host/gpu_client.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_WS_GPU_HOST_GPU_CLIENT_H_
-#define SERVICES_WS_GPU_HOST_GPU_CLIENT_H_
-
-#include "base/memory/weak_ptr.h"
-#include "gpu/config/gpu_feature_info.h"
-#include "gpu/config/gpu_info.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "services/ws/public/mojom/gpu.mojom.h"
-
-namespace viz {
-namespace mojom {
-class GpuService;
-}  // namespace mojom
-
-class HostGpuMemoryBufferManager;
-}  // namespace viz
-
-namespace ws {
-namespace gpu_host {
-
-namespace test {
-class GpuHostTest;
-}  // namespace test
-
-// The implementation that relays requests from clients to the real
-// service implementation in the GPU process over mojom.GpuService.
-class GpuClient : public mojom::GpuMemoryBufferFactory, public mojom::Gpu {
- public:
-  GpuClient(int client_id,
-            gpu::GPUInfo* gpu_info,
-            gpu::GpuFeatureInfo* gpu_feature_info,
-            viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager,
-            viz::mojom::GpuService* gpu_service);
-  ~GpuClient() override;
-
- private:
-  friend class test::GpuHostTest;
-
-  // EstablishGpuChannelCallback:
-  void OnGpuChannelEstablished(mojo::ScopedMessagePipeHandle channel_handle);
-
-  // mojom::GpuMemoryBufferFactory overrides:
-  void CreateGpuMemoryBuffer(
-      gfx::GpuMemoryBufferId id,
-      const gfx::Size& size,
-      gfx::BufferFormat format,
-      gfx::BufferUsage usage,
-      mojom::GpuMemoryBufferFactory::CreateGpuMemoryBufferCallback callback)
-      override;
-  void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
-                              const gpu::SyncToken& sync_token) override;
-
-  // mojom::Gpu overrides:
-  void CreateGpuMemoryBufferFactory(
-      mojom::GpuMemoryBufferFactoryRequest request) override;
-  void EstablishGpuChannel(EstablishGpuChannelCallback callback) override;
-  void CreateJpegDecodeAccelerator(
-      media::mojom::JpegDecodeAcceleratorRequest jda_request) override;
-  void CreateVideoEncodeAcceleratorProvider(
-      media::mojom::VideoEncodeAcceleratorProviderRequest vea_provider_request)
-      override;
-
-  const int client_id_;
-  mojo::BindingSet<mojom::GpuMemoryBufferFactory>
-      gpu_memory_buffer_factory_bindings_;
-
-  // The objects these pointers refer to are owned by the GpuHost object.
-  const gpu::GPUInfo* gpu_info_;
-  const gpu::GpuFeatureInfo* gpu_feature_info_;
-  viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager_;
-  viz::mojom::GpuService* gpu_service_;
-  EstablishGpuChannelCallback establish_callback_;
-
-  base::WeakPtrFactory<GpuClient> weak_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(GpuClient);
-};
-
-}  // namespace gpu_host
-}  // namespace ws
-
-#endif  // SERVICES_WS_GPU_HOST_GPU_CLIENT_H_
diff --git a/services/ws/gpu_host/gpu_host.cc b/services/ws/gpu_host/gpu_host.cc
index e66b0f1f..a95e967 100644
--- a/services/ws/gpu_host/gpu_host.cc
+++ b/services/ws/gpu_host/gpu_host.cc
@@ -11,17 +11,20 @@
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/switches.h"
+#include "components/viz/host/gpu_client.h"
+#include "components/viz/host/gpu_client_delegate.h"
 #include "components/viz/host/host_gpu_memory_buffer_manager.h"
+#include "gpu/command_buffer/service/gpu_switches.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h"
 #include "gpu/ipc/common/gpu_memory_buffer_support.h"
+#include "gpu/ipc/host/shader_disk_cache.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/buffer.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/viz/privileged/interfaces/viz_main.mojom.h"
 #include "services/viz/public/interfaces/constants.mojom.h"
-#include "services/ws/gpu_host/gpu_client.h"
 #include "services/ws/gpu_host/gpu_host_delegate.h"
 #include "ui/gfx/buffer_format_util.h"
 
@@ -47,13 +50,46 @@
   return base::CommandLine::ForCurrentProcess()->HasSwitch(kEnableViz);
 }
 
+class GpuClientDelegate : public viz::GpuClientDelegate {
+ public:
+  GpuClientDelegate(viz::GpuHostImpl* gpu_host_impl,
+                    viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager);
+  ~GpuClientDelegate() override;
+
+  // viz::GpuClientDelegate:
+  viz::GpuHostImpl* EnsureGpuHost() override;
+  viz::HostGpuMemoryBufferManager* GetGpuMemoryBufferManager() override;
+
+ private:
+  viz::GpuHostImpl* gpu_host_impl_;
+  viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(GpuClientDelegate);
+};
+
+GpuClientDelegate::GpuClientDelegate(
+    viz::GpuHostImpl* gpu_host_impl,
+    viz::HostGpuMemoryBufferManager* gpu_memory_buffer_manager)
+    : gpu_host_impl_(gpu_host_impl),
+      gpu_memory_buffer_manager_(gpu_memory_buffer_manager) {}
+
+GpuClientDelegate::~GpuClientDelegate() = default;
+
+viz::GpuHostImpl* GpuClientDelegate::EnsureGpuHost() {
+  return gpu_host_impl_;
+}
+
+viz::HostGpuMemoryBufferManager*
+GpuClientDelegate::GetGpuMemoryBufferManager() {
+  return gpu_memory_buffer_manager_;
+}
+
 }  // namespace
 
-DefaultGpuHost::DefaultGpuHost(
-    GpuHostDelegate* delegate,
-    service_manager::Connector* connector,
-    discardable_memory::DiscardableSharedMemoryManager*
-        discardable_shared_memory_manager)
+GpuHost::GpuHost(GpuHostDelegate* delegate,
+                 service_manager::Connector* connector,
+                 discardable_memory::DiscardableSharedMemoryManager*
+                     discardable_shared_memory_manager)
     : delegate_(delegate),
       discardable_shared_memory_manager_(discardable_shared_memory_manager),
       next_client_id_(kInternalGpuChannelClientId + 1),
@@ -71,9 +107,9 @@
     // TODO(crbug.com/620927): This should be removed once ozone-mojo is done.
     gpu_thread_.Start();
     gpu_thread_.task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&DefaultGpuHost::InitializeVizMain,
-                                  base::Unretained(this),
-                                  base::Passed(MakeRequest(&viz_main_ptr))));
+        FROM_HERE,
+        base::BindOnce(&GpuHost::InitializeVizMain, base::Unretained(this),
+                       base::Passed(MakeRequest(&viz_main_ptr))));
   } else {
     // Currently, GPU is only run in process in OOP-Ash.
     NOTREACHED();
@@ -82,7 +118,9 @@
   viz::GpuHostImpl::InitParams params;
   params.restart_id = viz::BeginFrameSource::kNotRestartableId + 1;
   params.in_process = in_process;
-  params.disable_gpu_shader_disk_cache = true;
+  params.disable_gpu_shader_disk_cache =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableGpuShaderDiskCache);
   params.deadline_to_synchronize_surfaces =
       switches::GetDeadlineToSynchronizeSurfaces();
   params.main_thread_task_runner = main_thread_task_runner_;
@@ -92,8 +130,8 @@
 
 #if defined(OS_WIN)
   // For OS_WIN the process id for GPU is needed. Using GetCurrentProcessId()
-  // only works with in-process GPU, which is fine because DefaultGpuHost isn't
-  // used outside of tests.
+  // only works with in-process GPU, which is fine because GpuHost isn't used
+  // outside of tests.
   gpu_host_impl_->OnProcessLaunched(::GetCurrentProcessId());
 #endif
 
@@ -107,96 +145,82 @@
               gpu_host_impl_->gpu_service()),
           next_client_id_++, std::make_unique<gpu::GpuMemoryBufferSupport>(),
           main_thread_task_runner_);
+
+  shader_cache_factory_ = std::make_unique<gpu::ShaderCacheFactory>();
 }
 
-DefaultGpuHost::~DefaultGpuHost() {
+GpuHost::~GpuHost() {
   // TODO(crbug.com/620927): This should be removed once ozone-mojo is done.
   if (gpu_thread_.IsRunning()) {
     // Stop() will return after |viz_main_impl_| has been destroyed.
     gpu_thread_.task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&DefaultGpuHost::DestroyVizMain,
-                                  base::Unretained(this)));
+        FROM_HERE,
+        base::BindOnce(&GpuHost::DestroyVizMain, base::Unretained(this)));
     gpu_thread_.Stop();
   }
 
   viz::GpuHostImpl::ResetFontRenderParams();
 }
 
-void DefaultGpuHost::CreateFrameSinkManager(
+void GpuHost::CreateFrameSinkManager(
     viz::mojom::FrameSinkManagerRequest request,
     viz::mojom::FrameSinkManagerClientPtrInfo client) {
   gpu_host_impl_->ConnectFrameSinkManager(std::move(request),
                                           std::move(client));
 }
 
-void DefaultGpuHost::Shutdown() {
+void GpuHost::Shutdown() {
   gpu_host_impl_.reset();
 
-  gpu_bindings_.CloseAllBindings();
+  gpu_clients_.clear();
 }
 
-void DefaultGpuHost::Add(mojom::GpuRequest request) {
-  AddInternal(std::move(request));
-}
-
-void DefaultGpuHost::OnAcceleratedWidgetAvailable(
-    gfx::AcceleratedWidget widget) {
-#if defined(OS_WIN)
-  gfx::RenderingWindowManager::GetInstance()->RegisterParent(widget);
-#endif
-}
-
-void DefaultGpuHost::OnAcceleratedWidgetDestroyed(
-    gfx::AcceleratedWidget widget) {
-#if defined(OS_WIN)
-  gfx::RenderingWindowManager::GetInstance()->UnregisterParent(widget);
-#endif
+void GpuHost::Add(mojom::GpuRequest request) {
+  const int client_id = next_client_id_++;
+  const uint64_t client_tracing_id = 0;
+  auto client = std::make_unique<viz::GpuClient>(
+      std::make_unique<GpuClientDelegate>(gpu_host_impl_.get(),
+                                          gpu_memory_buffer_manager_.get()),
+      client_id, client_tracing_id, main_thread_task_runner_);
+  client->Add(std::move(request));
+  gpu_clients_.push_back(std::move(client));
 }
 
 #if defined(OS_CHROMEOS)
-void DefaultGpuHost::AddArc(mojom::ArcRequest request) {
+void GpuHost::AddArc(mojom::ArcRequest request) {
   arc_bindings_.AddBinding(
       std::make_unique<ArcClient>(gpu_host_impl_->gpu_service()),
       std::move(request));
 }
 #endif  // defined(OS_CHROMEOS)
 
-GpuClient* DefaultGpuHost::AddInternal(mojom::GpuRequest request) {
-  auto client(std::make_unique<GpuClient>(
-      next_client_id_++, &gpu_info_, &gpu_feature_info_,
-      gpu_memory_buffer_manager_.get(), gpu_host_impl_->gpu_service()));
-  GpuClient* client_ref = client.get();
-  gpu_bindings_.AddBinding(std::move(client), std::move(request));
-  return client_ref;
-}
-
-void DefaultGpuHost::OnBadMessageFromGpu() {
+void GpuHost::OnBadMessageFromGpu() {
   // TODO(sad): Received some unexpected message from the gpu process. We
   // should kill the process and restart it.
   NOTIMPLEMENTED();
 }
 
-void DefaultGpuHost::InitializeVizMain(viz::mojom::VizMainRequest request) {
+void GpuHost::InitializeVizMain(viz::mojom::VizMainRequest request) {
   viz::VizMainImpl::ExternalDependencies deps;
   deps.create_display_compositor = true;
   viz_main_impl_ = std::make_unique<viz::VizMainImpl>(nullptr, std::move(deps));
   viz_main_impl_->Bind(std::move(request));
 }
 
-void DefaultGpuHost::DestroyVizMain() {
+void GpuHost::DestroyVizMain() {
   DCHECK(viz_main_impl_);
   viz_main_impl_.reset();
 }
 
-gpu::GPUInfo DefaultGpuHost::GetGPUInfo() const {
+gpu::GPUInfo GpuHost::GetGPUInfo() const {
   return gpu_info_;
 }
 
-gpu::GpuFeatureInfo DefaultGpuHost::GetGpuFeatureInfo() const {
+gpu::GpuFeatureInfo GpuHost::GetGpuFeatureInfo() const {
   return gpu_feature_info_;
 }
 
-void DefaultGpuHost::DidInitialize(
+void GpuHost::DidInitialize(
     const gpu::GPUInfo& gpu_info,
     const gpu::GpuFeatureInfo& gpu_feature_info,
     const base::Optional<gpu::GPUInfo>& gpu_info_for_hardware_gpu,
@@ -207,45 +231,41 @@
   delegate_->OnGpuServiceInitialized();
 }
 
-void DefaultGpuHost::DidFailInitialize() {}
+void GpuHost::DidFailInitialize() {}
 
-void DefaultGpuHost::DidCreateContextSuccessfully() {}
+void GpuHost::DidCreateContextSuccessfully() {}
 
-void DefaultGpuHost::BlockDomainFrom3DAPIs(const GURL& url,
-                                           gpu::DomainGuilt guilt) {}
+void GpuHost::BlockDomainFrom3DAPIs(const GURL& url, gpu::DomainGuilt guilt) {}
 
-void DefaultGpuHost::DisableGpuCompositing() {}
+void GpuHost::DisableGpuCompositing() {}
 
-bool DefaultGpuHost::GpuAccessAllowed() const {
-  NOTREACHED();
+bool GpuHost::GpuAccessAllowed() const {
   return true;
 }
 
-gpu::ShaderCacheFactory* DefaultGpuHost::GetShaderCacheFactory() {
-  NOTREACHED();
-  return nullptr;
+gpu::ShaderCacheFactory* GpuHost::GetShaderCacheFactory() {
+  return shader_cache_factory_.get();
 }
 
-void DefaultGpuHost::RecordLogMessage(int32_t severity,
-                                      const std::string& header,
-                                      const std::string& message) {}
+void GpuHost::RecordLogMessage(int32_t severity,
+                               const std::string& header,
+                               const std::string& message) {}
 
-void DefaultGpuHost::BindDiscardableMemoryRequest(
+void GpuHost::BindDiscardableMemoryRequest(
     discardable_memory::mojom::DiscardableSharedMemoryManagerRequest request) {
   service_manager::BindSourceInfo source_info;
   discardable_shared_memory_manager_->Bind(std::move(request), source_info);
 }
 
-void DefaultGpuHost::BindInterface(
-    const std::string& interface_name,
-    mojo::ScopedMessagePipeHandle interface_pipe) {
+void GpuHost::BindInterface(const std::string& interface_name,
+                            mojo::ScopedMessagePipeHandle interface_pipe) {
   NOTREACHED();
 }
 
 #if defined(USE_OZONE)
-void DefaultGpuHost::TerminateGpuProcess(const std::string& message) {}
+void GpuHost::TerminateGpuProcess(const std::string& message) {}
 
-void DefaultGpuHost::SendGpuProcessMessage(IPC::Message* message) {}
+void GpuHost::SendGpuProcessMessage(IPC::Message* message) {}
 #endif
 
 }  // namespace gpu_host
diff --git a/services/ws/gpu_host/gpu_host.h b/services/ws/gpu_host/gpu_host.h
index d7a9889..d87738e8 100644
--- a/services/ws/gpu_host/gpu_host.h
+++ b/services/ws/gpu_host/gpu_host.h
@@ -26,67 +26,48 @@
 class DiscardableSharedMemoryManager;
 }
 
+namespace gpu {
+class ShaderCacheFactory;
+}
+
 namespace service_manager {
 class Connector;
 }
 
 namespace viz {
+class GpuClient;
 class GpuHostImpl;
 class HostGpuMemoryBufferManager;
 }
 
 namespace ws {
 namespace gpu_host {
-
-class GpuClient;
-
-namespace test {
-class GpuHostTest;
-}  // namespace test
-
 class GpuHostDelegate;
 
 // GpuHost sets up connection from clients to the real service implementation in
 // the GPU process.
-class GpuHost {
+class GpuHost : public viz::GpuHostImpl::Delegate {
  public:
-  GpuHost() = default;
-  virtual ~GpuHost() = default;
-
-  virtual void Add(mojom::GpuRequest request) = 0;
-  virtual void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) = 0;
-  virtual void OnAcceleratedWidgetDestroyed(gfx::AcceleratedWidget widget) = 0;
-
-#if defined(OS_CHROMEOS)
-  virtual void AddArc(mojom::ArcRequest request) = 0;
-#endif  // defined(OS_CHROMEOS)
-};
-
-class DefaultGpuHost : public GpuHost, public viz::GpuHostImpl::Delegate {
- public:
-  DefaultGpuHost(GpuHostDelegate* delegate,
-                 service_manager::Connector* connector,
-                 discardable_memory::DiscardableSharedMemoryManager*
-                     discardable_shared_memory_manager);
-  ~DefaultGpuHost() override;
+  GpuHost(GpuHostDelegate* delegate,
+          service_manager::Connector* connector,
+          discardable_memory::DiscardableSharedMemoryManager*
+              discardable_shared_memory_manager);
+  ~GpuHost() override;
 
   void CreateFrameSinkManager(viz::mojom::FrameSinkManagerRequest request,
                               viz::mojom::FrameSinkManagerClientPtrInfo client);
 
   void Shutdown();
 
-  // GpuHost:
-  void Add(mojom::GpuRequest request) override;
-  void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override;
-  void OnAcceleratedWidgetDestroyed(gfx::AcceleratedWidget widget) override;
+  void Add(mojom::GpuRequest request);
+
 #if defined(OS_CHROMEOS)
-  void AddArc(mojom::ArcRequest request) override;
+  void AddArc(mojom::ArcRequest request);
 #endif  // defined(OS_CHROMEOS)
 
  private:
-  friend class test::GpuHostTest;
+  friend class GpuHostTestApi;
 
-  GpuClient* AddInternal(mojom::GpuRequest request);
   void OnBadMessageFromGpu();
 
   // TODO(crbug.com/611505): this goes away after the gpu process split in mus.
@@ -132,16 +113,19 @@
 
   std::unique_ptr<viz::HostGpuMemoryBufferManager> gpu_memory_buffer_manager_;
 
+  std::unique_ptr<gpu::ShaderCacheFactory> shader_cache_factory_;
+
+  std::vector<std::unique_ptr<viz::GpuClient>> gpu_clients_;
+
   // TODO(crbug.com/620927): This should be removed once ozone-mojo is done.
   base::Thread gpu_thread_;
   std::unique_ptr<viz::VizMainImpl> viz_main_impl_;
 
-  mojo::StrongBindingSet<mojom::Gpu> gpu_bindings_;
 #if defined(OS_CHROMEOS)
   mojo::StrongBindingSet<mojom::Arc> arc_bindings_;
 #endif  // defined(OS_CHROMEOS)
 
-  DISALLOW_COPY_AND_ASSIGN(DefaultGpuHost);
+  DISALLOW_COPY_AND_ASSIGN(GpuHost);
 };
 
 }  // namespace gpu_host
diff --git a/services/ws/gpu_host/gpu_host_test_api.cc b/services/ws/gpu_host/gpu_host_test_api.cc
new file mode 100644
index 0000000..a89f9bf
--- /dev/null
+++ b/services/ws/gpu_host/gpu_host_test_api.cc
@@ -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.
+
+#include "services/ws/gpu_host/gpu_host_test_api.h"
+
+#include <algorithm>
+
+#include "components/viz/host/gpu_client.h"
+#include "components/viz/test/gpu_host_impl_test_api.h"
+#include "services/ws/gpu_host/gpu_host.h"
+
+namespace ws {
+namespace gpu_host {
+
+GpuHostTestApi::GpuHostTestApi(GpuHost* gpu_host) : gpu_host_(gpu_host) {}
+
+GpuHostTestApi::~GpuHostTestApi() = default;
+
+void GpuHostTestApi::SetGpuService(viz::mojom::GpuServicePtr gpu_service) {
+  return viz::GpuHostImplTestApi(gpu_host_->gpu_host_impl_.get())
+      .SetGpuService(std::move(gpu_service));
+}
+
+base::WeakPtr<viz::GpuClient> GpuHostTestApi::GetLastGpuClient() {
+  if (gpu_host_->gpu_clients_.empty())
+    return nullptr;
+  return gpu_host_->gpu_clients_.back()->GetWeakPtr();
+}
+
+}  // namespace gpu_host
+}  // namespace ws
diff --git a/services/ws/gpu_host/gpu_host_test_api.h b/services/ws/gpu_host/gpu_host_test_api.h
new file mode 100644
index 0000000..c4a5f60
--- /dev/null
+++ b/services/ws/gpu_host/gpu_host_test_api.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WS_GPU_HOST_GPU_HOST_TEST_API_H_
+#define SERVICES_WS_GPU_HOST_GPU_HOST_TEST_API_H_
+
+#include "base/memory/weak_ptr.h"
+#include "services/viz/privileged/interfaces/gl/gpu_service.mojom.h"
+
+namespace viz {
+class GpuClient;
+}
+
+namespace ws {
+namespace gpu_host {
+class GpuHost;
+
+class GpuHostTestApi {
+ public:
+  GpuHostTestApi(GpuHost* gpu_host);
+  ~GpuHostTestApi();
+
+  void SetGpuService(viz::mojom::GpuServicePtr gpu_service);
+  base::WeakPtr<viz::GpuClient> GetLastGpuClient();
+
+ private:
+  GpuHost* gpu_host_;
+
+  DISALLOW_COPY_AND_ASSIGN(GpuHostTestApi);
+};
+
+}  // namespace gpu_host
+}  // namespace ws
+
+#endif  // SERVICES_WS_GPU_HOST_GPU_HOST_TEST_API_H_
diff --git a/services/ws/gpu_host/gpu_host_unittest.cc b/services/ws/gpu_host/gpu_host_unittest.cc
index 510c6d9d..a758399 100644
--- a/services/ws/gpu_host/gpu_host_unittest.cc
+++ b/services/ws/gpu_host/gpu_host_unittest.cc
@@ -9,11 +9,12 @@
 #include "base/message_loop/message_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
+#include "components/viz/host/gpu_client.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
 #include "components/viz/test/gpu_host_impl_test_api.h"
 #include "gpu/config/gpu_info.h"
-#include "services/ws/gpu_host/gpu_client.h"
 #include "services/ws/gpu_host/gpu_host_delegate.h"
+#include "services/ws/gpu_host/gpu_host_test_api.h"
 #include "services/ws/public/mojom/gpu.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gl/init/gl_factory.h"
@@ -37,7 +38,7 @@
 };
 
 // Test implementation of GpuService. For testing behaviour of calls made by
-// GpuClient
+// viz::GpuClient.
 class TestGpuService : public viz::GpuServiceImpl {
  public:
   explicit TestGpuService(
@@ -73,7 +74,7 @@
     io_thread_.Stop();
   }
 
-  base::WeakPtr<GpuClient> AddGpuClient();
+  base::WeakPtr<viz::GpuClient> AddGpuClient();
   void DestroyHost();
 
   // testing::Test
@@ -83,22 +84,20 @@
  private:
   base::MessageLoop message_loop_;
 
-  base::WeakPtr<GpuClient> client_ref_;
-
   base::Thread io_thread_;
   TestGpuHostDelegate gpu_host_delegate_;
   discardable_memory::DiscardableSharedMemoryManager
       discardable_memory_manager_;
   std::unique_ptr<TestGpuService> gpu_service_;
   viz::mojom::GpuServicePtr gpu_service_ptr_;
-  std::unique_ptr<DefaultGpuHost> gpu_host_;
+  std::unique_ptr<GpuHost> gpu_host_;
 
   DISALLOW_COPY_AND_ASSIGN(GpuHostTest);
 };
 
-base::WeakPtr<GpuClient> GpuHostTest::AddGpuClient() {
-  GpuClient* client = gpu_host_->AddInternal(mojom::GpuRequest());
-  return client->weak_factory_.GetWeakPtr();
+base::WeakPtr<viz::GpuClient> GpuHostTest::AddGpuClient() {
+  gpu_host_->Add(mojom::GpuRequest());
+  return GpuHostTestApi(gpu_host_.get()).GetLastGpuClient();
 }
 
 void GpuHostTest::DestroyHost() {
@@ -107,12 +106,10 @@
 
 void GpuHostTest::SetUp() {
   testing::Test::SetUp();
-  gpu_host_ = std::make_unique<DefaultGpuHost>(&gpu_host_delegate_, nullptr,
-                                               &discardable_memory_manager_);
-  viz::GpuHostImplTestApi test_api(gpu_host_->gpu_host_impl_.get());
-
+  gpu_host_ = std::make_unique<GpuHost>(&gpu_host_delegate_, nullptr,
+                                        &discardable_memory_manager_);
   gpu_service_->Bind(mojo::MakeRequest(&gpu_service_ptr_));
-  test_api.SetGpuService(std::move(gpu_service_ptr_));
+  GpuHostTestApi(gpu_host_.get()).SetGpuService(std::move(gpu_service_ptr_));
 }
 
 void GpuHostTest::TearDown() {
@@ -121,18 +118,18 @@
   testing::Test::TearDown();
 }
 
-// Tests to verify, that if a GpuHost is deleted before GpuClient receives a
-// callback, that GpuClient is torn down and does not attempt to use GpuInfo
-// after deletion. This should not crash on asan-builds.
+// Tests to verify, that if a GpuHost is deleted before viz::GpuClient receives
+// a callback, that viz::GpuClient is torn down and does not attempt to use
+// GpuInfo after deletion. This should not crash on asan-builds.
 TEST_F(GpuHostTest, GpuClientDestructionOrder) {
-  base::WeakPtr<GpuClient> client_ref = AddGpuClient();
+  base::WeakPtr<viz::GpuClient> client_ref = AddGpuClient();
   EXPECT_NE(nullptr, client_ref);
   DestroyHost();
   EXPECT_EQ(nullptr, client_ref);
 }
 
 TEST_F(GpuHostTest, GpuClientDestroyedWhileChannelRequestInFlight) {
-  base::WeakPtr<GpuClient> client_ref = AddGpuClient();
+  base::WeakPtr<viz::GpuClient> client_ref = AddGpuClient();
   mojom::Gpu* gpu = client_ref.get();
   bool callback_called = false;
   gpu->EstablishGpuChannel(
diff --git a/services/ws/public/mojom/BUILD.gn b/services/ws/public/mojom/BUILD.gn
index 665702ab..3ab531b 100644
--- a/services/ws/public/mojom/BUILD.gn
+++ b/services/ws/public/mojom/BUILD.gn
@@ -70,6 +70,7 @@
     "//testing/gtest",
     "//ui/display/types",
     "//ui/gfx:test_support",
+    "//ui/gfx/geometry/mojo:struct_traits",
     "//ui/gfx/range/mojo:struct_traits",
   ]
 }
diff --git a/services/ws/test_ws/test_window_service.cc b/services/ws/test_ws/test_window_service.cc
index 70ed107..651066c 100644
--- a/services/ws/test_ws/test_window_service.cc
+++ b/services/ws/test_ws/test_window_service.cc
@@ -182,7 +182,7 @@
   discardable_shared_memory_manager_ =
       std::make_unique<discardable_memory::DiscardableSharedMemoryManager>();
 
-  gpu_host_ = std::make_unique<gpu_host::DefaultGpuHost>(
+  gpu_host_ = std::make_unique<gpu_host::GpuHost>(
       this, context()->connector(), discardable_shared_memory_manager_.get());
 
   gpu_interface_provider_ = std::make_unique<TestGpuInterfaceProvider>(
diff --git a/services/ws/test_ws/test_window_service.h b/services/ws/test_ws/test_window_service.h
index 49773e37..28d8bac 100644
--- a/services/ws/test_ws/test_window_service.h
+++ b/services/ws/test_ws/test_window_service.h
@@ -109,7 +109,7 @@
 
   std::unique_ptr<discardable_memory::DiscardableSharedMemoryManager>
       discardable_shared_memory_manager_;
-  std::unique_ptr<gpu_host::DefaultGpuHost> gpu_host_;
+  std::unique_ptr<gpu_host::GpuHost> gpu_host_;
 
   // For drag and drop code to convert to/from screen coordinates.
   wm::DefaultScreenPositionClient screen_position_client_;
diff --git a/storage/browser/blob/blob_data_builder.cc b/storage/browser/blob/blob_data_builder.cc
index 4a101542..79b0308 100644
--- a/storage/browser/blob/blob_data_builder.cc
+++ b/storage/browser/blob/blob_data_builder.cc
@@ -103,7 +103,6 @@
 
 void BlobDataBuilder::AppendIPCDataElement(
     const network::DataElement& ipc_data,
-    const scoped_refptr<FileSystemContext>& file_system_context,
     const BlobStorageRegistry& blob_registry) {
   uint64_t length = ipc_data.length();
   switch (ipc_data.type()) {
@@ -400,22 +399,23 @@
 }
 
 void BlobDataBuilder::AppendDiskCacheEntry(
-    const scoped_refptr<DataHandle>& data_handle,
+    scoped_refptr<DataHandle> data_handle,
     disk_cache::Entry* disk_cache_entry,
     int disk_cache_stream_index) {
-  AppendDiskCacheEntryWithSideData(data_handle, disk_cache_entry,
+  AppendDiskCacheEntryWithSideData(std::move(data_handle), disk_cache_entry,
                                    disk_cache_stream_index,
                                    kInvalidDiskCacheSideStreamIndex);
 }
 
 void BlobDataBuilder::AppendDiskCacheEntryWithSideData(
-    const scoped_refptr<DataHandle>& data_handle,
+    scoped_refptr<DataHandle> data_handle,
     disk_cache::Entry* disk_cache_entry,
     int disk_cache_stream_index,
     int disk_cache_side_stream_index) {
   auto item = BlobDataItem::CreateDiskCacheEntry(
-      0u, disk_cache_entry->GetDataSize(disk_cache_stream_index), data_handle,
-      disk_cache_entry, disk_cache_stream_index, disk_cache_side_stream_index);
+      0u, disk_cache_entry->GetDataSize(disk_cache_stream_index),
+      std::move(data_handle), disk_cache_entry, disk_cache_stream_index,
+      disk_cache_side_stream_index);
 
   total_size_ += item->length();
   UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.CacheEntry",
diff --git a/storage/browser/blob/blob_data_builder.h b/storage/browser/blob/blob_data_builder.h
index 231223c..35ce853 100644
--- a/storage/browser/blob/blob_data_builder.h
+++ b/storage/browser/blob/blob_data_builder.h
@@ -55,12 +55,9 @@
   // it's a 'bytes' element. Data elements of BYTES_DESCRIPTION or
   // DISK_CACHE_ENTRY types are not valid IPC data element types, and cannot be
   // given to this method.
-  // |file_system_context| needs to be set if data element is of type
-  // FILE_FILESYSTEM.
   // |blob_registry| is needed for data elements of type BLOB.
   void AppendIPCDataElement(
       const network::DataElement& ipc_data,
-      const scoped_refptr<FileSystemContext>& file_system_context,
       const BlobStorageRegistry& blob_registry);
 
   // Copies the given data into the blob.
@@ -161,16 +158,15 @@
       const base::Time& expected_modification_time,
       scoped_refptr<FileSystemContext> file_system_context);
 
-  void AppendDiskCacheEntry(const scoped_refptr<DataHandle>& data_handle,
+  void AppendDiskCacheEntry(scoped_refptr<DataHandle> data_handle,
                             disk_cache::Entry* disk_cache_entry,
                             int disk_cache_stream_index);
 
   // The content of the side data is accessible with BlobReader::ReadSideData().
-  void AppendDiskCacheEntryWithSideData(
-      const scoped_refptr<DataHandle>& data_handle,
-      disk_cache::Entry* disk_cache_entry,
-      int disk_cache_stream_index,
-      int disk_cache_side_stream_index);
+  void AppendDiskCacheEntryWithSideData(scoped_refptr<DataHandle> data_handle,
+                                        disk_cache::Entry* disk_cache_entry,
+                                        int disk_cache_stream_index,
+                                        int disk_cache_side_stream_index);
 
   void set_content_type(const std::string& content_type) {
     content_type_ = content_type;
diff --git a/storage/browser/blob/blob_storage_context_unittest.cc b/storage/browser/blob/blob_storage_context_unittest.cc
index b94c7c5..e2b3523 100644
--- a/storage/browser/blob/blob_storage_context_unittest.cc
+++ b/storage/browser/blob/blob_storage_context_unittest.cc
@@ -483,7 +483,7 @@
   ASSERT_TRUE(blob_data_handle2);
   std::unique_ptr<BlobDataSnapshot> data = blob_data_handle2->CreateSnapshot();
   ASSERT_EQ(1u, data->items().size());
-  const scoped_refptr<BlobDataItem> item = data->items()[0];
+  BlobDataItem* item = data->items()[0].get();
   EXPECT_EQ(kLargeSize - kBlobLength, item->offset());
   EXPECT_EQ(kBlobLength, item->length());
 
diff --git a/storage/browser/blob/blob_transport_strategy.cc b/storage/browser/blob/blob_transport_strategy.cc
index f9c40e7..3415f02 100644
--- a/storage/browser/blob/blob_transport_strategy.cc
+++ b/storage/browser/blob/blob_transport_strategy.cc
@@ -316,7 +316,7 @@
 
  private:
   void OnReply(BlobDataBuilder::FutureFile future_file,
-               const scoped_refptr<ShareableFileReference>& file_reference,
+               scoped_refptr<ShareableFileReference> file_reference,
                base::Optional<base::Time> time_file_modified) {
     if (!time_file_modified) {
       // Writing to the file failed in the renderer.
@@ -325,7 +325,7 @@
     }
 
     bool populate_result =
-        future_file.Populate(file_reference, *time_file_modified);
+        future_file.Populate(std::move(file_reference), *time_file_modified);
     DCHECK(populate_result);
 
     if (--num_unresolved_requests_ == 0)
diff --git a/storage/browser/blob/scoped_file.cc b/storage/browser/blob/scoped_file.cc
index a26c7d5..bf25710 100644
--- a/storage/browser/blob/scoped_file.cc
+++ b/storage/browser/blob/scoped_file.cc
@@ -19,12 +19,12 @@
 
 ScopedFile::ScopedFile(const base::FilePath& path,
                        ScopeOutPolicy policy,
-                       const scoped_refptr<base::TaskRunner>& file_task_runner)
+                       scoped_refptr<base::TaskRunner> file_task_runner)
     : path_(path),
       scope_out_policy_(policy),
-      file_task_runner_(file_task_runner) {
+      file_task_runner_(std::move(file_task_runner)) {
   DCHECK(path.empty() || policy != DELETE_ON_SCOPE_OUT ||
-         file_task_runner.get())
+         file_task_runner_.get())
       << "path:" << path.value() << " policy:" << policy
       << " runner:" << file_task_runner.get();
 }
diff --git a/storage/browser/blob/scoped_file.h b/storage/browser/blob/scoped_file.h
index 4735658..b482ef28 100644
--- a/storage/browser/blob/scoped_file.h
+++ b/storage/browser/blob/scoped_file.h
@@ -43,7 +43,7 @@
   // is DELETE_ON_SCOPE_OUT.
   ScopedFile(const base::FilePath& path,
              ScopeOutPolicy policy,
-             const scoped_refptr<base::TaskRunner>& file_task_runner);
+             scoped_refptr<base::TaskRunner> file_task_runner);
 
   ScopedFile(ScopedFile&& other);
   ScopedFile& operator=(ScopedFile&& rhs) {
diff --git a/storage/browser/quota/quota_client.h b/storage/browser/quota/quota_client.h
index 61a000585..0b74ba2 100644
--- a/storage/browser/quota/quota_client.h
+++ b/storage/browser/quota/quota_client.h
@@ -7,7 +7,6 @@
 
 #include <stdint.h>
 
-#include <list>
 #include <set>
 #include <string>
 
@@ -79,9 +78,6 @@
   virtual bool DoesSupport(blink::mojom::StorageType type) const = 0;
 };
 
-// TODO(dmikurube): Replace it to std::vector for efficiency.
-using QuotaClientList = std::list<QuotaClient*>;
-
 }  // namespace storage
 
 #endif  // STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 11a057a..a4d24bf 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -813,21 +813,21 @@
 QuotaManager::QuotaManager(
     bool is_incognito,
     const base::FilePath& profile_path,
-    const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
-    const scoped_refptr<SpecialStoragePolicy>& special_storage_policy,
+    scoped_refptr<base::SingleThreadTaskRunner> io_thread,
+    scoped_refptr<SpecialStoragePolicy> special_storage_policy,
     const GetQuotaSettingsFunc& get_settings_function)
     : is_incognito_(is_incognito),
       profile_path_(profile_path),
       proxy_(new QuotaManagerProxy(this, io_thread)),
       db_disabled_(false),
       eviction_disabled_(false),
-      io_thread_(io_thread),
+      io_thread_(std::move(io_thread)),
       db_runner_(base::CreateSequencedTaskRunnerWithTraits(
           {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
            base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
       get_settings_function_(get_settings_function),
       is_getting_eviction_origin_(false),
-      special_storage_policy_(special_storage_policy),
+      special_storage_policy_(std::move(special_storage_policy)),
       get_volume_info_fn_(&QuotaManager::GetVolumeInfo),
       storage_monitor_(new StorageMonitor(this)),
       weak_factory_(this) {
diff --git a/storage/browser/quota/quota_manager.h b/storage/browser/quota/quota_manager.h
index 7673368..6489da7 100644
--- a/storage/browser/quota/quota_manager.h
+++ b/storage/browser/quota/quota_manager.h
@@ -23,7 +23,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
-#include "base/sequenced_task_runner_helpers.h"
 #include "base/stl_util.h"
 #include "storage/browser/quota/quota_callbacks.h"
 #include "storage/browser/quota/quota_client.h"
@@ -36,7 +35,6 @@
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
 namespace base {
-class FilePath;
 class SequencedTaskRunner;
 class SingleThreadTaskRunner;
 class TaskRunner;
@@ -126,12 +124,11 @@
 
   static const int64_t kNoLimit;
 
-  QuotaManager(
-      bool is_incognito,
-      const base::FilePath& profile_path,
-      const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
-      const scoped_refptr<SpecialStoragePolicy>& special_storage_policy,
-      const GetQuotaSettingsFunc& get_settings_function);
+  QuotaManager(bool is_incognito,
+               const base::FilePath& profile_path,
+               scoped_refptr<base::SingleThreadTaskRunner> io_thread,
+               scoped_refptr<SpecialStoragePolicy> special_storage_policy,
+               const GetQuotaSettingsFunc& get_settings_function);
 
   const QuotaSettings& settings() const { return settings_; }
   void SetQuotaSettings(const QuotaSettings& settings);
@@ -452,7 +449,7 @@
   GetOriginCallback lru_origin_callback_;
   std::set<url::Origin> access_notified_origins_;
 
-  QuotaClientList clients_;
+  std::vector<QuotaClient*> clients_;
 
   std::unique_ptr<UsageTracker> temporary_usage_tracker_;
   std::unique_ptr<UsageTracker> persistent_usage_tracker_;
diff --git a/storage/browser/quota/quota_manager_proxy.cc b/storage/browser/quota/quota_manager_proxy.cc
index 0125cff..2473ffd 100644
--- a/storage/browser/quota/quota_manager_proxy.cc
+++ b/storage/browser/quota/quota_manager_proxy.cc
@@ -154,9 +154,8 @@
 
 QuotaManagerProxy::QuotaManagerProxy(
     QuotaManager* manager,
-    const scoped_refptr<base::SingleThreadTaskRunner>& io_thread)
-    : manager_(manager), io_thread_(io_thread) {
-}
+    scoped_refptr<base::SingleThreadTaskRunner> io_thread)
+    : manager_(manager), io_thread_(std::move(io_thread)) {}
 
 QuotaManagerProxy::~QuotaManagerProxy() = default;
 
diff --git a/storage/browser/quota/quota_manager_proxy.h b/storage/browser/quota/quota_manager_proxy.h
index 32b3292..91ebbf4 100644
--- a/storage/browser/quota/quota_manager_proxy.h
+++ b/storage/browser/quota/quota_manager_proxy.h
@@ -66,9 +66,8 @@
   friend class QuotaManager;
   friend class base::RefCountedThreadSafe<QuotaManagerProxy>;
 
-  QuotaManagerProxy(
-      QuotaManager* manager,
-      const scoped_refptr<base::SingleThreadTaskRunner>& io_thread);
+  QuotaManagerProxy(QuotaManager* manager,
+                    scoped_refptr<base::SingleThreadTaskRunner> io_thread);
   virtual ~QuotaManagerProxy();
 
   QuotaManager* manager_;  // only accessed on the io thread
diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc
index 6362317..7530dc64 100644
--- a/storage/browser/quota/usage_tracker.cc
+++ b/storage/browser/quota/usage_tracker.cc
@@ -37,7 +37,7 @@
 
 }  // namespace
 
-UsageTracker::UsageTracker(const QuotaClientList& clients,
+UsageTracker::UsageTracker(const std::vector<QuotaClient*>& clients,
                            blink::mojom::StorageType type,
                            SpecialStoragePolicy* special_storage_policy,
                            StorageMonitor* storage_monitor)
diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h
index d03bffd..48f9f0e 100644
--- a/storage/browser/quota/usage_tracker.h
+++ b/storage/browser/quota/usage_tracker.h
@@ -11,6 +11,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
@@ -33,7 +34,7 @@
 // An instance of this class is created per storage type.
 class STORAGE_EXPORT UsageTracker : public QuotaTaskObserver {
  public:
-  UsageTracker(const QuotaClientList& clients,
+  UsageTracker(const std::vector<QuotaClient*>& clients,
                blink::mojom::StorageType type,
                SpecialStoragePolicy* special_storage_policy,
                StorageMonitor* storage_monitor);
diff --git a/storage/browser/quota/usage_tracker_unittest.cc b/storage/browser/quota/usage_tracker_unittest.cc
index ace109a..4bb6162 100644
--- a/storage/browser/quota/usage_tracker_unittest.cc
+++ b/storage/browser/quota/usage_tracker_unittest.cc
@@ -4,6 +4,8 @@
 
 #include <stdint.h>
 
+#include <vector>
+
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
@@ -19,7 +21,6 @@
 using blink::mojom::QuotaStatusCode;
 using blink::mojom::StorageType;
 using storage::QuotaClient;
-using storage::QuotaClientList;
 using storage::SpecialStoragePolicy;
 using storage::UsageTracker;
 
@@ -221,8 +222,8 @@
   }
 
  private:
-  QuotaClientList GetUsageTrackerList() {
-    QuotaClientList client_list;
+  std::vector<QuotaClient*> GetUsageTrackerList() {
+    std::vector<QuotaClient*> client_list;
     client_list.push_back(&quota_client_);
     return client_list;
   }
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc
index 276ef66..cd26b9d 100644
--- a/storage/browser/test/mock_quota_manager.cc
+++ b/storage/browser/test/mock_quota_manager.cc
@@ -6,6 +6,7 @@
 
 #include <limits>
 #include <memory>
+#include <utility>
 
 #include "base/location.h"
 #include "base/memory/ref_counted.h"
@@ -33,12 +34,12 @@
 MockQuotaManager::MockQuotaManager(
     bool is_incognito,
     const base::FilePath& profile_path,
-    const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
-    const scoped_refptr<SpecialStoragePolicy>& special_storage_policy)
+    scoped_refptr<base::SingleThreadTaskRunner> io_thread,
+    scoped_refptr<SpecialStoragePolicy> special_storage_policy)
     : QuotaManager(is_incognito,
                    profile_path,
-                   io_thread,
-                   special_storage_policy,
+                   std::move(io_thread),
+                   std::move(special_storage_policy),
                    storage::GetQuotaSettingsFunc()),
       weak_factory_(this) {}
 
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index 129cb5d..675f4c0 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -42,11 +42,10 @@
 // origin data stored in the profile.
 class MockQuotaManager : public QuotaManager {
  public:
-  MockQuotaManager(
-      bool is_incognito,
-      const base::FilePath& profile_path,
-      const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
-      const scoped_refptr<SpecialStoragePolicy>& special_storage_policy);
+  MockQuotaManager(bool is_incognito,
+                   const base::FilePath& profile_path,
+                   scoped_refptr<base::SingleThreadTaskRunner> io_thread,
+                   scoped_refptr<SpecialStoragePolicy> special_storage_policy);
 
   // Overrides QuotaManager's implementation. The internal usage data is
   // updated when MockQuotaManagerProxy::NotifyStorageModified() is
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 75785de..c1266ab2 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -3245,15 +3245,6 @@
         "test": "base_unittests"
       },
       {
-        "args": [
-          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "content_unittests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
diff --git a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
index 852719cd..e387f32 100644
--- a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
@@ -109,36 +109,6 @@
 # variations::kClientDataHeader in GoogleURLLoaderThrottle.
 -ChromeResourceDispatcherHostDelegateMirrorBrowserTest.MirrorRequestHeader
 
-# Relies on net::URLRequestMockHTTPJob.
--AffiliationCheck/EnterpriseDeviceAttributesTest.Success/Affiliated
--AffiliationCheck/EnterpriseDeviceAttributesTest.Success/NotAffiliated
--CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/0
--CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/1
--CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/2
--CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/3
--SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/0
--SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/1
--SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/2
--SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/3
--SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/4
--SigninProfileAppsPolicyPerChannelTest.NotWhitelistedAppInstallation/0
--SigninProfileAppsPolicyPerChannelTest.NotWhitelistedAppInstallation/1
--SigninProfileAppsPolicyPerChannelTest.NotWhitelistedAppInstallation/2
--SigninProfileAppsPolicyPerChannelTest.NotWhitelistedAppInstallation/3
--SigninProfileAppsPolicyPerChannelTest.NotWhitelistedAppInstallation/4
--SigninProfileAppsPolicyPerChannelTest.WhitelistedAppInstallation/0
--SigninProfileAppsPolicyPerChannelTest.WhitelistedAppInstallation/1
--SigninProfileAppsPolicyPerChannelTest.WhitelistedAppInstallation/2
--SigninProfileAppsPolicyPerChannelTest.WhitelistedAppInstallation/3
--SigninProfileAppsPolicyPerChannelTest.WhitelistedAppInstallation/4
--SigninProfileAppsPolicyTest.BackgroundPage
--SigninProfileAppsPolicyTest.MultipleApps
-
-# Navigation blocked by Safe Browsing.
-# e.g. Showing 'Your connection is not private' instead of opening 'app.com'.
--HostedAppNonClientFrameViewAshTest.FocusableViews/material_refresh
--HostedAppNonClientFrameViewAshTest.FocusableViews/material_refresh_touch_optimized
-
 # Flaky with error: `Check failed: (sequence_checker_).CalledOnValidSequence()`.
 -DevToolsSanityTest.DisposeEmptyBrowserContext
 
diff --git a/testing/buildbot/filters/webui_polymer2_browser_tests.filter b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
index 307ff9d8..80cc817b 100644
--- a/testing/buildbot/filters/webui_polymer2_browser_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
@@ -21,10 +21,6 @@
 -MediaRouterElementsBrowserTest.MediaRouterContainerSinkList
 -MediaRouterElementsBrowserTest.MediaRouterRouteDetails
 
-# Flaky on Windows. See https://crbug.com/881685
-# May also be flaky on Mac/ChromeOS. See https://crbug.com/669227#c10
--MaterialHistoryListTest.All
-
 # Mac only failure. See crbug.com/876990
 -CrSettingsPrivacyPageTest.All
 
@@ -34,7 +30,6 @@
 -ActiveDirectoryJoinTest.TestActiveDirectoryEnrollment_Success
 -ActiveDirectoryJoinTest.TestActiveDirectoryEnrollment_UIErrors
 -CrSettingsFingerprintProgressArcTest.All
--CrSettingsMultidevicePageTest.All
 -CrSettingsPeoplePageLockScreenTest.All
 -CrSettingsPeoplePageQuickUnlockAuthenticateTest.All
 -CrSettingsPeoplePageSetupPinDialogTest.All
@@ -148,6 +143,7 @@
 CrSettingsMultideviceFeatureToggleTest.*
 CrSettingsMultidevicePageContainerTest.*
 CrSettingsMultidevicePageTest.*
+CrSettingsMultideviceSubpageTest.*
 CrSettingsNonExistentRouteTest.*
 CrSettingsOnStartupPageTest.*
 CrSettingsPaymentsSectionTest.*
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 2b61f2d..8ee2e2edc 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -476,6 +476,10 @@
     },
   },
   'content_unittests': {
+    'remove_from': [
+      # chromium.fyi
+      'fuchsia-fyi-arm64-rel',  # https://crbug.com/881334
+    ],
     'modifications': {
       # chromium.memory
       'Linux ASan LSan Tests (1)': {
diff --git a/testing/scripts/sizes.py b/testing/scripts/sizes.py
index 3713fae..04d264c 100755
--- a/testing/scripts/sizes.py
+++ b/testing/scripts/sizes.py
@@ -44,51 +44,19 @@
         os.path.join(
             common.SRC_DIR, 'infra', 'scripts', 'legacy', 'scripts', 'slave',
             'chromium', 'sizes.py'),
-        '--json', tempfile_path
+        '--failures', tempfile_path
     ]
     if args.platform:
       sizes_cmd.extend(['--platform', args.platform])
     rc = common.run_runtest(script_args, runtest_args + sizes_cmd)
     with open(tempfile_path) as f:
-      results = json.load(f)
-
-  with open(os.path.join(common.SRC_DIR, 'tools', 'perf_expectations',
-                         'perf_expectations.json')) as f:
-    perf_expectations = json.load(f)
-
-  valid = (rc == 0)
-  failures = []
-
-  for name, result in results.iteritems():
-    fqtn = '%s/%s/%s' % (args.prefix, name, result['identifier'])
-    if fqtn not in perf_expectations:
-      continue
-
-    if perf_expectations[fqtn]['type'] != 'absolute':
-      print 'ERROR: perf expectation %r is not yet supported' % fqtn
-      valid = False
-      continue
-
-    actual = result['value']
-    expected = perf_expectations[fqtn]['regress']
-    better = perf_expectations[fqtn]['better']
-    check_result = ((actual <= expected) if better == 'lower'
-                    else (actual >= expected))
-
-    if not check_result:
-      failures.append(fqtn)
-      print 'FAILED %s: actual %s, expected %s, better %s' % (
-          fqtn, actual, expected, better)
+      failures = json.load(f)
 
   json.dump({
-      'valid': valid,
+      'valid': (rc == 0 or rc == 125),
       'failures': failures,
   }, script_args.output)
 
-  # sizes.py itself doesn't fail on regressions.
-  if failures and rc == 0:
-    rc = 1
-
   return rc
 
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f0fb225e..167c0ae 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1,4 +1,19 @@
 {
+    "AImageReaderMediaPlayer": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Use_AImageReaderMediaPlayer_V2",
+                    "enable_features": [
+                        "AImageReaderMediaPlayer"
+                    ]
+                }
+            ]
+        }
+    ],
     "AImageReaderVideoOutput": [
         {
             "platforms": [
@@ -2731,24 +2746,6 @@
             ]
         }
     ],
-    "NewPrintPreview": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NewPrintPreview"
-                    ]
-                }
-            ]
-        }
-    ],
     "NewTabInProductHelp": [
         {
             "platforms": [
@@ -4383,10 +4380,13 @@
             ],
             "experiments": [
                 {
-                    "name": "FinalV6",
+                    "name": "FinalV7",
                     "params": {
                         "variant": "final"
-                    }
+                    },
+                    "enable_features": [
+                        "IgnoreTLS13Downgrade"
+                    ]
                 }
             ]
         }
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index 3a65886a..70549451 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -1931,5 +1931,8 @@
 crbug.com/870172 virtual/outofblink-cors-ns/http/tests/xmlhttprequest/origin-whitelisting-subdomains.html [ WontFix ]
 crbug.com/870172 virtual/outofblink-cors-ns/http/tests/xmlhttprequest/origin-whitelisting-ip-addresses.html [ WontFix ]
 
+# Tests that only work when the mixed content autoupgrade experiment is enabled
+http/tests/mixed-autoupgrade [ WontFix ]
+
 # Not expected to pass in default configuration, only virtual test suite.
 crbug.com/830901 fast/webgl/video-metadata/texImage-video-last-uploaded-metadata.html [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index d8713be..824960d 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -5218,7 +5218,6 @@
 
 # Sheriff 2018-09-13
 crbug.com/883591 [ Android ] fullscreen/full-screen-inline-split-crash.html [ Pass Crash ]
-crbug.com/883860 [ Mac ] external/wpt/service-workers/service-worker/interfaces-sw.https.html  [ Failure ]
 crbug.com/883837 [ Win7 ] http/tests/performance-timing/resource_timing_buffer_full_250.html [ Timeout Pass ]
 
 # Sheriff 2018-09-18
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index bebf54bc..d3b07ed8 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -770,6 +770,21 @@
     "args": ["--enable-blink-features=DisplayLocking"]
   },
   {
+    "prefix" : "autoupgrade-optionally-blockable-mixed-content",
+    "base": "http/tests/mixed-autoupgrade/optionally",
+    "args": ["--enable-features=AutoupgradeMixedContent<AU --force-fieldtrials=AU/G1 --force-fieldtrial-params=AU.G1:mode/optionally-blockable"]
+  },
+  {
+    "prefix" : "autoupgrade-blockable-mixed-content",
+    "base": "http/tests/mixed-autoupgrade/blockable",
+    "args": ["--enable-features=AutoupgradeMixedContent<AU --force-fieldtrials=AU/G1 --force-fieldtrial-params=AU.G1:mode/blockable"]
+  },
+  {
+    "prefix" : "autoupgrade-all-mixed-content",
+    "base": "http/tests/mixed-autoupgrade/all",
+    "args": ["--enable-features=AutoupgradeMixedContent"]
+  },
+  {
     "prefix": "webgl-extra-video-texture-metadata",
     "base": "fast/webgl/video-metadata",
     "args": ["--enable-blink-features=ExtraWebGLVideoTextureMetadata"]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/inheritance-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/inheritance-expected.txt
index 67d9a63..1508689 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/inheritance-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/inheritance-expected.txt
@@ -32,7 +32,7 @@
 FAIL Property scroll-padding-top has initial value 0px assert_equals: expected "0px" but got "auto"
 PASS Property scroll-padding-top does not inherit
 PASS Property scroll-snap-align has initial value none
-FAIL Property scroll-snap-align does not inherit assert_equals: expected "start end" but got "end start"
+PASS Property scroll-snap-align does not inherit
 PASS Property scroll-snap-stop has initial value normal
 PASS Property scroll-snap-stop does not inherit
 PASS Property scroll-snap-type has initial value none
diff --git a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container-expected.html b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container-expected.html
new file mode 100644
index 0000000..acd1b80
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container-expected.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<style>
+  body {
+    margin: 0;
+    height: 4000px;
+  }
+  .sticky {
+    position: absolute;
+    top: 200px;
+  }
+  .columncontainer {
+    width: 100px;
+    height: 100px;
+    background: green;
+    columns: 1;
+  }
+  .contents {
+    margin-left: 10%;
+    margin-top: 10%;
+    width: 80%;
+    height: 80%;
+    background: lightgreen;
+  }
+</style>
+<script>
+  function doTest() {
+    window.scrollTo(0, 100);
+  }
+  window.addEventListener('load', doTest, false);
+</script>
+This test passes if there is a light green square with a dark green border.
+<div class="sticky">
+  <div class="columncontainer">
+    <div class="contents"></div>
+  </div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container.html b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container.html
new file mode 100644
index 0000000..22b38b6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-as-column-container.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<style>
+  body {
+    margin: 0;
+    height: 4000px;
+  }
+  .sticky {
+    position: sticky;
+    top: 100px;
+  }
+  .columncontainer {
+    width: 100px;
+    height: 100px;
+    background: green;
+    columns: 1;
+  }
+  .contents {
+    margin-left: 10%;
+    margin-top: 10%;
+    width: 80%;
+    height: 80%;
+    background: lightgreen;
+  }
+</style>
+<script>
+  function doTest() {
+    window.scrollTo(0, 100);
+  }
+  window.addEventListener('load', doTest, false);
+</script>
+This test passes if there is a light green square with a dark green border.
+<div class="sticky">
+  <div class="columncontainer">
+    <div class="contents"></div>
+  </div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/fast/css/update-option-label-recalc-root-crash.html b/third_party/WebKit/LayoutTests/fast/css/update-option-label-recalc-root-crash.html
new file mode 100644
index 0000000..fcbb579
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/update-option-label-recalc-root-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<select>
+  <option id="opt">Content</option>
+</select>
+<script>
+  test(() => {
+    opt.appendChild(document.createElement("span"));
+    document.body.offsetTop;
+    // Mark for style recalc and make the span the recalc root.
+    opt.lastChild.style.color = "pink";
+    // Trigger HTMLOptionElement::UpdateLabel() which removes option UA shadow children.
+    opt.innerHTML = "";
+  }, "Should not cause DCHECK failures or crashes.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand-expected.txt
index 02ea3eb2..274e9566 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand-expected.txt
@@ -24,17 +24,187 @@
 Force selecting index 0
 Viewport virtual selection: 0
 Is group expanded: YES
-console-key-expand.js:27 group
-console-key-expand.js:27 log child
+console-key-expand.js:35 group
+console-key-expand.js:35 log child
 
 ArrowLeft:
 Viewport virtual selection: 0
 Is group expanded: NO
-console-key-expand.js:27 group
+console-key-expand.js:35 group
 
 ArrowRight:
 Viewport virtual selection: 0
 Is group expanded: YES
-console-key-expand.js:27 group
-console-key-expand.js:27 log child
+console-key-expand.js:35 group
+console-key-expand.js:35 log child
+
+Running: testNavigateBetweenObjectsAndLogs
+Evaluating: console.log("before");console.log("text", obj1, obj2);console.log("after");
+Message count: 3
+
+Force selecting index 1
+Viewport virtual selection: 1
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:51 text {x: 1} {y: 2}
+
+ArrowRight:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {x: 1}
+
+ArrowDown:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {y: 2}
+
+ArrowDown:
+Viewport virtual selection: 2
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:51 after
+
+ArrowUp:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {y: 2}
+
+ArrowLeft:
+Viewport virtual selection: 1
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:51 text {x: 1} {y: 2}
+
+Running: testExpandingObjects
+Evaluating: console.log("before");console.log("text", obj1, obj2);console.log("after");
+Message count: 3
+
+Force selecting index 1
+Viewport virtual selection: 1
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 text {x: 1} {y: 2}
+
+ArrowRight:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {x: 1}
+
+ArrowRight:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected.expanded
+active text: {x: 1}
+
+ArrowDown:
+Viewport virtual selection: 1
+activeElement: LI.selected
+active text: x: 1
+
+ArrowDown:
+
+ArrowDown:
+Viewport virtual selection: 2
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 after
+
+ArrowRight:
+Viewport virtual selection: 2
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 after
+
+ArrowDown:
+
+ArrowDown:
+
+ArrowDown:
+
+ArrowDown:
+Viewport virtual selection: 2
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 after
+
+ArrowUp:
+Viewport virtual selection: 1
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {y: 2}
+
+ArrowLeft:
+Viewport virtual selection: 1
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 text {x: 1}x: 1 {y: 2}y: 2
+
+ArrowLeft:
+Viewport virtual selection: 1
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:70 text {x: 1}x: 1 {y: 2}y: 2
+
+Running: testExpandingObjectInTrace
+Evaluating: console.log("before");console.warn("warning", obj1);console.log("after");
+Message count: 3
+
+Force selecting index 1
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: NO
+activeElement: DIV.console-message-wrapper.console-from-api.console-warning-level.console-selected
+active text: console-key-expand.js:112 warning {x: 1}
+(anonymous) @ console-key-expand.js:112
+
+ArrowRight:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: YES
+activeElement: DIV.console-message-wrapper.console-from-api.console-warning-level.console-selected
+active text: console-key-expand.js:112 warning {x: 1}
+(anonymous) @ console-key-expand.js:112
+
+ArrowRight:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: YES
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {x: 1}
+
+ArrowDown:
+Viewport virtual selection: 2
+Has object: collapsed
+Is trace expanded: YES
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:112 after
+
+ArrowDown:
+
+ArrowDown:
+Viewport virtual selection: 2
+Has object: collapsed
+Is trace expanded: YES
+activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
+active text: console-key-expand.js:112 after
+
+ArrowUp:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: YES
+activeElement: LI.parent.object-properties-section-root-element.selected
+active text: {x: 1}
+
+ArrowUp:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: YES
+activeElement: DIV.console-message-wrapper.console-from-api.console-warning-level.console-selected
+active text: console-key-expand.js:112 warning {x: 1}
+(anonymous) @ console-key-expand.js:112
+
+ArrowLeft:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: NO
+activeElement: DIV.console-message-wrapper.console-from-api.console-warning-level.console-selected
+active text: console-key-expand.js:112 warning {x: 1}
+(anonymous) @ console-key-expand.js:112
+
+ArrowLeft:
+Viewport virtual selection: 1
+Has object: collapsed
+Is trace expanded: NO
+activeElement: DIV.console-message-wrapper.console-from-api.console-warning-level.console-selected
+active text: console-key-expand.js:112 warning {x: 1}
+(anonymous) @ console-key-expand.js:112
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand.js b/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand.js
index 4ac46ed..8794dc0 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/console/viewport-testing/console-key-expand.js
@@ -13,6 +13,14 @@
   const viewport = consoleView._viewport;
   const prompt = consoleView._prompt;
 
+  await TestRunner.evaluateInPagePromise(`
+    var obj1 = Object.create(null);
+    obj1.x = 1;
+
+    var obj2 = Object.create(null);
+    obj2.y = 2;
+  `);
+
   TestRunner.runTestSuite([
     async function testExpandingTraces(next) {
       await clearAndLog(`console.warn("warning")`);
@@ -42,6 +50,101 @@
 
       next();
     },
+
+    async function testNavigateBetweenObjectsAndLogs(next) {
+      await clearAndLog(`console.log("before");console.log("text", obj1, obj2);console.log("after");`, 3);
+      forceSelect(1);
+
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowRight');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowDown');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowDown');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowUp');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowLeft');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      next();
+    },
+
+    async function testExpandingObjects(next) {
+      await clearAndLog(`console.log("before");console.log("text", obj1, obj2);console.log("after");`, 3);
+      forceSelect(1);
+
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowRight');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      // Expand object.
+      press('ArrowRight');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
+      press('ArrowDown');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      press('ArrowDown');
+      press('ArrowDown');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      // Expand array.
+      press('ArrowRight');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+      await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
+      press('ArrowDown');
+      press('ArrowDown');
+      press('ArrowDown');
+      press('ArrowDown');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      press('ArrowUp');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      // Collapse object.
+      press('ArrowLeft');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      // Select message.
+      press('ArrowLeft');
+      dumpFocus(true, 1, true /* skipObjectCheck */);
+
+      next();
+    },
+
+    async function testExpandingObjectInTrace(next) {
+      await clearAndLog(`console.log("before");console.warn("warning", obj1);console.log("after");`, 3);
+      forceSelect(1);
+
+      dumpFocus(true, 1);
+      press('ArrowRight');
+      dumpFocus(true, 1);
+
+      // Expand object.
+      press('ArrowRight');
+      dumpFocus(true, 1);
+      await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
+      press('ArrowDown');
+      dumpFocus(true, 1);
+      press('ArrowDown');
+      press('ArrowDown');
+      dumpFocus(true, 1);
+
+      press('ArrowUp');
+      dumpFocus(true, 1);
+      press('ArrowUp');
+      dumpFocus(true, 1);
+
+      // Collapse trace.
+      press('ArrowLeft');
+      dumpFocus(true, 1);
+
+      // ArrowLeft on message does not collapse object.
+      press('ArrowLeft');
+      dumpFocus(true, 1);
+
+      next();
+    },
   ]);
 
 
@@ -66,19 +169,21 @@
     eventSender.keyDown(key);
   }
 
-  function dumpFocus() {
-    const firstMessage = consoleView._visibleViewMessages[0];
+  function dumpFocus(activeElement, messageIndex = 0, skipObjectCheck) {
+    const firstMessage = consoleView._visibleViewMessages[messageIndex];
     const hasTrace = !!firstMessage.element().querySelector('.console-message-stack-trace-toggle');
     const hasHiddenStackTrace = firstMessage.element().querySelector('.console-message-stack-trace-wrapper > div.hidden');
-    const hasCollapsedObject = firstMessage.element().querySelector('.console-view-object-properties-section.hidden');
-    const hasExpandedObject = firstMessage.element().querySelector('.console-view-object-properties-section:not(.hidden)');
+    const hasCollapsedObject = firstMessage.element().querySelector('.console-view-object-properties-section:not(.expanded)');
+    const hasExpandedObject = firstMessage.element().querySelector('.console-view-object-properties-section.expanded');
 
     TestRunner.addResult(`Viewport virtual selection: ${viewport._virtualSelectedIndex}`);
 
-    if (hasCollapsedObject) {
-      TestRunner.addResult(`Has object: collapsed`);
-    } else if (hasExpandedObject) {
-      TestRunner.addResult(`Has object: expanded`);
+    if (!skipObjectCheck) {
+      if (hasCollapsedObject) {
+        TestRunner.addResult(`Has object: collapsed`);
+      } else if (hasExpandedObject) {
+        TestRunner.addResult(`Has object: expanded`);
+      }
     }
 
     if (hasTrace) {
@@ -88,5 +193,21 @@
       const expanded = !firstMessage.collapsed();
       TestRunner.addResult(`Is group expanded: ${expanded ? 'YES' : 'NO'}`);
     }
+
+    if (!activeElement)
+      return;
+    var element = document.deepActiveElement();
+    if (!element) {
+      TestRunner.addResult('null');
+      return;
+    }
+    var name = `activeElement: ${element.tagName}`;
+    if (element.id)
+      name += '#' + element.id;
+    else if (element.className)
+      name += '.' + element.className.split(' ').join('.');
+    if (element.deepTextContent())
+      name += '\nactive text: ' + element.deepTextContent();
+    TestRunner.addResult(name);
   }
 })();
diff --git a/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state-expected.txt b/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state-expected.txt
new file mode 100644
index 0000000..ebea9be
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state-expected.txt
@@ -0,0 +1,5 @@
+
+
+============== Back Forward List ==============
+curr->  http://127.0.0.1:8000/resources/dummy.html
+===============================================
diff --git a/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state.html b/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state.html
new file mode 100644
index 0000000..d5013606
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/history/history-entry-requires-user-gesture-push-state.html
@@ -0,0 +1,17 @@
+<body onload="loaded();">
+<script>
+if (window.testRunner) {
+  testRunner.dumpAsText();
+  testRunner.dumpBackForwardList();
+  internals.settings.setHistoryEntryRequiresUserGesture(true);
+}
+
+/* Because user gesture required flag is enabled this html file will not be part
+ * of history
+ */
+function loaded() {
+   history.pushState(null, "", "../resources/dummy.html");
+}
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/all/image-script-upgrade.https.html b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/all/image-script-upgrade.https.html
new file mode 100644
index 0000000..32eb9757
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/all/image-script-upgrade.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Autoupgrade mixed content: All.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="http://web-platform.test:8443/mixed-autoupgrade/resources/test.js"></script>
+
+</head>
+<body>
+  <script>
+    async_test(t => pass_test(t), "Script autoupgraded");
+    async_test(t => assert_image_loads(t), "Image autoupgraded");
+
+    function assert_image_loads(test) {
+        var url = new URL("http://web-platform.test:8443/mixed-autoupgrade/resources/pass.png")
+        var i = document.createElement('img');
+        i.onload = test.step_func_done(_ => {
+            assert_equals(i.naturalHeight, 64, "Height.");
+            assert_equals(i.naturalWidth, 168, "Width.");
+        });
+        i.onerror = test.unreached_func("Image should load successfully from " + url);
+        i.src = url;
+    }
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/blockable/script-upgrade.https.html b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/blockable/script-upgrade.https.html
new file mode 100644
index 0000000..c1011b01
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/blockable/script-upgrade.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Autoupgrade mixed content: Blockable.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="http://web-platform.test:8443/mixed-autoupgrade/resources/test.js"></script>
+
+</head>
+<body>
+  <script>
+    async_test(t => pass_test(t), "Script autoupgraded");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/optionally/image-upgrade.https.html b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/optionally/image-upgrade.https.html
new file mode 100644
index 0000000..b39e13b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/optionally/image-upgrade.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Autoupgrade mixed content: Optionally Blockable.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+</head>
+<body>
+  <script>
+    async_test(t => assert_image_loads(t), "Image autoupgraded");
+
+    function assert_image_loads(test) {
+        var url = new URL("http://web-platform.test:8443/mixed-autoupgrade/resources/pass.png")
+        var i = document.createElement('img');
+        i.onload = test.step_func_done(_ => {
+            assert_equals(i.naturalHeight, 64, "Height.");
+            assert_equals(i.naturalWidth, 168, "Width.");
+        });
+        i.onerror = test.unreached_func("Image should load successfully from " + url);
+        i.src = url;
+    }
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/pass.png b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/pass.png
new file mode 100644
index 0000000..2fa1e0a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/pass.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/test.js b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/test.js
new file mode 100644
index 0000000..66db5e78
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/mixed-autoupgrade/resources/test.js
@@ -0,0 +1,3 @@
+function pass_test(test) {
+  test.done()
+}
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/doubletap-on-play-button.html b/third_party/WebKit/LayoutTests/media/controls/modern/doubletap-on-play-button.html
index 989517c..b35fead 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/doubletap-on-play-button.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/doubletap-on-play-button.html
@@ -1,13 +1,21 @@
 <!DOCTYPE html>
 <html>
-<title>Test that player will play then pause if double tapped on the play button.</title>
+<title>Test that player will play then pause if double tapped on the overlay play button.</title>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src = '../../content/60_sec_video.webm';
+  document.body.appendChild(video);
   let didPause = false;
 
   video.onplaying = t.step_func(() => {
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/immersive-mode-adds-css-class.html b/third_party/WebKit/LayoutTests/media/controls/modern/immersive-mode-adds-css-class.html
index b99fe52e..61a49242 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/immersive-mode-adds-css-class.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/immersive-mode-adds-css-class.html
@@ -34,7 +34,6 @@
     const buttonPanelStyle = getComputedStyle(buttonPanelElement(video));
     const timelineStyle = getComputedStyle(timelineElement(video));
     const thumbStyle = getComputedStyle(timelineThumb(video));
-    const overlayPlayButtonStyle = getComputedStyle(mediaControlsOverlayPlayButtonInternal(video));
 
     assert_equals('43px', muteButtonStyle.height, 'Mute button height is respected');
     assert_equals('24px', fullscreenButtonStyle['background-size'], 'Fullscreen button background size is respected');
@@ -45,7 +44,12 @@
     assert_equals('5px', timelineStyle.height, 'Timeline height is respected');
     assert_equals('471px', timelineStyle['max-width'], 'Timeline max-width is respected');
     assert_equals('16px', thumbStyle.width, 'Thumb width is respected');
-    assert_equals('64px', overlayPlayButtonStyle.height, 'Overlay play button height is respected');
+
+    if (internals.runtimeFlags.mediaControlsOverlayPlayButtonEnabled) {
+      const overlayPlayButtonStyle = getComputedStyle(mediaControlsOverlayPlayButtonInternal(video));
+      assert_equals('64px', overlayPlayButtonStyle.height, 'Overlay play button height is respected');
+    }
+
   }
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/overlay-play-button-tap-to-hide.html b/third_party/WebKit/LayoutTests/media/controls/modern/overlay-play-button-tap-to-hide.html
index f9191ad..729fcc0 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/overlay-play-button-tap-to-hide.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/overlay-play-button-tap-to-hide.html
@@ -3,11 +3,18 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
+<script src="../overlay-play-button.js"></script>
 <body>
-<video id=video width=400 controls preload=metadata></video>
 <script>
 async_test((t) => {
-  const video = document.getElementById('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.width = 400;
+  video.controls = true;
+  video.preload = 'metadata'
+  document.body.appendChild(video);
   const button = mediaControlsOverlayPlayButtonInternal(video);
   const controls = mediaControls(video);
   const overlay = mediaControlsOverlayPlayButton(video);
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-outside.html b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-outside.html
index ae07b7e5..f253040 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-outside.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-outside.html
@@ -4,10 +4,18 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src = '../../content/60_sec_video.webm';
+  document.body.appendChild(video);
 
   video.addEventListener('playing', t.step_func(() => {
     // Single tap in the top right hand corner
@@ -16,15 +24,10 @@
     singleTapAtCoordinates(coordinates[0] + 1, coordinates[1] + 1);
   }), { once: true });
 
-  video.addEventListener('pause', t.unreached_func());
+  video.addEventListener('pause', t.done.bind(t));
 
   video.addEventListener('webkitfullscreenchange', t.unreached_func());
 
-  video.ontimeupdate = t.step_func(() => {
-    if (video.currentTime > 0)
-      t.done();
-  });
-
   video.play();
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-overlay-closes-overflow-menu.html b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-overlay-closes-overflow-menu.html
index bfcb67d..0948e16 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-overlay-closes-overflow-menu.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-overlay-closes-overflow-menu.html
@@ -4,10 +4,19 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src='../../content/60_sec_video.webm';
+  document.body.appendChild(video);
+
   var button = overflowButton(video);
   var menu = overflowMenu(video);
   // Need to add a text track for the overflow menu to appear.
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-play-button.html b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-play-button.html
index e7460298..b91556b 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-play-button.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/singletap-on-play-button.html
@@ -4,10 +4,18 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src='../../content/60_sec_video.webm';
+  document.body.appendChild(video);
 
   video.addEventListener('playing', t.step_func(() => {
     // Single tap in the middle of the button.
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/singletouch-on-play-button.html b/third_party/WebKit/LayoutTests/media/controls/modern/singletouch-on-play-button.html
index fb1c5de..860c963 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/singletouch-on-play-button.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/singletouch-on-play-button.html
@@ -4,10 +4,18 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src='../../content/60_sec_video.webm';
+  document.body.appendChild(video);
 
   video.addEventListener('playing', t.step_func(() => {
     // Single tap in the middle of the button.
diff --git a/third_party/WebKit/LayoutTests/media/controls/modern/video-does-not-go-fullscreen-on-double-click-before-preload.html b/third_party/WebKit/LayoutTests/media/controls/modern/video-does-not-go-fullscreen-on-double-click-before-preload.html
index f8595bd1..34bb9fb 100644
--- a/third_party/WebKit/LayoutTests/media/controls/modern/video-does-not-go-fullscreen-on-double-click-before-preload.html
+++ b/third_party/WebKit/LayoutTests/media/controls/modern/video-does-not-go-fullscreen-on-double-click-before-preload.html
@@ -4,10 +4,18 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
-<video controls width=400 preload=none src="../../content/60_sec_video.webm"></video>
+<script src="../overlay-play-button.js"></script>
+<body></body>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 400;
+  video.src='../../content/60_sec_video.webm';
+  document.body.appendChild(video);
   video.addEventListener("webkitfullscreenchange", t.unreached_func());
 
   window.onload = t.step_func(() => {
diff --git a/third_party/WebKit/LayoutTests/media/controls/overlay-play-button-resizes-with-video.html b/third_party/WebKit/LayoutTests/media/controls/overlay-play-button-resizes-with-video.html
index cfb8e57..2bcbd11 100644
--- a/third_party/WebKit/LayoutTests/media/controls/overlay-play-button-resizes-with-video.html
+++ b/third_party/WebKit/LayoutTests/media/controls/overlay-play-button-resizes-with-video.html
@@ -4,6 +4,7 @@
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../media-controls.js"></script>
+<script src="overlay-play-button.js"></script>
 <body></body>
 <script>
 const testCases = [
@@ -23,6 +24,9 @@
 });
 
 function runTestCase(t) {
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
   let test = t.properties;
   test.video = createVideo(test);
   setTimeout(t.step_func_done(() => {
diff --git a/third_party/WebKit/LayoutTests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html b/third_party/WebKit/LayoutTests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
index 62da232..a48b0a5 100644
--- a/third_party/WebKit/LayoutTests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
+++ b/third_party/WebKit/LayoutTests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
@@ -4,13 +4,21 @@
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../media-controls.js"></script>
+<script src="overlay-play-button.js"></script>
 <div id="outer">
-<video controls width=500 preload=none src="../content/60_sec_video.webm"></video>
 </div>
 <script>
 async_test(t => {
-  const video = document.querySelector('video');
+  // This test is only valid when the overlay play button is enabled.
+  enableOverlayPlayButtonForTest(t);
+
   const outerDiv = document.getElementById('outer');
+  const video = document.createElement('video');
+  video.controls = true;
+  video.width = 500;
+  video.preload = 'none';
+  video.src = '../content/60_sec_video.webm';
+  outerDiv.appendChild(video);
 
   video.addEventListener('loadedmetadata', t.step_func(() => {
     singleTouchOnControl(mediaControlsOverlayPlayButton(video));
diff --git a/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html b/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html
index 100f4af5..b1d82643 100644
--- a/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html
+++ b/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html
@@ -19,7 +19,7 @@
     var panel = mediaControlsButton(video, "panel");
 
     // Move mouse to the play button and start playing the video.
-    clickAtCoordinates(...mediaControlsButtonCoordinates(video, "overlay-play-button"));
+    clickAtCoordinates(...elementCoordinates(enabledPlayButton(video)));
 
     assert_equals(getComputedStyle(panel).opacity, "1",
                   "Inline controls should initially show since controls " +
diff --git a/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html b/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html
index ed356bb..1c434cf8 100644
--- a/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html
+++ b/third_party/WebKit/LayoutTests/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html
@@ -20,7 +20,7 @@
     var panel = mediaControlsButton(video, "panel");
 
     // Move mouse to the play button and start playing the video.
-    clickAtCoordinates(...mediaControlsButtonCoordinates(video, "overlay-play-button"));
+    clickAtCoordinates(...elementCoordinates(enabledPlayButton(video)));
 
     assert_equals(getComputedStyle(panel).opacity, "1",
                   "Inline controls should initially show since controls " +
diff --git a/third_party/WebKit/LayoutTests/media/media-controls-tap-show-controls-without-activating.html b/third_party/WebKit/LayoutTests/media/media-controls-tap-show-controls-without-activating.html
index fd3bc4d..3cb54aa 100644
--- a/third_party/WebKit/LayoutTests/media/media-controls-tap-show-controls-without-activating.html
+++ b/third_party/WebKit/LayoutTests/media/media-controls-tap-show-controls-without-activating.html
@@ -14,7 +14,7 @@
         runAfterHideMediaControlsTimerFired(t.step_func(function() {
           assert_false(isControlsPanelVisible(video));
 
-          var coords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+          var coords = elementCoordinates(enabledPlayButton(video));
 
           eventSender.gestureTapDown(coords[0], coords[1]);
           assert_false(video.paused);
diff --git a/third_party/WebKit/LayoutTests/media/media-controls.js b/third_party/WebKit/LayoutTests/media/media-controls.js
index 083af60..d0504e93 100644
--- a/third_party/WebKit/LayoutTests/media/media-controls.js
+++ b/third_party/WebKit/LayoutTests/media/media-controls.js
@@ -287,6 +287,13 @@
     return mediaControlsButton(videoElement, 'play-button');
 }
 
+function enabledPlayButton(videoElement) {
+  if (internals.runtimeFlags.mediaControlsOverlayPlayButtonEnabled) {
+    return mediaControlsOverlayPlayButton(videoElement);
+  }
+  return playButton(videoElement);
+}
+
 function muteButton(videoElement) {
     return mediaControlsButton(videoElement, 'mute-button');
 }
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-always-visible-when-control-hovered.html b/third_party/WebKit/LayoutTests/media/video-controls-always-visible-when-control-hovered.html
index dded11d1..0eb24f737 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-always-visible-when-control-hovered.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-always-visible-when-control-hovered.html
@@ -13,7 +13,7 @@
         assert_true(video.paused);
 
         // Click the play button.
-        var playCoords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var playCoords = elementCoordinates(enabledPlayButton(video));
         eventSender.mouseMoveTo(playCoords[0], playCoords[1]);
         eventSender.mouseDown();
         eventSender.mouseUp();
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-auto-hide-after-play-by-touch.html b/third_party/WebKit/LayoutTests/media/video-controls-auto-hide-after-play-by-touch.html
index 9760781..4650405 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-auto-hide-after-play-by-touch.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-auto-hide-after-play-by-touch.html
@@ -14,7 +14,7 @@
         assert_true(video.paused);
 
         // Click the play button.
-        var playCoords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var playCoords = elementCoordinates(enabledPlayButton(video));
         var clickX = playCoords[0];
         var clickY = playCoords[1];
         eventSender.gestureTap(clickX, clickY);
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-hide-after-touch-on-control.html b/third_party/WebKit/LayoutTests/media/video-controls-hide-after-touch-on-control.html
index a838e2e..5809fa9 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-hide-after-touch-on-control.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-hide-after-touch-on-control.html
@@ -12,7 +12,7 @@
         assert_true(video.paused);
 
         // Tap the play button
-        var coords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var coords = elementCoordinates(enabledPlayButton(video));
         eventSender.gestureTapDown(coords[0], coords[1]);
         eventSender.gestureShowPress(coords[0], coords[1]);
         eventSender.gestureTap(coords[0], coords[1]);
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-hide-on-move-outside-controls.html b/third_party/WebKit/LayoutTests/media/video-controls-hide-on-move-outside-controls.html
index fecb0dd2..8e3c19c 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-hide-on-move-outside-controls.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-hide-on-move-outside-controls.html
@@ -12,7 +12,7 @@
         assert_true(video.paused);
 
         // Click the play button.
-        var coords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var coords = elementCoordinates(enabledPlayButton(video));
         eventSender.mouseMoveTo(coords[0], coords[1]);
         eventSender.mouseDown();
         eventSender.mouseUp();
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-transformed.html b/third_party/WebKit/LayoutTests/media/video-controls-transformed.html
index 4b4d86fa..1ee7be9 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-transformed.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-transformed.html
@@ -17,7 +17,7 @@
 
     video.oncanplaythrough = t.step_func_done(function() {
         // Find the play button and click the middle of its bounding box.
-        var playCoords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var playCoords = elementCoordinates(enabledPlayButton(video));
         eventSender.mouseMoveTo(playCoords[0], playCoords[1]);
         eventSender.mouseDown();
         eventSender.mouseUp();
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-mouse-after-touch.html b/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-mouse-after-touch.html
index 97ac8e4..55c83be 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-mouse-after-touch.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-mouse-after-touch.html
@@ -12,7 +12,7 @@
         assert_true(video.paused);
 
         // Tap (touch input) the play button.
-        var coords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var coords = elementCoordinates(enabledPlayButton(video));
         eventSender.gestureTapDown(coords[0], coords[1]);
         eventSender.gestureShowPress(coords[0], coords[1]);
         eventSender.gestureTap(coords[0], coords[1]);
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-touch-after-mouse.html b/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-touch-after-mouse.html
index f389cd0..6fe2561 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-touch-after-mouse.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-visibility-multimodal-touch-after-mouse.html
@@ -7,12 +7,13 @@
 <script>
 async_test(function(t) {
     var video = document.querySelector("video");
+    enableTestMode(video);
 
     video.oncanplaythrough = t.step_func(function() {
         assert_true(video.paused);
 
         // Hover the control with the mouse.
-        var coords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var coords = elementCoordinates(enabledPlayButton(video));
         eventSender.mouseMoveTo(coords[0], coords[1]);
 
         // And then tap (touch input) the play button.
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-zoomed.html b/third_party/WebKit/LayoutTests/media/video-controls-zoomed.html
index 07f985b..04b9cc1 100644
--- a/third_party/WebKit/LayoutTests/media/video-controls-zoomed.html
+++ b/third_party/WebKit/LayoutTests/media/video-controls-zoomed.html
@@ -22,7 +22,7 @@
             });
 
             // Find the play button and click the middle of its bounding box.
-            var playCoords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+            var playCoords = elementCoordinates(enabledPlayButton(video));
 
             eventSender.mouseMoveTo(playCoords[0] * 1.5, playCoords[1] * 1.5);
             eventSender.mouseDown();
diff --git a/third_party/WebKit/LayoutTests/media/video-play-require-user-gesture.html b/third_party/WebKit/LayoutTests/media/video-play-require-user-gesture.html
index 136274c6..2667aef 100644
--- a/third_party/WebKit/LayoutTests/media/video-play-require-user-gesture.html
+++ b/third_party/WebKit/LayoutTests/media/video-play-require-user-gesture.html
@@ -19,7 +19,7 @@
 
         // User gesture initiated.
         userGestureInitiated = true;
-        var playCoords = mediaControlsButtonCoordinates(video, "overlay-play-button");
+        var playCoords = elementCoordinates(enabledPlayButton(video));
         eventSender.mouseMoveTo(playCoords[0], playCoords[1]);
         eventSender.mouseDown();
         eventSender.mouseUp();
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/video-paint-invalidation-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/video-paint-invalidation-expected.txt
index a0c1265..e2b1cf40 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/video-paint-invalidation-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/video-paint-invalidation-expected.txt
@@ -36,22 +36,6 @@
       "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV)",
       "position": [8, 8],
       "bounds": [320, 240]
-    },
-    {
-      "name": "LayoutFlexibleBox DIV",
-      "position": [8, 8],
-      "bounds": [320, 240]
-    },
-    {
-      "name": "Child Containment Layer",
-      "position": [8, 8],
-      "bounds": [320, 240],
-      "drawsContent": false
-    },
-    {
-      "name": "LayoutButton (positioned) INPUT",
-      "position": [118, 66],
-      "bounds": [100, 100]
     }
   ]
 }
diff --git a/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-controls-layer-creation-expected.png b/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-controls-layer-creation-expected.png
index b6aaaf4..4f5be9b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-controls-layer-creation-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/compositing/video/video-controls-layer-creation-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/overflow/overflow-of-video-outline-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/overflow/overflow-of-video-outline-expected.png
index dd431b4..8d98b38 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/fast/overflow/overflow-of-video-outline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/fast/overflow/overflow-of-video-outline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.png
index 44d9038..af2d071b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.txt
index 07d397e..2195cd9 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-layout-direction-expected.png
index 3af4d8b..8307e65 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.png
index f4562b3..58856e2 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.txt
index 14b3278f..32e696c 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,220) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.png
index 89a5a0e..ae1db3b4 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.txt
index 5d39a85..a46b3d6 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 66x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (114,48) size 110x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 66x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (146,48) size 78x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (242,212) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,452) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.png
index 353a2ce8..2948071 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.txt
index 22b3e5e6..f039fd4 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,220) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (556,220) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.png
index d8fd7cb..0f254c8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.txt
index 119340a..1ece823 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/lazy-loaded-style-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls/lazy-loaded-style-expected.txt
index 9c7d82f..a005ba0 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/lazy-loaded-style-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/lazy-loaded-style-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x300
 layer at (8,8) size 400x300
   LayoutFlexibleBox {DIV} at (0,0) size 400x300
-layer at (151,89) size 115x115
-  LayoutButton (positioned) {INPUT} at (142.50,80.50) size 115x115
-    LayoutBlockFlow (anonymous) at (20,20) size 75x75
-      LayoutBlockFlow {DIV} at (0,0) size 75x75 [bgcolor=#FFFFFFE6]
 layer at (8,236) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,228) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (304,0) size 48x48
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,236) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 7744a07..120b231 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
index 362a1479..5bac4a4b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.png
index 7744a07..4341c83 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.txt
index 362a1479..5bac4a4b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/paint-controls-webkit-appearance-none-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.png
index 3aaa55c..78014c8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.txt
index a6e883e..c2c340d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/controls/video-controls-with-cast-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,297) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,355) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,465) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,542) size 320x240 backgroundClip at (8,542) size 320x58 clip at (8,542) size 320x58
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,600) size 100x100 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,710) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.png
index 1887e224..138bbbf50 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.txt
index 3e0bffd1..0e9e2c0 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-clone-expected.txt
@@ -42,13 +42,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,104) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#9D968E]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,129) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,129) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#9D968E]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,129) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,129) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,129) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,129) size 0x4
@@ -82,13 +81,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,259) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#9D968E]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,284) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,284) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#9D968E]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,284) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,284) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,284) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,284) size 0x4
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.png
index 3b15efd..a9868aa 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.txt
index 250c2ab..0f8ed2e3 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/media-controls-grey-scrubber-expected.txt
@@ -13,16 +13,13 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (8,8) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (114,27) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-      LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (8,86) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 160x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 128x0
 layer at (212,86) size 48x48 transparent
   LayoutButton {INPUT} at (204,0) size 48x48 [color=#808080]
 layer at (260,86) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.png
index d186f610..6f061986 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.txt
index 63195ebd..49823c2 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,452) size 0x48 transparent
@@ -118,19 +112,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,524) size 320x240 backgroundClip at (8,524) size 320x76 clip at (8,524) size 320x76
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,582) size 100x100 backgroundClip at (118,582) size 100x18 clip at (118,582) size 100x18
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,692) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,692) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.png
index 32c2e159..3368e343 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.txt
index 1e6ca8e..fd4da0c 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,28) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,86) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,196) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,196) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.png
index 12d0f5f..8e2e3c1 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.txt
index 0da22bb..1b9541c5 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-empty-source-expected.txt
@@ -17,17 +17,14 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (9,45) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (115,64) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-layer at (135,84) size 48x48 transparent
-  LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (9,123) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 112x0
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 80x0
+layer at (9,123) size 48x48 transparent
+  LayoutButton {INPUT} at (0,0) size 48x48 [color=#808080]
 layer at (165,123) size 48x48 transparent
   LayoutButton {INPUT} at (156,0) size 48x48 [color=#808080]
 layer at (213,123) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.png
index 1d95d1b..3072ab0b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.txt
index d067b18..6601fee6 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-no-audio-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 352x288
 layer at (8,44) size 352x288
   LayoutFlexibleBox {DIV} at (0,0) size 352x288
-layer at (128,120) size 112x112
-  LayoutButton (positioned) {INPUT} at (120,76) size 112x112
-    LayoutBlockFlow (anonymous) at (20,20) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
 layer at (8,260) size 352x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,216) size 352x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 172x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 140x0
     LayoutButton {INPUT} at (304,0) size 48x48
 layer at (264,260) size 48x48 transparent
   LayoutButton {INPUT} at (256,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
index f4b0048..0d30c06 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
index b75f1d0..be34eb7f 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
@@ -15,33 +15,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (57,85) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 258
+layer at (57,157) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-layer at (171,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#9D968E]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (225,157) size 0x72 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#9D968E]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (171,190) size 18x6
+layer at (225,190) size 18x6
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (171,190) size 18x6 scrollWidth 27
+layer at (225,190) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (171,190) size 0x6
+layer at (225,190) size 0x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (171,190) size 27x6 backgroundClip at (171,190) size 18x6 clip at (171,190) size 18x6
+layer at (225,190) size 27x6 backgroundClip at (225,190) size 18x6 clip at (225,190) size 18x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (57,229) size 240x36
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#9D968E]
@@ -65,33 +54,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (43,291) size 268x218 clip at (43,291) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 258
+layer at (49,361) size 249x113 clip at (49,361) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-layer at (162,381) size 12x71 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#9D968E]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (215,391) size 12x71 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#9D968E]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (167,414) size 19x9
+layer at (221,423) size 18x9
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (167,414) size 19x9 clip at (167,414) size 18x6 scrollWidth 27
+layer at (221,423) size 18x9 clip at (221,423) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (167,414) size 2x6
+layer at (221,423) size 1x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (167,414) size 28x10 backgroundClip at (167,414) size 19x9 clip at (167,414) size 19x9
+layer at (221,423) size 27x11 backgroundClip at (221,423) size 18x9 clip at (221,423) size 18x9
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (43,432) size 243x77
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#9D968E]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
index ca51ff90..8aedabea 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/full-screen-iframe-allowed-video-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
index e16ffc5..ef81892 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-controls-timeline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-overlay-scroll-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-overlay-scroll-expected.txt
deleted file mode 100644
index c018a97..0000000
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-overlay-scroll-expected.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-EVENT(fullscreenchange)
-END OF TEST
-{
-  "layers": [
-    {
-      "name": "LayoutVideo (positioned) VIDEO id='video'",
-      "bounds": [800, 600],
-      "drawsContent": false
-    },
-    {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='phase-pre-ready state-no-source use-default-poster sizing-medium'",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#333333"
-    },
-    {
-      "name": "LayoutBlockFlow (positioned) DIV",
-      "bounds": [800, 600],
-      "drawsContent": false
-    },
-    {
-      "name": "LayoutFlexibleBox DIV",
-      "bounds": [800, 600]
-    },
-    {
-      "name": "Child Containment Layer",
-      "bounds": [800, 600],
-      "drawsContent": false
-    },
-    {
-      "name": "LayoutButton (positioned) INPUT",
-      "position": [335, 223],
-      "bounds": [130, 130]
-    },
-    {
-      "name": "Ancestor Clipping Layer",
-      "position": [335, 223],
-      "bounds": [130, 130],
-      "drawsContent": false
-    },
-    {
-      "name": "LayoutBlockFlow DIV",
-      "position": [336, 224],
-      "bounds": [128, 128],
-      "opacity": 0.300000011920929,
-      "backgroundColor": "#FFFFFFE6"
-    }
-  ]
-}
-
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
index ca51ff90..8aedabea 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/android/fullscreen/video-scrolled-iframe-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/lazy-loaded-style-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/lazy-loaded-style-expected.txt
index 9c7d82f..a005ba0 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/lazy-loaded-style-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/lazy-loaded-style-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x300
 layer at (8,8) size 400x300
   LayoutFlexibleBox {DIV} at (0,0) size 400x300
-layer at (151,89) size 115x115
-  LayoutButton (positioned) {INPUT} at (142.50,80.50) size 115x115
-    LayoutBlockFlow (anonymous) at (20,20) size 75x75
-      LayoutBlockFlow {DIV} at (0,0) size 75x75 [bgcolor=#FFFFFFE6]
 layer at (8,236) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,228) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (304,0) size 48x48
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,236) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 7744a07..120b231 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
index 362a1479..5bac4a4b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
index 7744a07..4341c83 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.txt
index 362a1479..5bac4a4b 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.png
index 3aaa55c..78014c8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.txt
index a6e883e..c2c340d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/new-remote-playback-pipeline/media/controls/video-controls-with-cast-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,297) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,355) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,465) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,542) size 320x240 backgroundClip at (8,542) size 320x58 clip at (8,542) size 320x58
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,600) size 100x100 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,710) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.png
index 4c6ac2f..8c2df1c 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.txt
index b61bba8e..5a9b399 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-layout-direction-expected.png
index 3af4d8b..8307e65 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.png
index 4e3bc5b..45b8be89 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.txt
index 71f1589..8147bf17 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.png
index 05df64de..744888a 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.txt
index 4006d6d..54b6ba8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 66x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (114,48) size 62x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 66x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (146,48) size 30x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.png
index 28f4432..e4574ee 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
index 87f4643..2dbdf17 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.png
index d1b1798d..43d1d77 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.txt
index 73d2da6..fbe4340 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.png
index 3aaa55c..78014c8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.txt
index a6e883e..c2c340d 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/controls/video-controls-with-cast-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,297) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,355) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,465) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,542) size 320x240 backgroundClip at (8,542) size 320x58 clip at (8,542) size 320x58
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,600) size 100x100 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,710) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.png
index 1887e224..138bbbf50 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.txt
index 3e0bffd1..0e9e2c0 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-clone-expected.txt
@@ -42,13 +42,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,104) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#9D968E]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,129) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,129) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#9D968E]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,129) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,129) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,129) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,129) size 0x4
@@ -82,13 +81,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,259) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#9D968E]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,284) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,284) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#9D968E]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,284) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,284) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,284) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,284) size 0x4
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
index 3b15efd..a9868aa 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.txt
index 250c2ab..0f8ed2e3 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.txt
@@ -13,16 +13,13 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (8,8) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (114,27) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-      LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (8,86) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 160x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 128x0
 layer at (212,86) size 48x48 transparent
   LayoutButton {INPUT} at (204,0) size 48x48 [color=#808080]
 layer at (260,86) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.png
index ba6b2a0..51f69271 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
index a9b0006..0b2b901 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,524) size 320x240 backgroundClip at (8,524) size 320x76 clip at (8,524) size 320x76
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,582) size 100x100 backgroundClip at (118,582) size 100x18 clip at (118,582) size 100x18
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,692) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.png
index 66c0d6f..d6e30d2 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.txt
index ec3ccd9..42ebcb0 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,28) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,86) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,196) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.png
index 12d0f5f..8e2e3c1 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.txt
index 0da22bb..1b9541c5 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-empty-source-expected.txt
@@ -17,17 +17,14 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (9,45) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (115,64) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-layer at (135,84) size 48x48 transparent
-  LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (9,123) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 112x0
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 80x0
+layer at (9,123) size 48x48 transparent
+  LayoutButton {INPUT} at (0,0) size 48x48 [color=#808080]
 layer at (165,123) size 48x48 transparent
   LayoutButton {INPUT} at (156,0) size 48x48 [color=#808080]
 layer at (213,123) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.png
index bc09186..bb9aa8b3 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.txt
index 372a8d1..ab203036 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-no-audio-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 352x288
 layer at (8,44) size 352x288
   LayoutFlexibleBox {DIV} at (0,0) size 352x288
-layer at (128,120) size 112x112
-  LayoutButton (positioned) {INPUT} at (120,76) size 112x112
-    LayoutBlockFlow (anonymous) at (20,20) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
 layer at (8,260) size 352x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,216) size 352x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 124x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 92x0
     LayoutButton {INPUT} at (256,0) size 48x48
     LayoutButton {INPUT} at (304,0) size 48x48
 layer at (216,260) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.png
index 1e845cb..6201165 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
index 7bc22b6..be34eb7f 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
@@ -15,34 +15,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (57,85) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 330
+layer at (57,157) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-    LayoutButton {INPUT} at (258,0) size 72x72
-layer at (171,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#9D968E]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (225,157) size 0x72 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#9D968E]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (171,190) size 18x6
+layer at (225,190) size 18x6
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (171,190) size 18x6 scrollWidth 27
+layer at (225,190) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (171,190) size 0x6
+layer at (225,190) size 0x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (171,190) size 27x6 backgroundClip at (171,190) size 18x6 clip at (171,190) size 18x6
+layer at (225,190) size 27x6 backgroundClip at (225,190) size 18x6 clip at (225,190) size 18x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (57,229) size 240x36
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#9D968E]
@@ -66,34 +54,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (43,291) size 268x218 clip at (43,291) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 330
+layer at (49,361) size 249x113 clip at (49,361) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-    LayoutButton {INPUT} at (258,0) size 72x72
-layer at (162,381) size 12x71 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#9D968E]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (215,391) size 12x71 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#9D968E]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (167,414) size 19x9
+layer at (221,423) size 18x9
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (167,414) size 19x9 clip at (167,414) size 18x6 scrollWidth 27
+layer at (221,423) size 18x9 clip at (221,423) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (167,414) size 2x6
+layer at (221,423) size 1x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (167,414) size 28x10 backgroundClip at (167,414) size 19x9 clip at (167,414) size 19x9
+layer at (221,423) size 27x11 backgroundClip at (221,423) size 18x9 clip at (221,423) size 18x9
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (43,432) size 243x77
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#9D968E]
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/media/video-zoom-controls-expected.png
deleted file mode 100644
index f786942..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/media/video-zoom-controls-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/video-surface-layer/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/video-surface-layer/media/video-zoom-controls-expected.png
deleted file mode 100644
index a28f8fab..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/video-surface-layer/media/video-zoom-controls-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/compositing/video/video-controls-layer-creation-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/compositing/video/video-controls-layer-creation-expected.png
index eeb3ddc..e7d3ff7 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/compositing/video/video-controls-layer-creation-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/compositing/video/video-controls-layer-creation-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/fast/overflow/overflow-of-video-outline-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/fast/overflow/overflow-of-video-outline-expected.png
index 1b248183..c59ff35 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/fast/overflow/overflow-of-video-outline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/fast/overflow/overflow-of-video-outline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-after-reload-expected.png
index 9117e226..86276bb 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-layout-direction-expected.png
index ae76971..ee415f0 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-strict-expected.png
index 58207902..83d0ebfa 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-expected.png
index 60b94b8..df2a7f2a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-strict-expected.png
index 33690f9..ccf39e5 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-without-preload-expected.png
index e73e31d..4a55d10 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 20644d0..f5398dc8 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-expected.png
index 20644d0..c1a23f7d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-clone-expected.png
index 8cc5110..b907545 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-grey-scrubber-expected.png
index 5dd371d..69170764 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-controls-rendering-expected.png
index 004affd..7b7848d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-display-toggle-expected.png
index 05eac13e..897023e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-empty-source-expected.png
index 62ae15e..2550ff8 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-no-audio-expected.png
index 1bc397b..2557d3e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-zoom-controls-expected.png
index 336cd246..0462bbf 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 20644d0..f5398dc8 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
index 20644d0..c1a23f7d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/new-remote-playback-pipeline/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-after-reload-expected.png
index 1eaa335..f23c766 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-layout-direction-expected.png
index ae76971..ee415f0 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-strict-expected.png
index 08598ecd..918138b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-expected.png
index 587cdf71..2a62ad5 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-strict-expected.png
index 081f1fe0..14c39a3 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-without-preload-expected.png
index c62e66f..e43fdb7 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-clone-expected.png
index 8cc5110..b907545 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
index 5dd371d..69170764 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-controls-rendering-expected.png
index 8e42b2b..ce354585f 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-display-toggle-expected.png
index c9f86b4..bd14330 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-empty-source-expected.png
index 62ae15e..2550ff8 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-zoom-controls-expected.png
index 557f42884..947ca89 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.12/virtual/video-surface-layer/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-controls-layer-creation-expected.png b/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-controls-layer-creation-expected.png
index f2001dc..6f1d074 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-controls-layer-creation-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/compositing/video/video-controls-layer-creation-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/overflow/overflow-of-video-outline-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/overflow/overflow-of-video-outline-expected.png
index c8631bd..676354f2 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/fast/overflow/overflow-of-video-outline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/fast/overflow/overflow-of-video-outline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.png
index 05a9d16..e7bd7cc 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.txt
index 234ca03..ccc5608 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,210) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-layout-direction-expected.png
index 0862e85..41f88b6e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.png
index 2df02901..b84d92af 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.txt
index dc9fd62..84d2af2 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,218) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.png
index 8fd38ecad..7804a7c 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.txt
index 6cd702a6..7608023d 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 65.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (112.28,48) size 111.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 65.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (144.28,48) size 79.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (242,210) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,282) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,340) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,450) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,450) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.png
index 0a2cbe62..0d46dcb 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.txt
index cc003e6..88e6c08 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,218) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (556,218) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.png
index 695be562c..d32f00c 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.txt
index c7910e7..5ee8ca90 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,210) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls/lazy-loaded-style-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls/lazy-loaded-style-expected.txt
index 0239b40..7702252 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls/lazy-loaded-style-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls/lazy-loaded-style-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x300
 layer at (8,8) size 400x300
   LayoutFlexibleBox {DIV} at (0,0) size 400x300
-layer at (151,89) size 115x115
-  LayoutButton (positioned) {INPUT} at (142.50,80.50) size 115x115
-    LayoutBlockFlow (anonymous) at (20,20) size 75x75
-      LayoutBlockFlow {DIV} at (0,0) size 75x75 [bgcolor=#FFFFFFE6]
 layer at (8,236) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,228) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 221.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 189.72x0
     LayoutButton {INPUT} at (304,0) size 48x48
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,236) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 93e28e1..d3de7e9 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
index df4fb91..1459cbb4 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (82.28,48) size 221.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (114.28,48) size 189.72x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#7F7F7F]
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.png
index 93e28e1..7dc5dcb 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.txt
index df4fb91..1459cbb4 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/controls/paint-controls-webkit-appearance-none-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (82.28,48) size 221.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (114.28,48) size 189.72x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#7F7F7F]
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.png
index e08b5ba..6fae837 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.txt
index 5afb9bd..9ba2571 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-clone-expected.txt
@@ -42,13 +42,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,104) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#909090]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,129) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,129) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#909090]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,129) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,129) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,129) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,129) size 0x4
@@ -82,13 +81,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,258) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#909090]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,283) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,283) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#909090]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,283) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,283) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,283) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,283) size 0x4
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.png
index 467fb97..5cc4966 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.txt
index 4b6994a..e3ee427 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/media-controls-grey-scrubber-expected.txt
@@ -13,16 +13,13 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (8,8) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (114,27) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-      LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (8,86) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (43.25,48) size 160.75x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (75.25,48) size 128.75x0
 layer at (212,86) size 48x48 transparent
   LayoutButton {INPUT} at (204,0) size 48x48 [color=#7F7F7F]
 layer at (260,86) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.png
index a96a444..b9436e4 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.txt
index 9b2361e..3330a368 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,210) size 0x48 transparent
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,282) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,340) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,450) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,450) size 0x48 transparent
@@ -118,19 +112,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,522) size 320x240 backgroundClip at (8,522) size 320x78 clip at (8,522) size 320x78
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,580) size 100x100 backgroundClip at (118,580) size 100x20 clip at (118,580) size 100x20
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,690) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,690) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.png
index 0eb1c2b..3795abc 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.txt
index 31c9da1f..3bf0b40 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,26) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,84) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,194) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 141.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 109.72x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,194) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.png
index 302ad884..1d9f54a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.txt
index b3469148..b474ca6e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-empty-source-expected.txt
@@ -17,17 +17,14 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (9,43) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (115,62) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-layer at (135,82) size 48x48 transparent
-  LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (9,121) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (43.25,48) size 112.75x0
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (75.25,48) size 80.75x0
+layer at (9,121) size 48x48 transparent
+  LayoutButton {INPUT} at (0,0) size 48x48 [color=#7F7F7F]
 layer at (165,121) size 48x48 transparent
   LayoutButton {INPUT} at (156,0) size 48x48 [color=#7F7F7F]
 layer at (213,121) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.png
index 56ec42d3..2443723 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.txt
index bb05e7a..66636d6 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-no-audio-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 352x288
 layer at (8,42) size 352x288
   LayoutFlexibleBox {DIV} at (0,0) size 352x288
-layer at (128,118) size 112x112
-  LayoutButton (positioned) {INPUT} at (120,76) size 112x112
-    LayoutBlockFlow (anonymous) at (20,20) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
 layer at (8,258) size 352x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,216) size 352x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (82.28,48) size 173.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (114.28,48) size 141.72x0
     LayoutButton {INPUT} at (304,0) size 48x48
 layer at (264,258) size 48x48 transparent
   LayoutButton {INPUT} at (256,0) size 48x48 [color=#7F7F7F]
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
index 8ca4730..65ca7c7e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.txt
index 368eaff..51e8b9b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/media/video-zoom-controls-expected.txt
@@ -15,33 +15,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (57,85) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 256
+layer at (57,157) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 40.88x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x24
-        text run at (0,21) width 41: "0:00"
-    LayoutBlockFlow {DIV} at (70.88,-60) size 40.88x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 41: "0:06"
-    LayoutBlockFlow {DIV} at (111.75,72) size 0x0
-    LayoutButton {INPUT} at (111.75,0) size 72x72
-    LayoutButton {INPUT} at (183.75,0) size 72x72
-layer at (169,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (111.75,0) size 0x72 [color=#909090]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (225,157) size 0x72 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#909090]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (169,190) size 18x6
+layer at (225,190) size 18x6
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (169,190) size 18x6 scrollWidth 27
+layer at (225,190) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (169,190) size 0x6
+layer at (225,190) size 0x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (169,190) size 27x6 backgroundClip at (169,190) size 18x6 clip at (169,190) size 18x6
+layer at (225,190) size 27x6 backgroundClip at (225,190) size 18x6 clip at (225,190) size 18x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (57,229) size 240x36
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#909090]
@@ -65,33 +54,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (43,291) size 268x218 clip at (43,291) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 256
+layer at (49,361) size 249x113 clip at (49,361) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 40.88x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x24
-        text run at (0,21) width 41: "0:00"
-    LayoutBlockFlow {DIV} at (70.88,-60) size 40.88x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 41: "0:06"
-    LayoutBlockFlow {DIV} at (111.75,72) size 0x0
-    LayoutButton {INPUT} at (111.75,0) size 72x72
-    LayoutButton {INPUT} at (183.75,0) size 72x72
-layer at (159,381) size 13x71 transparent
-  LayoutSlider {INPUT} at (111.75,0) size 0x72 [color=#909090]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (215,391) size 12x71 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#909090]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (165,413) size 19x9
+layer at (221,423) size 18x9
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (165,413) size 19x9 clip at (165,413) size 18x6 scrollWidth 27
+layer at (221,423) size 18x9 clip at (221,423) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (165,413) size 1x6
+layer at (221,423) size 1x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (165,413) size 28x11 backgroundClip at (165,413) size 19x9 clip at (165,413) size 19x9
+layer at (221,423) size 27x11 backgroundClip at (221,423) size 18x9 clip at (221,423) size 18x9
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (43,432) size 243x77
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#909090]
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.png
index ff76c075..3166d16 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.txt
index 7313fec..ec38389 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.png
index 6df2a7b..1232bba86 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.txt
index ca359621..a6b0496 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.png
index a6f86d9b..70870275 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.txt
index c4383e6..d23718f 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 65.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (112.28,48) size 63.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 65.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (144.28,48) size 31.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,282) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,340) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,450) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.png
index 19624bc..c74ab967 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
index 12d4853..960af0f 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,50) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,108) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,218) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.png
index 7ec320d..1d20bca 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.txt
index 3a95ed9..ebebdde 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.png
index 04e720b..5c4be3a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
index 15b32fd..fe171ae 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,42) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,100) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,210) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,282) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,340) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,450) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,522) size 320x240 backgroundClip at (8,522) size 320x78 clip at (8,522) size 320x78
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,580) size 100x100 backgroundClip at (118,580) size 100x20 clip at (118,580) size 100x20
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,690) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.png
index 389d207..61bab506 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.txt
index 9d4ff732..0b7a2ad 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,26) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,84) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,194) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 27.25x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (47.25,4) size 35.03x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (82.28,48) size 93.72x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 27.25x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (79.25,0) size 35.03x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (114.28,48) size 61.72x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.png
index 6494d7f8..058e324 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
deleted file mode 100644
index de9743c..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
+++ /dev/null
@@ -1,110 +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 (12,12) size 776x543
-      LayoutBlockFlow {P} at (0,0) size 776x28
-        LayoutText {#text} at (0,0) size 278x28
-          text run at (0,0) width 278: "Zoomed video with controls."
-layer at (57,85) size 240x180
-  LayoutVideo {VIDEO} at (45,73) size 240x180
-layer at (57,85) size 240x180
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x180
-    LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (57,85) size 240x180
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
-layer at (57,85) size 240x180
-  LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 328
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 40.88x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x24
-        text run at (0,21) width 41: "0:00"
-    LayoutBlockFlow {DIV} at (70.88,-60) size 40.88x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 41: "0:06"
-    LayoutBlockFlow {DIV} at (111.75,72) size 0x0
-    LayoutButton {INPUT} at (111.75,0) size 72x72
-    LayoutButton {INPUT} at (183.75,0) size 72x72
-    LayoutButton {INPUT} at (255.75,0) size 72x72
-layer at (169,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (111.75,0) size 0x72 [color=#909090]
-    LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (169,190) size 18x6
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
-    LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (169,190) size 18x6 scrollWidth 27
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (169,190) size 0x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (169,190) size 27x6 backgroundClip at (169,190) size 18x6 clip at (169,190) size 18x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
-layer at (57,229) size 240x36
-  LayoutSlider {INPUT} at (0,144) size 240x36 [color=#909090]
-    LayoutFlexibleBox {DIV} at (24,0) size 192x6
-layer at (81,229) size 192x6
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 192x6 [bgcolor=#FFFFFF4D]
-layer at (81,223) size 18x18
-  LayoutBlockFlow (relative positioned) {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (81,229) size 192x6 scrollWidth 288
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 192x6
-layer at (81,229) size 0x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (81,229) size 288x6 backgroundClip at (81,229) size 192x6 clip at (81,229) size 192x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 288x6 [bgcolor=#FFFFFF8A]
-layer at (57,310) size 240x180
-  LayoutVideo {VIDEO} at (45,298) size 240x180
-layer at (43,291) size 268x218
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x180
-    LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (43,291) size 268x218
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
-layer at (43,291) size 268x218 clip at (43,291) size 240x180
-  LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 328
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 40.88x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x24
-        text run at (0,21) width 41: "0:00"
-    LayoutBlockFlow {DIV} at (70.88,-60) size 40.88x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 41x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 41: "0:06"
-    LayoutBlockFlow {DIV} at (111.75,72) size 0x0
-    LayoutButton {INPUT} at (111.75,0) size 72x72
-    LayoutButton {INPUT} at (183.75,0) size 72x72
-    LayoutButton {INPUT} at (255.75,0) size 72x72
-layer at (159,381) size 13x71 transparent
-  LayoutSlider {INPUT} at (111.75,0) size 0x72 [color=#909090]
-    LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (165,413) size 19x9
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
-    LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (165,413) size 19x9 clip at (165,413) size 18x6 scrollWidth 27
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (165,413) size 1x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (165,413) size 28x11 backgroundClip at (165,413) size 19x9 clip at (165,413) size 19x9
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
-layer at (43,432) size 243x77
-  LayoutSlider {INPUT} at (0,144) size 240x36 [color=#909090]
-    LayoutFlexibleBox {DIV} at (24,0) size 192x6
-layer at (72,437) size 190x39
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 192x6 [bgcolor=#FFFFFF4D]
-layer at (71,431) size 21x20
-  LayoutBlockFlow (relative positioned) {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (72,437) size 190x39 clip at (72,437) size 190x6 scrollWidth 288
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 192x6
-layer at (72,437) size 1x5
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (72,437) size 285x55 backgroundClip at (72,437) size 190x39 clip at (72,437) size 190x39
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 288x6 [bgcolor=#FFFFFF8A]
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/overflow/overflow-of-video-outline-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/overflow/overflow-of-video-outline-expected.png
index 358a2398..641dbb8a 100644
--- a/third_party/WebKit/LayoutTests/platform/win/fast/overflow/overflow-of-video-outline-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/fast/overflow/overflow-of-video-outline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.png
index cfab8461..ec294b55 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.txt
index 4afee15..5f6dcd6 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-layout-direction-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-layout-direction-expected.png
index efe724818..e3be6c2 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-layout-direction-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-layout-direction-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.png
index f516aa485..d5f2e89b 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.txt
index b918b54..9152fb7 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,220) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.png
index dd1ef6a..539cbac1 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.txt
index 2ef96fe..6c6cb89 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 66x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (114,48) size 110x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 66x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (146,48) size 78x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (242,212) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,452) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.png
index 2dfdfb8d..870b219 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.txt
index b0a7e915..31bff01 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,220) size 0x48 transparent
@@ -68,19 +65,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (556,220) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.png
index 3ff9433..73a3fa9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.txt
index f7fa29b7..8c3c8a6 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls/lazy-loaded-style-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls/lazy-loaded-style-expected.txt
index 45b6a1a..6943382 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls/lazy-loaded-style-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls/lazy-loaded-style-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x300
 layer at (8,8) size 400x300
   LayoutFlexibleBox {DIV} at (0,0) size 400x300
-layer at (151,89) size 115x115
-  LayoutButton (positioned) {INPUT} at (142.50,80.50) size 115x115
-    LayoutBlockFlow (anonymous) at (20,20) size 75x75
-      LayoutBlockFlow {DIV} at (0,0) size 75x75 [bgcolor=#FFFFFFE6]
 layer at (8,236) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,228) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (304,0) size 48x48
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,236) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
index 0a14ccf..5acc521 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
index b5f82f0..4451555 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-custom-bg-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
index 0a14ccf..b34f451 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.txt
index b5f82f0..4451555 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/controls/paint-controls-webkit-appearance-none-expected.txt
@@ -13,19 +13,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 400x327.27
 layer at (8,8) size 400x327
   LayoutFlexibleBox {DIV} at (0,0) size 400x327.27
-layer at (147,99) size 122x122
-  LayoutButton (positioned) {INPUT} at (139.13,90.75) size 121.75x121.75
-    LayoutBlockFlow (anonymous) at (20,20) size 81.75x81.75
-      LayoutBlockFlow {DIV} at (0,0) size 81.75x81.75 [bgcolor=#FFFFFFE6]
 layer at (8,263) size 400x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,255.27) size 400x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 220x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 188x0
     LayoutButton {INPUT} at (352,0) size 48x48
 layer at (312,263) size 48x48 transparent
   LayoutButton {INPUT} at (304,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.png
index 81bcf379..7f91881a 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.txt
index 6e9089f..cbe76da8 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-clone-expected.txt
@@ -42,13 +42,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,104) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#C4C4C4]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,129) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,129) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#C4C4C4]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,129) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,129) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,129) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,129) size 0x4
@@ -82,13 +81,12 @@
   LayoutFlexibleBox {DIV} at (0,0) size 300x54 [bgcolor=#F1F3F4]
 layer at (308,259) size 300x54 scrollHeight 55
   LayoutFlexibleBox {DIV} at (0,0) size 300x54
-    LayoutSlider {INPUT} at (10,-1) size 248x56 [color=#C4C4C4]
-      LayoutFlexibleBox {DIV} at (16,26) size 216x4
-    LayoutButton {INPUT} at (258,11) size 32x32
-layer at (334,284) size 216x4
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 216x4 [bgcolor=#00000033]
-layer at (334,284) size 216x4
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 216x4
+    LayoutSlider {INPUT} at (10,-1) size 280x56 [color=#C4C4C4]
+      LayoutFlexibleBox {DIV} at (16,26) size 248x4
+layer at (334,284) size 248x4
+  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 248x4 [bgcolor=#00000033]
+layer at (334,284) size 248x4
+  LayoutBlockFlow (positioned) {DIV} at (0,0) size 248x4
 layer at (334,284) size 0x4
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x4 [bgcolor=#000000DE]
 layer at (334,284) size 0x4
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.png
index 47562f8..1596127 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.txt
index b414d9d..e62d1399 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/media-controls-grey-scrubber-expected.txt
@@ -13,16 +13,13 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (8,8) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (114,27) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-      LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (8,86) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 160x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 128x0
 layer at (212,86) size 48x48 transparent
   LayoutButton {INPUT} at (204,0) size 48x48 [color=#808080]
 layer at (260,86) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.png
index c7df7308..9cd5b09 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.txt
index 8c638bf..e5811c9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,212) size 0x48 transparent
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,452) size 0x48 transparent
@@ -118,19 +112,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,524) size 320x240 backgroundClip at (8,524) size 320x76 clip at (8,524) size 320x76
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,582) size 100x100 backgroundClip at (118,582) size 100x18 clip at (118,582) size 100x18
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,692) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,692) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.png
index 5d0e38e..fbb8785 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.txt
index 2a7de13..dffd5692 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,28) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,86) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,196) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 140x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 108x0
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
 layer at (232,196) size 0x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.png
index dc20421..c5e9eec 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.txt
index c426dc3..93785723 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-empty-source-expected.txt
@@ -17,17 +17,14 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 300x150
 layer at (9,45) size 300x150
   LayoutFlexibleBox {DIV} at (0,0) size 300x150
-layer at (115,64) size 88x88
-  LayoutButton (positioned) {INPUT} at (106,19) size 88x88
-    LayoutBlockFlow (anonymous) at (20,20) size 48x48
-layer at (135,84) size 48x48 transparent
-  LayoutBlockFlow {DIV} at (0,0) size 48x48 [bgcolor=#FFFFFFE6]
 layer at (9,123) size 300x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,78) size 300x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (44,48) size 112x0
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (76,48) size 80x0
+layer at (9,123) size 48x48 transparent
+  LayoutButton {INPUT} at (0,0) size 48x48 [color=#808080]
 layer at (165,123) size 48x48 transparent
   LayoutButton {INPUT} at (156,0) size 48x48 [color=#808080]
 layer at (213,123) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.png
index 5fc61f3..64235ca 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.txt
index ac6e20a..89f06c9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-no-audio-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 352x288
 layer at (8,44) size 352x288
   LayoutFlexibleBox {DIV} at (0,0) size 352x288
-layer at (128,120) size 112x112
-  LayoutButton (positioned) {INPUT} at (120,76) size 112x112
-    LayoutBlockFlow (anonymous) at (20,20) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
 layer at (8,260) size 352x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,216) size 352x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 172x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 140x0
     LayoutButton {INPUT} at (304,0) size 48x48
 layer at (264,260) size 48x48 transparent
   LayoutButton {INPUT} at (256,0) size 48x48 [color=#808080]
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
index 291b643..f1043561d0 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.txt
index 98970df..dae47559 100644
--- a/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/media/video-zoom-controls-expected.txt
@@ -15,33 +15,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (57,85) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 258
+layer at (57,157) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-layer at (171,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#C4C4C4]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (225,157) size 0x72 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#C4C4C4]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (171,190) size 18x6
+layer at (225,190) size 18x6
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (171,190) size 18x6 scrollWidth 27
+layer at (225,190) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (171,190) size 0x6
+layer at (225,190) size 0x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (171,190) size 27x6 backgroundClip at (171,190) size 18x6 clip at (171,190) size 18x6
+layer at (225,190) size 27x6 backgroundClip at (225,190) size 18x6 clip at (225,190) size 18x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (57,229) size 240x36
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#C4C4C4]
@@ -65,33 +54,22 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
 layer at (43,291) size 268x218 clip at (43,291) size 240x180
   LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 258
+layer at (49,361) size 249x113 clip at (49,361) size 240x72
   LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-layer at (162,381) size 12x71 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#C4C4C4]
+    LayoutButton {INPUT} at (0,0) size 72x72
+    LayoutBlockFlow {DIV} at (72,72) size 96x0
+    LayoutButton {INPUT} at (168,0) size 72x72
+layer at (215,391) size 12x71 transparent
+  LayoutSlider {INPUT} at (168,0) size 0x72 [color=#C4C4C4]
     LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (167,414) size 19x9
+layer at (221,423) size 18x9
   LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
     LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (167,414) size 19x9 clip at (167,414) size 18x6 scrollWidth 27
+layer at (221,423) size 18x9 clip at (221,423) size 18x6 scrollWidth 27
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (167,414) size 2x6
+layer at (221,423) size 1x6
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (167,414) size 28x10 backgroundClip at (167,414) size 19x9 clip at (167,414) size 19x9
+layer at (221,423) size 27x11 backgroundClip at (221,423) size 18x9 clip at (221,423) size 18x9
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
 layer at (43,432) size 243x77
   LayoutSlider {INPUT} at (0,144) size 240x36 [color=#C4C4C4]
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.png
index 09e9939b..54b8c22 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.txt
index 54d48a6b..aa176f35 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-after-reload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.png
index af5565f..c18b578 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.txt
index 1bf1fb5..2e6dd43 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-strict-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.png
index a800a55..bd7c6c4 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.txt
index 76ff76fe..eda6d2c1 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (18,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (128,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (18,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 66x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 66x16
-        text run at (0,14) width 66: "/ 0:06"
-    LayoutBlockFlow {DIV} at (114,48) size 62x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 66x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 66x16
+        text run at (0,16) width 66: "/ 0:06"
+    LayoutBlockFlow {DIV} at (146,48) size 30x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.png
index 23d7e83..79086fa 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
index 45bacc33..5db283d 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-styling-strict-expected.txt
@@ -21,19 +21,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -69,19 +66,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (332,52) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (442,110) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (332,220) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.png
index 276c9d6..b99686f5 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.txt
index a4e2426..97e58fb 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/controls-without-preload-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.png
index a967d269..2008981 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
index 4ce55d8..13e2174f13 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-controls-rendering-expected.txt
@@ -22,19 +22,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,44) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,102) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,212) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -70,19 +67,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,284) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,342) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,452) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
@@ -120,19 +114,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,524) size 320x240 backgroundClip at (8,524) size 320x76 clip at (8,524) size 320x76
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,582) size 100x100 backgroundClip at (118,582) size 100x18 clip at (118,582) size 100x18
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,692) size 320x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.png
index af8c0cf..e000e22 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.txt
index 1c3d168..63bc16b 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-display-toggle-expected.txt
@@ -16,19 +16,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 320x240
 layer at (8,28) size 320x240
   LayoutFlexibleBox {DIV} at (0,0) size 320x240
-layer at (118,86) size 100x100
-  LayoutButton (positioned) {INPUT} at (110,58) size 100x100
-    LayoutBlockFlow (anonymous) at (20,20) size 60x60
-      LayoutBlockFlow {DIV} at (0,0) size 60x60 [bgcolor=#FFFFFFE6]
 layer at (8,196) size 320x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,168) size 320x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:06"
-    LayoutBlockFlow {DIV} at (84,48) size 92x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:06"
+    LayoutBlockFlow {DIV} at (116,48) size 60x0
     LayoutButton {INPUT} at (176,0) size 48x48
     LayoutButton {INPUT} at (224,0) size 48x48
     LayoutButton {INPUT} at (272,0) size 48x48
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.png
index 568b112..ffbabb6 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.txt
index 99dfb36..a6a3eae4 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-no-audio-expected.txt
@@ -17,19 +17,16 @@
   LayoutBlockFlow (positioned) {DIV} at (0,0) size 352x288
 layer at (8,44) size 352x288
   LayoutFlexibleBox {DIV} at (0,0) size 352x288
-layer at (128,120) size 112x112
-  LayoutButton (positioned) {INPUT} at (120,76) size 112x112
-    LayoutBlockFlow (anonymous) at (20,20) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
 layer at (8,260) size 352x48
   LayoutFlexibleBox (relative positioned) {DIV} at (0,216) size 352x48
-    LayoutBlockFlow {DIV} at (16,4) size 28x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 28x16
-        text run at (0,14) width 28: "0:00"
-    LayoutBlockFlow {DIV} at (48,4) size 36x44 [color=#FFFFFF]
-      LayoutText {#text} at (0,14) size 36x16
-        text run at (0,14) width 36: "/ 0:09"
-    LayoutBlockFlow {DIV} at (84,48) size 124x0
+    LayoutButton {INPUT} at (0,0) size 48x48
+    LayoutBlockFlow {DIV} at (48,0) size 28x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 28x16
+        text run at (0,16) width 28: "0:00"
+    LayoutBlockFlow {DIV} at (80,0) size 36x48 [color=#FFFFFF]
+      LayoutText {#text} at (0,16) size 36x16
+        text run at (0,16) width 36: "/ 0:09"
+    LayoutBlockFlow {DIV} at (116,48) size 92x0
     LayoutButton {INPUT} at (256,0) size 48x48
     LayoutButton {INPUT} at (304,0) size 48x48
 layer at (216,260) size 48x48 transparent
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.png
index a92b7e0..b6d0785 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
deleted file mode 100644
index dd0be87..0000000
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/video-surface-layer/media/video-zoom-controls-expected.txt
+++ /dev/null
@@ -1,110 +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 (12,12) size 776x543
-      LayoutBlockFlow {P} at (0,0) size 776x28
-        LayoutText {#text} at (0,0) size 275x27
-          text run at (0,0) width 275: "Zoomed video with controls."
-layer at (57,85) size 240x180
-  LayoutVideo {VIDEO} at (45,73) size 240x180
-layer at (57,85) size 240x180
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x180
-    LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (57,85) size 240x180
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
-layer at (57,85) size 240x180
-  LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (111,91) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (57,157) size 240x72 scrollWidth 330
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-    LayoutButton {INPUT} at (258,0) size 72x72
-layer at (171,157) size 0x72 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#C4C4C4]
-    LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (171,190) size 18x6
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
-    LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (171,190) size 18x6 scrollWidth 27
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (171,190) size 0x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (171,190) size 27x6 backgroundClip at (171,190) size 18x6 clip at (171,190) size 18x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
-layer at (57,229) size 240x36
-  LayoutSlider {INPUT} at (0,144) size 240x36 [color=#C4C4C4]
-    LayoutFlexibleBox {DIV} at (24,0) size 192x6
-layer at (81,229) size 192x6
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 192x6 [bgcolor=#FFFFFF4D]
-layer at (81,223) size 18x18
-  LayoutBlockFlow (relative positioned) {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (81,229) size 192x6 scrollWidth 288
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 192x6
-layer at (81,229) size 0x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (81,229) size 288x6 backgroundClip at (81,229) size 192x6 clip at (81,229) size 192x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 288x6 [bgcolor=#FFFFFF8A]
-layer at (57,310) size 240x180
-  LayoutVideo {VIDEO} at (45,298) size 240x180
-layer at (43,291) size 268x218
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x180
-    LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (43,291) size 268x218
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 240x180
-layer at (43,291) size 268x218 clip at (43,291) size 240x180
-  LayoutFlexibleBox {DIV} at (0,0) size 240x180
-layer at (104,306) size 153x153 clip at (104,306) size 132x132
-  LayoutButton (positioned) {INPUT} at (54,6) size 132x132
-    LayoutBlockFlow (anonymous) at (30,30) size 72x72
-      LayoutBlockFlow {DIV} at (0,0) size 72x72 [bgcolor=#FFFFFFE6]
-layer at (49,361) size 249x113 clip at (49,361) size 240x72 scrollWidth 330
-  LayoutFlexibleBox (relative positioned) {DIV} at (0,72) size 240x72
-    LayoutBlockFlow {DIV} at (24,6) size 42x66 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x24
-        text run at (0,21) width 42: "0:00"
-    LayoutBlockFlow {DIV} at (72,-60) size 42x132 [color=#FFFFFF]
-      LayoutText {#text} at (0,21) size 42x90
-        text run at (0,21) width 6: "/"
-        text run at (0,87) width 42: "0:06"
-    LayoutBlockFlow {DIV} at (114,72) size 0x0
-    LayoutButton {INPUT} at (114,0) size 72x72
-    LayoutButton {INPUT} at (186,0) size 72x72
-    LayoutButton {INPUT} at (258,0) size 72x72
-layer at (162,381) size 12x71 transparent
-  LayoutSlider {INPUT} at (114,0) size 0x72 [color=#C4C4C4]
-    LayoutFlexibleBox {DIV} at (0,33) size 0x6
-layer at (167,414) size 19x9
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 18x6 [bgcolor=#FFFFFF4D]
-    LayoutBlockFlow {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (167,414) size 19x9 clip at (167,414) size 18x6 scrollWidth 27
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 18x6
-layer at (167,414) size 2x6
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (167,414) size 28x10 backgroundClip at (167,414) size 19x9 clip at (167,414) size 19x9
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 27x6 [bgcolor=#FFFFFF8A]
-layer at (43,432) size 243x77
-  LayoutSlider {INPUT} at (0,144) size 240x36 [color=#C4C4C4]
-    LayoutFlexibleBox {DIV} at (24,0) size 192x6
-layer at (72,437) size 190x39
-  LayoutBlockFlow (relative positioned) {DIV} at (0,0) size 192x6 [bgcolor=#FFFFFF4D]
-layer at (71,431) size 21x20
-  LayoutBlockFlow (relative positioned) {DIV} at (0,-6) size 18x18 [bgcolor=#FFFFFF]
-layer at (72,437) size 190x39 clip at (72,437) size 190x6 scrollWidth 288
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 192x6
-layer at (72,437) size 1x5
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 0x6 [bgcolor=#FFFFFF]
-layer at (72,437) size 285x55 backgroundClip at (72,437) size 190x39 clip at (72,437) size 190x39
-  LayoutBlockFlow (positioned) {DIV} at (0,0) size 288x6 [bgcolor=#FFFFFF8A]
diff --git a/third_party/WebKit/LayoutTests/virtual/autoupgrade-all-mixed-content/http/tests/mixed-autoupgrade/all/README.txt b/third_party/WebKit/LayoutTests/virtual/autoupgrade-all-mixed-content/http/tests/mixed-autoupgrade/all/README.txt
new file mode 100644
index 0000000..a170d76
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/autoupgrade-all-mixed-content/http/tests/mixed-autoupgrade/all/README.txt
@@ -0,0 +1 @@
+Tests that depend on the AutoupgradeMixedContent feature with the "mode" parameter set to "all".
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/virtual/autoupgrade-blockable-mixed-content/http/tests/mixed-autoupgrade/blockable/README.txt b/third_party/WebKit/LayoutTests/virtual/autoupgrade-blockable-mixed-content/http/tests/mixed-autoupgrade/blockable/README.txt
new file mode 100644
index 0000000..4930315f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/autoupgrade-blockable-mixed-content/http/tests/mixed-autoupgrade/blockable/README.txt
@@ -0,0 +1 @@
+Tests that depend on the AutoupgradeMixedContent feature with the "mode" parameter set to "blockable".
diff --git a/third_party/WebKit/LayoutTests/virtual/autoupgrade-optionally-blockable-mixed-content/http/tests/mixed-autoupgrade/optionally/README.txt b/third_party/WebKit/LayoutTests/virtual/autoupgrade-optionally-blockable-mixed-content/http/tests/mixed-autoupgrade/optionally/README.txt
new file mode 100644
index 0000000..aeed7f2d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/autoupgrade-optionally-blockable-mixed-content/http/tests/mixed-autoupgrade/optionally/README.txt
@@ -0,0 +1 @@
+Tests that depend on the AutoupgradeMixedContent feature with the "mode" parameter set to "optionally-blockable".
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 3d6721a..68e82b2 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -88,7 +88,7 @@
   using WebWidget::HandleInputEvent;
   using WebWidget::DispatchBufferedTouchEvents;
   using WebWidget::SetCursorVisibilityState;
-  using WebWidget::ApplyViewportDeltas;
+  using WebWidget::ApplyViewportChanges;
   using WebWidget::MouseCaptureLost;
   using WebWidget::SetFocus;
   using WebWidget::SelectionBounds;
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index 900a584..916fd45d 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -50,6 +50,10 @@
 
 class SkBitmap;
 
+namespace cc {
+struct ApplyViewportChangesArgs;
+}
+
 namespace blink {
 
 class WebCoalescedInputEvent;
@@ -164,11 +168,7 @@
 
   // Applies viewport related properties during a commit from the compositor
   // thread.
-  virtual void ApplyViewportDeltas(const WebFloatSize& visual_viewport_delta,
-                                   const WebFloatSize& layout_viewport_delta,
-                                   const WebFloatSize& elastic_overscroll_delta,
-                                   float scale_factor,
-                                   float browser_controls_shown_ratio_delta) {}
+  virtual void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) {}
 
   virtual void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                                  bool has_scrolled_by_touch) {}
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index bb3dfcc..566b70c 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -2043,8 +2043,8 @@
     const ScrollSnapAlign& align,
     const ComputedStyle& style) {
   return CSSValuePair::Create(
-      CSSIdentifierValue::Create(align.alignment_inline),
       CSSIdentifierValue::Create(align.alignment_block),
+      CSSIdentifierValue::Create(align.alignment_inline),
       CSSValuePair::kDropIdenticalValues);
 }
 
diff --git a/third_party/blink/renderer/core/css/properties/longhands/scroll_snap_align_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/scroll_snap_align_custom.cc
index 44cf3a57..8890163 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/scroll_snap_align_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/scroll_snap_align_custom.cc
@@ -16,20 +16,18 @@
     CSSParserTokenRange& range,
     const CSSParserContext& context,
     const CSSParserLocalContext&) const {
-  CSSValueID x_id = range.Peek().Id();
-  if (x_id != CSSValueNone && x_id != CSSValueStart && x_id != CSSValueEnd &&
-      x_id != CSSValueCenter)
+  CSSValue* block_value = CSSPropertyParserHelpers::ConsumeIdent<
+      CSSValueNone, CSSValueStart, CSSValueEnd, CSSValueCenter>(range);
+  if (!block_value)
     return nullptr;
-  CSSValue* x_value = CSSPropertyParserHelpers::ConsumeIdent(range);
   if (range.AtEnd())
-    return x_value;
+    return block_value;
 
-  CSSValueID y_id = range.Peek().Id();
-  if (y_id != CSSValueNone && y_id != CSSValueStart && y_id != CSSValueEnd &&
-      y_id != CSSValueCenter)
-    return x_value;
-  CSSValue* y_value = CSSPropertyParserHelpers::ConsumeIdent(range);
-  CSSValuePair* pair = CSSValuePair::Create(x_value, y_value,
+  CSSValue* inline_value = CSSPropertyParserHelpers::ConsumeIdent<
+      CSSValueNone, CSSValueStart, CSSValueEnd, CSSValueCenter>(range);
+  if (!inline_value)
+    return block_value;
+  CSSValuePair* pair = CSSValuePair::Create(block_value, inline_value,
                                             CSSValuePair::kDropIdenticalValues);
   return pair;
 }
diff --git a/third_party/blink/renderer/core/events/web_input_event_conversion_test.cc b/third_party/blink/renderer/core/events/web_input_event_conversion_test.cc
index 7e641e35..abdd0ce 100644
--- a/third_party/blink/renderer/core/events/web_input_event_conversion_test.cc
+++ b/third_party/blink/renderer/core/events/web_input_event_conversion_test.cc
@@ -740,9 +740,9 @@
 
   LocalFrameView* view = ToLocalFrame(web_view->GetPage()->MainFrame())->View();
 
-  FloatSize elastic_overscroll(10, -20);
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(),
-                                elastic_overscroll, 1.0f, 0.0f);
+  gfx::Vector2dF elastic_overscroll(10, -20);
+  web_view->ApplyViewportChanges(
+      {gfx::Vector2dF(), elastic_overscroll, 1.0f, 0.0f});
 
   // Just elastic overscroll.
   {
@@ -757,11 +757,10 @@
     IntPoint position =
         FlooredIntPoint(transformed_mouse_event.PositionInRootFrame());
 
-    EXPECT_EQ(web_mouse_event.PositionInWidget().x + elastic_overscroll.Width(),
+    EXPECT_EQ(web_mouse_event.PositionInWidget().x + elastic_overscroll.x(),
               position.X());
-    EXPECT_EQ(
-        web_mouse_event.PositionInWidget().y + elastic_overscroll.Height(),
-        position.Y());
+    EXPECT_EQ(web_mouse_event.PositionInWidget().y + elastic_overscroll.y(),
+              position.Y());
     EXPECT_EQ(web_mouse_event.PositionInScreen().x,
               transformed_mouse_event.PositionInScreen().x);
     EXPECT_EQ(web_mouse_event.PositionInScreen().y,
@@ -788,10 +787,10 @@
         FlooredIntPoint(transformed_mouse_event.PositionInRootFrame());
 
     EXPECT_EQ(web_mouse_event.PositionInWidget().x / page_scale +
-                  visual_offset.X() + elastic_overscroll.Width(),
+                  visual_offset.X() + elastic_overscroll.x(),
               position.X());
     EXPECT_EQ(web_mouse_event.PositionInWidget().y / page_scale +
-                  visual_offset.Y() + elastic_overscroll.Height(),
+                  visual_offset.Y() + elastic_overscroll.y(),
               position.Y());
     EXPECT_EQ(web_mouse_event.PositionInScreen().x,
               transformed_mouse_event.PositionInScreen().x);
@@ -814,9 +813,9 @@
   web_view->Resize(WebSize(page_width, page_height));
   web_view->UpdateAllLifecyclePhases();
 
-  FloatSize elastic_overscroll(10, -20);
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(),
-                                elastic_overscroll, 1.0f, 0.0f);
+  gfx::Vector2dF elastic_overscroll(10, -20);
+  web_view->ApplyViewportChanges(
+      {gfx::Vector2dF(), elastic_overscroll, 1.0f, 0.0f});
   FrameTestHelpers::ReloadFrame(web_view_helper.GetWebView()->MainFrameImpl());
   LocalFrameView* view = ToLocalFrame(web_view->GetPage()->MainFrame())->View();
 
@@ -833,11 +832,10 @@
     IntPoint position =
         FlooredIntPoint(transformed_mouse_event.PositionInRootFrame());
 
-    EXPECT_EQ(web_mouse_event.PositionInWidget().x + elastic_overscroll.Width(),
+    EXPECT_EQ(web_mouse_event.PositionInWidget().x + elastic_overscroll.x(),
               position.X());
-    EXPECT_EQ(
-        web_mouse_event.PositionInWidget().y + elastic_overscroll.Height(),
-        position.Y());
+    EXPECT_EQ(web_mouse_event.PositionInWidget().y + elastic_overscroll.y(),
+              position.Y());
     EXPECT_EQ(web_mouse_event.PositionInScreen().x,
               transformed_mouse_event.PositionInScreen().x);
     EXPECT_EQ(web_mouse_event.PositionInScreen().y,
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
index e9408193..09032f01 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
@@ -449,8 +449,6 @@
     web_frame_->Client()->DidStartProvisionalLoad(
         WebDocumentLoaderImpl::FromDocumentLoader(loader), wrapped_request);
   }
-  if (WebDevToolsAgentImpl* dev_tools = DevToolsAgent())
-    dev_tools->DidStartProvisionalLoad(web_frame_->GetFrame());
   virtual_time_pauser_.PauseVirtualTime();
 }
 
@@ -496,8 +494,6 @@
     const ResourceError& error,
     WebHistoryCommitType commit_type) {
   web_frame_->DidFail(error, true, commit_type);
-  if (WebDevToolsAgentImpl* dev_tools = DevToolsAgent())
-    dev_tools->DidFailProvisionalLoad(web_frame_->GetFrame());
   virtual_time_pauser_.UnpauseVirtualTime();
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
index 04eb8da..4509fbe 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
@@ -199,7 +199,7 @@
   bool should_reattach = !reattach_session_state.is_null();
 
   InspectorSession* inspector_session = new InspectorSession(
-      session_client, probe_sink_.Get(), 0,
+      session_client, probe_sink_.Get(), inspected_frames, 0,
       main_thread_debugger->GetV8Inspector(),
       main_thread_debugger->ContextGroupId(inspected_frames->Root()),
       std::move(reattach_session_state));
@@ -412,26 +412,6 @@
   resource_content_loader_->DidCommitLoadForLocalFrame(frame);
   for (auto& session : sessions_)
     session->DidCommitLoadForLocalFrame(frame);
-  if (inspected_frames_->Root() == frame) {
-    for (auto& session : sessions_)
-      session->V8Session()->setSkipAllPauses(false);
-  }
-}
-
-void WebDevToolsAgentImpl::DidFailProvisionalLoad(LocalFrame* frame) {
-  if (inspected_frames_->Root() == frame) {
-    for (auto& session : sessions_)
-      session->V8Session()->setSkipAllPauses(false);
-  }
-}
-
-void WebDevToolsAgentImpl::DidStartProvisionalLoad(LocalFrame* frame) {
-  if (inspected_frames_->Root() == frame) {
-    for (auto& session : sessions_) {
-      session->V8Session()->setSkipAllPauses(true);
-      session->V8Session()->resume();
-    }
-  }
 }
 
 bool WebDevToolsAgentImpl::ScreencastEnabled() {
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
index 4262f26..f9a84dd 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
@@ -83,8 +83,6 @@
 
   // Instrumentation from web/ layer.
   void DidCommitLoadForLocalFrame(LocalFrame*);
-  void DidFailProvisionalLoad(LocalFrame*);
-  void DidStartProvisionalLoad(LocalFrame*);
   bool ScreencastEnabled();
   String NavigationInitiatorInfo(LocalFrame*);
   String EvaluateInOverlayForTesting(const String& script);
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index c178723..f968f22 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -3433,8 +3433,8 @@
   float scale_delta =
       web_view_impl->FakePageScaleAnimationPageScaleForTesting() /
       web_view_impl->PageScaleFactor();
-  web_view_impl->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(),
-                                     WebFloatSize(), scale_delta, 0);
+  web_view_impl->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), scale_delta, 0});
   scale = web_view_impl->PageScaleFactor();
 }
 
@@ -3623,8 +3623,8 @@
   // back to the div.
   SimulateDoubleTap(web_view_helper.GetWebView(), top_point, scale);
   EXPECT_FLOAT_EQ(1, scale);
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 0.6f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 0.6f, 0});
   SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
   EXPECT_FLOAT_EQ(1, scale);
   SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
@@ -3633,8 +3633,8 @@
 
   // If we didn't yet get an auto-zoom update and a second double-tap arrives,
   // should go back to minimum scale.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   web_view_helper.GetWebView()->AnimateDoubleTapZoom(top_point);
   EXPECT_TRUE(
       web_view_helper.GetWebView()->FakeDoubleTapAnimationPendingForTesting());
@@ -3684,8 +3684,8 @@
   EXPECT_FLOAT_EQ(1, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // 1 < minimumPageScale < doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.1f, 4);
   web_view_helper.GetWebView()->UpdateAllLifecyclePhases();
@@ -3705,8 +3705,8 @@
   EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
   web_view_helper.GetWebView()->UpdateAllLifecyclePhases();
@@ -3774,8 +3774,8 @@
   EXPECT_FLOAT_EQ(legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // 1 < maximumLegibleScaleFactor < minimumPageScale <
   //     doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.0f, 4);
@@ -3796,8 +3796,8 @@
   EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // minimumPageScale < 1 < maximumLegibleScaleFactor <
   //     doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
@@ -3818,8 +3818,8 @@
   EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
   //     maximumLegibleScaleFactor
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.9f, 4);
@@ -3891,8 +3891,8 @@
   EXPECT_FLOAT_EQ(legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // 1 < accessibilityFontScaleFactor < minimumPageScale <
   //     doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.0f, 4);
@@ -3913,8 +3913,8 @@
   EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // minimumPageScale < 1 < accessibilityFontScaleFactor <
   //     doubleTapZoomAlreadyLegibleScale
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
@@ -3935,8 +3935,8 @@
   EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
 
   // Zoom in to reset double_tap_zoom_in_effect flag.
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.1f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.1f, 0});
   // minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
   //     accessibilityFontScaleFactor
   web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.9f, 4);
@@ -7246,8 +7246,8 @@
 
   // Do a compositor scroll, verify that this is counted as a user scroll.
   scrollable_area->DidScroll(FloatPoint(0, 1));
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.7f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.7f, 0});
   EXPECT_TRUE(client.WasFrameScrolled());
   EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
 
@@ -7256,8 +7256,8 @@
 
   // The page scale 1.0f and scroll.
   scrollable_area->DidScroll(FloatPoint(0, 2));
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.0f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.0f, 0});
   EXPECT_TRUE(client.WasFrameScrolled());
   EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
   client.Reset();
@@ -7265,16 +7265,16 @@
 
   // No scroll event if there is no scroll delta.
   scrollable_area->DidScroll(FloatPoint(0, 2));
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 1.0f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1.0f, 0});
   EXPECT_FALSE(client.WasFrameScrolled());
   EXPECT_FALSE(initial_scroll_state.was_scrolled_by_user);
   client.Reset();
 
   // Non zero page scale and scroll.
   scrollable_area->DidScroll(FloatPoint(9, 15));
-  web_view_helper.GetWebView()->ApplyViewportDeltas(
-      WebFloatSize(), WebFloatSize(), WebFloatSize(), 0.6f, 0);
+  web_view_helper.GetWebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 0.6f, 0});
   EXPECT_TRUE(client.WasFrameScrolled());
   EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
   client.Reset();
@@ -8088,44 +8088,44 @@
 
   // Simulate the browser controls showing by 20px, thus shrinking the viewport
   // and allowing it to scroll an additional 20px.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, 20.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  20.0f / browser_controls_height});
   EXPECT_EQ(ScrollOffset(0, 1920),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 
   // Show more, make sure the scroll actually gets clamped.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, 20.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  20.0f / browser_controls_height});
   web_view->MainFrameImpl()->SetScrollOffset(WebSize(0, 2000));
   EXPECT_EQ(ScrollOffset(0, 1940),
             frame_view->LayoutViewport()->GetScrollOffset());
 
   // Hide until there's 10px showing.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, -30.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  -30.0f / browser_controls_height});
   EXPECT_EQ(ScrollOffset(0, 1910),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 
   // Simulate a LayoutEmbeddedContent::resize. The frame is resized to
   // accomodate the browser controls and Blink's view of the browser controls
   // matches that of the CC
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, 30.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  30.0f / browser_controls_height});
   web_view->ResizeWithBrowserControls(WebSize(100, 60), 40.0f, 0, true);
   web_view->UpdateAllLifecyclePhases();
   EXPECT_EQ(ScrollOffset(0, 1940),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 
   // Now simulate hiding.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, -10.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  -10.0f / browser_controls_height});
   EXPECT_EQ(ScrollOffset(0, 1930),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 
   // Reset to original state: 100px widget height, browser controls fully
   // hidden.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, -30.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  -30.0f / browser_controls_height});
   web_view->ResizeWithBrowserControls(WebSize(100, 100),
                                       browser_controls_height, 0, false);
   web_view->UpdateAllLifecyclePhases();
@@ -8136,13 +8136,13 @@
   // should allow an extra 0.5px of scrolling in the visual viewport. Make
   // sure we're not losing any pixels when applying the adjustment on the
   // main frame.
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, 1.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  1.0f / browser_controls_height});
   EXPECT_EQ(ScrollOffset(0, 1901),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 
-  web_view->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                1.0f, 2.0f / browser_controls_height);
+  web_view->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1.0f,
+                                  2.0f / browser_controls_height});
   EXPECT_EQ(ScrollOffset(0, 1903),
             frame_view->LayoutViewport()->MaximumScrollOffset());
 }
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 865f79d..81b10a7 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -3286,14 +3286,7 @@
   scoped_defer_commits_ = layer_tree_view_->DeferCommits();
 }
 
-void WebViewImpl::ApplyViewportDeltas(
-    const WebFloatSize& visual_viewport_delta,
-    // TODO(bokan): This parameter is to be removed but requires adjusting many
-    // callsites.
-    const WebFloatSize&,
-    const WebFloatSize& elastic_overscroll_delta,
-    float page_scale_delta,
-    float browser_controls_shown_ratio_delta) {
+void WebViewImpl::ApplyViewportChanges(const ApplyViewportChangesArgs& args) {
   VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
 
   // Store the desired offsets the visual viewport before setting the top
@@ -3301,21 +3294,21 @@
   // viewports to keep the offsets valid. The compositor may have already
   // done that so we don't want to double apply the deltas here.
   FloatPoint visual_viewport_offset = visual_viewport.VisibleRect().Location();
-  visual_viewport_offset.Move(visual_viewport_delta.width,
-                              visual_viewport_delta.height);
+  visual_viewport_offset.Move(args.inner_delta.x(), args.inner_delta.y());
 
   GetBrowserControls().SetShownRatio(GetBrowserControls().ShownRatio() +
-                                     browser_controls_shown_ratio_delta);
+                                     args.browser_controls_delta);
 
-  SetPageScaleFactorAndLocation(PageScaleFactor() * page_scale_delta,
+  SetPageScaleFactorAndLocation(PageScaleFactor() * args.page_scale_delta,
                                 visual_viewport_offset);
 
-  if (page_scale_delta != 1) {
+  if (args.page_scale_delta != 1) {
     double_tap_zoom_pending_ = false;
     visual_viewport.UserDidChangeScale();
   }
 
-  elastic_overscroll_ += elastic_overscroll_delta;
+  elastic_overscroll_ += FloatSize(args.elastic_overscroll_delta.x(),
+                                   args.elastic_overscroll_delta.y());
 }
 
 void WebViewImpl::RecordWheelAndTouchScrollingCount(
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index db5e6b3..fb8a36b 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -58,6 +58,7 @@
 #include "third_party/blink/renderer/core/page/scoped_page_pauser.h"
 #include "third_party/blink/renderer/platform/geometry/int_point.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
+#include "third_party/blink/renderer/platform/graphics/apply_viewport_changes.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
@@ -129,11 +130,7 @@
   WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override;
   WebInputEventResult DispatchBufferedTouchEvents() override;
   void SetCursorVisibilityState(bool is_visible) override;
-  void ApplyViewportDeltas(const WebFloatSize& visual_viewport_delta,
-                           const WebFloatSize& layout_viewport_delta,
-                           const WebFloatSize& elastic_overscroll_delta,
-                           float page_scale_delta,
-                           float browser_controls_shown_ratio_delta) override;
+  void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override;
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override;
   void MouseCaptureLost() override;
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 39f9cb41..f72f6fec 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -253,10 +253,6 @@
   ScrollableArea::Trace(visitor);
 }
 
-void VisualViewport::SetNeedsPaintPropertiesUpdate() {
-  needs_paint_property_update_ = true;
-}
-
 void VisualViewport::UpdateStyleAndLayoutIgnorePendingStylesheets() const {
   if (!MainFrame())
     return;
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.h b/third_party/blink/renderer/core/frame/visual_viewport.h
index 071c04b..4fe803da 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.h
+++ b/third_party/blink/renderer/core/frame/visual_viewport.h
@@ -269,7 +269,8 @@
 
   CompositorElementId GetCompositorOverscrollElasticityElementId() const;
 
-  void SetNeedsPaintPropertiesUpdate();
+  void SetNeedsPaintPropertyUpdate() { needs_paint_property_update_ = true; }
+  bool NeedsPaintPropertyUpdate() const { return needs_paint_property_update_; }
 
  private:
   explicit VisualViewport(Page&);
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 51b9a7b..93621f8 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/platform/geometry/double_rect.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
+#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
@@ -311,6 +312,20 @@
   EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100),
                        visual_viewport.VisibleRect().Size());
 
+  // Verify the paint property nodes and GeometryMapper cache.
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
+      RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
+    WebView()->UpdateAllLifecyclePhases();
+    EXPECT_EQ(TransformationMatrix().Scale(2),
+              visual_viewport.GetPageScaleNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Translate(0, -300),
+              visual_viewport.GetScrollTranslationNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Scale(2).Translate(0, -300),
+              GeometryMapper::SourceToDestinationProjection(
+                  visual_viewport.GetScrollTranslationNode(),
+                  &TransformPaintPropertyNode::Root()));
+  }
+
   // Perform the resizing
   WebView()->Resize(IntSize(200, 100));
 
@@ -320,6 +335,20 @@
   EXPECT_EQ(ScrollOffset(0, 625),
             GetFrame()->View()->LayoutViewport()->GetScrollOffset());
   EXPECT_FLOAT_SIZE_EQ(FloatSize(0, 75), visual_viewport.GetScrollOffset());
+
+  // Verify the paint property nodes and GeometryMapper cache.
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
+      RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
+    WebView()->UpdateAllLifecyclePhases();
+    EXPECT_EQ(TransformationMatrix().Scale(4),
+              visual_viewport.GetPageScaleNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Translate(0, -75),
+              visual_viewport.GetScrollTranslationNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Scale(4).Translate(0, -75),
+              GeometryMapper::SourceToDestinationProjection(
+                  visual_viewport.GetScrollTranslationNode(),
+                  &TransformPaintPropertyNode::Root()));
+  }
 }
 
 // Test that the VisualViewport works as expected in case if a scaled
@@ -371,6 +400,20 @@
   EXPECT_FLOAT_SIZE_EQ(FloatSize(50, 100),
                        visual_viewport.VisibleRect().Size());
 
+  // Verify the paint property nodes and GeometryMapper cache.
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
+      RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
+    WebView()->UpdateAllLifecyclePhases();
+    EXPECT_EQ(TransformationMatrix().Scale(2),
+              visual_viewport.GetPageScaleNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Translate(-150, 0),
+              visual_viewport.GetScrollTranslationNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Scale(2).Translate(-150, 0),
+              GeometryMapper::SourceToDestinationProjection(
+                  visual_viewport.GetScrollTranslationNode(),
+                  &TransformPaintPropertyNode::Root()));
+  }
+
   WebView()->Resize(IntSize(200, 100));
 
   // After resizing the scale changes 2.0 -> 4.0
@@ -379,6 +422,20 @@
   EXPECT_EQ(ScrollOffset(0, 0),
             GetFrame()->View()->LayoutViewport()->GetScrollOffset());
   EXPECT_FLOAT_SIZE_EQ(FloatSize(150, 0), visual_viewport.GetScrollOffset());
+
+  // Verify the paint property nodes and GeometryMapper cache.
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
+      RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
+    WebView()->UpdateAllLifecyclePhases();
+    EXPECT_EQ(TransformationMatrix().Scale(4),
+              visual_viewport.GetPageScaleNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Translate(-150, 0),
+              visual_viewport.GetScrollTranslationNode()->Matrix());
+    EXPECT_EQ(TransformationMatrix().Scale(4).Translate(-150, 0),
+              GeometryMapper::SourceToDestinationProjection(
+                  visual_viewport.GetScrollTranslationNode(),
+                  &TransformPaintPropertyNode::Root()));
+  }
 }
 
 // Test that the container layer gets sized properly if the WebView is resized
@@ -1214,8 +1271,7 @@
   EXPECT_EQ(IntSize(1000, 900), frame_view.FrameRect().Size());
 
   // Simulate bringing down the browser controls by 20px.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, 1);
+  WebView()->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1, 1});
   EXPECT_EQ(FloatSize(500, 430), visual_viewport.VisibleRect().Size());
 
   // Test that the scroll bounds are adjusted appropriately: the visual viewport
@@ -1232,8 +1288,8 @@
             frame_view.LayoutViewport()->GetScrollOffset());
 
   // Simulate bringing up the browser controls by 10.5px.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, -10.5f / 20);
+  WebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1, -10.5f / 20});
   EXPECT_FLOAT_SIZE_EQ(FloatSize(500, 440.5f),
                        visual_viewport.VisibleRect().Size());
 
@@ -1267,8 +1323,7 @@
   // Simulate bringing down the browser controls by 20px. Since we're zoomed in,
   // the browser controls take up half as much space (in document-space) than
   // they do at an unzoomed level.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, 1);
+  WebView()->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1, 1});
   EXPECT_EQ(FloatSize(250, 215), visual_viewport.VisibleRect().Size());
 
   // Test that the scroll bounds are adjusted appropriately.
@@ -1284,8 +1339,8 @@
 
   // Scale back out, LocalFrameView max scroll shouldn't have changed. Visual
   // viewport should be moved up to accomodate larger view.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 0.5f, 0);
+  WebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 0.5f, 0});
   EXPECT_EQ(1, visual_viewport.Scale());
   EXPECT_EQ(expected, frame_view.LayoutViewport()->GetScrollOffset());
   frame_view.LayoutViewport()->ScrollBy(ScrollOffset(10000, 10000),
@@ -1297,13 +1352,13 @@
   EXPECT_EQ(FloatSize(500, 860 - 430), visual_viewport.GetScrollOffset());
 
   // Scale out, use a scale that causes fractional rects.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 0.8f, -1);
+  WebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 0.8f, -1});
   EXPECT_EQ(FloatSize(625, 562.5), visual_viewport.VisibleRect().Size());
 
   // Bring out the browser controls by 11
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, 11 / 20.f);
+  WebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(), gfx::Vector2dF(), 1, 11 / 20.f});
   EXPECT_EQ(FloatSize(625, 548.75), visual_viewport.VisibleRect().Size());
 
   // Ensure max scroll offsets are updated properly.
@@ -1469,8 +1524,7 @@
 TEST_P(VisualViewportTest, TestTopControlHidingResizeDoesntClampMainFrame) {
   InitializeWithAndroidSettings();
   WebView()->ResizeWithBrowserControls(WebView()->Size(), 500, 0, false);
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, 1);
+  WebView()->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1, 1});
   WebView()->ResizeWithBrowserControls(WebSize(1000, 1000), 500, 0, true);
 
   RegisterMockedHttpURLLoad("content-width-1000.html");
@@ -1480,8 +1534,7 @@
   // Scroll the LocalFrameView to the bottom of the page but "hide" the browser
   // controls on the compositor side so the max scroll position should account
   // for the full viewport height.
-  WebView()->ApplyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
-                                 1, -1);
+  WebView()->ApplyViewportChanges({gfx::Vector2dF(), gfx::Vector2dF(), 1, -1});
   LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView();
   frame_view.LayoutViewport()->SetScrollOffset(ScrollOffset(0, 10000),
                                                kProgrammaticScroll);
@@ -1755,8 +1808,8 @@
   VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport();
 
   // Apply some scroll and scale from the impl-side.
-  WebView()->ApplyViewportDeltas(WebFloatSize(300, 200), WebFloatSize(0, 0),
-                                 WebFloatSize(0, 0), 2, 0);
+  WebView()->ApplyViewportChanges(
+      {gfx::Vector2dF(300, 200), gfx::Vector2dF(0, 0), 2, 0});
 
   EXPECT_EQ(FloatSize(300, 200), visual_viewport.GetScrollOffset());
 
@@ -2386,8 +2439,8 @@
               visual_viewport.GetScrollNode()->ContentsSize());
   }
 
-  WebView().ApplyViewportDeltas(WebFloatSize(1, 1), WebFloatSize(),
-                                WebFloatSize(), 2, 1);
+  WebView().ApplyViewportChanges(
+      {gfx::Vector2dF(1, 1), gfx::Vector2dF(), 2, 1});
   EXPECT_EQ(gfx::Size(400, 600), visual_viewport.ContainerLayer()->Size());
   EXPECT_EQ(gfx::Size(400, 600),
             visual_viewport.ContainerLayer()->CcLayer()->bounds());
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 3c2210de..c37c6a6b 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -599,13 +599,7 @@
   return mutator_dispatcher_;
 }
 
-void WebFrameWidgetImpl::ApplyViewportDeltas(
-    const WebFloatSize& visual_viewport_delta,
-    const WebFloatSize& main_frame_delta,
-    const WebFloatSize& elastic_overscroll_delta,
-    float page_scale_delta,
-    float browser_controls_delta) {
-  // FIXME: To be implemented.
+void WebFrameWidgetImpl::ApplyViewportChanges(const ApplyViewportChangesArgs&) {
 }
 
 void WebFrameWidgetImpl::MouseCaptureLost() {
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index ab70553..e1979d2 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -43,6 +43,7 @@
 #include "third_party/blink/renderer/core/frame/web_frame_widget_base.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/page/page_widget_delegate.h"
+#include "third_party/blink/renderer/platform/graphics/apply_viewport_changes.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
@@ -94,11 +95,7 @@
   WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override;
   void SetCursorVisibilityState(bool is_visible) override;
 
-  void ApplyViewportDeltas(const WebFloatSize& visual_viewport_delta,
-                           const WebFloatSize& main_frame_delta,
-                           const WebFloatSize& elastic_overscroll_delta,
-                           float page_scale_delta,
-                           float browser_controls_delta) override;
+  void ApplyViewportChanges(const ApplyViewportChangesArgs&) override;
   void MouseCaptureLost() override;
   void SetFocus(bool enable) override;
   SkColor BackgroundColor() const override;
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 0d106f0a..c56d7946 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -102,15 +102,9 @@
   web_view_->SetCursorVisibilityState(is_visible);
 }
 
-void WebViewFrameWidget::ApplyViewportDeltas(
-    const WebFloatSize& visual_viewport_delta,
-    const WebFloatSize& layout_viewport_delta,
-    const WebFloatSize& elastic_overscroll_delta,
-    float scale_factor,
-    float browser_controls_shown_ratio_delta) {
-  web_view_->ApplyViewportDeltas(visual_viewport_delta, layout_viewport_delta,
-                                 elastic_overscroll_delta, scale_factor,
-                                 browser_controls_shown_ratio_delta);
+void WebViewFrameWidget::ApplyViewportChanges(
+    const ApplyViewportChangesArgs& args) {
+  web_view_->ApplyViewportChanges(args);
 }
 
 void WebViewFrameWidget::RecordWheelAndTouchScrollingCount(
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index 0ed4e90..044004cd 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/frame/web_frame_widget_base.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+#include "third_party/blink/renderer/platform/graphics/apply_viewport_changes.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
 
@@ -58,11 +59,7 @@
   WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override;
   WebInputEventResult DispatchBufferedTouchEvents() override;
   void SetCursorVisibilityState(bool is_visible) override;
-  void ApplyViewportDeltas(const WebFloatSize& visual_viewport_delta,
-                           const WebFloatSize& layout_viewport_delta,
-                           const WebFloatSize& elastic_overscroll_delta,
-                           float scale_factor,
-                           float browser_controls_shown_ratio_delta) override;
+  void ApplyViewportChanges(const ApplyViewportChangesArgs&) override;
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override;
   void MouseCaptureLost() override;
diff --git a/third_party/blink/renderer/core/fullscreen/fullscreen.cc b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
index 1c58d04..791a834 100644
--- a/third_party/blink/renderer/core/fullscreen/fullscreen.cc
+++ b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
@@ -116,7 +116,7 @@
     // Update paint properties on the visual viewport since
     // user-input-scrollable bits will change based on fullscreen state.
     if (Page* page = frame->GetPage())
-      page->GetVisualViewport().SetNeedsPaintPropertiesUpdate();
+      page->GetVisualViewport().SetNeedsPaintPropertyUpdate();
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
index e6d9575..5289eb4f 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
+++ b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
@@ -587,7 +587,6 @@
     }
   }
   style->SetWidth(Length(ceilf(width), kFixed));
-  style->SetUnique();
   return style;
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/html_option_element.cc b/third_party/blink/renderer/core/html/forms/html_option_element.cc
index d1c3e09..4f88654 100644
--- a/third_party/blink/renderer/core/html/forms/html_option_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_option_element.cc
@@ -268,12 +268,12 @@
 }
 
 void HTMLOptionElement::ChildrenChanged(const ChildrenChange& change) {
+  HTMLElement::ChildrenChanged(change);
   if (HTMLDataListElement* data_list = OwnerDataListElement())
     data_list->OptionElementChildrenChanged();
   else if (HTMLSelectElement* select = OwnerSelectElement())
     select->OptionElementChildrenChanged(*this);
   UpdateLabel();
-  HTMLElement::ChildrenChanged(change);
 }
 
 HTMLDataListElement* HTMLOptionElement::OwnerDataListElement() const {
diff --git a/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc b/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc
index fa68adc9..30b5ac5f 100644
--- a/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc
+++ b/third_party/blink/renderer/core/html/forms/multiple_fields_temporal_input_type_view.cc
@@ -351,7 +351,6 @@
   scoped_refptr<ComputedStyle> style = ComputedStyle::Clone(*original_style);
   style->SetDirection(content_direction);
   style->SetDisplay(new_display);
-  style->SetUnique();
   return style;
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
index 0bbcdc9..c07c17f 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
@@ -88,7 +88,6 @@
   // We don't want the shadow dom to be editable, so we set this block to
   // read-only in case the input itself is editable.
   style->SetUserModify(EUserModify::kReadOnly);
-  style->SetUnique();
 
   return style;
 }
@@ -169,7 +168,6 @@
           ? EUserModify::kReadOnly
           : EUserModify::kReadWritePlaintextOnly);
   text_block_style->SetDisplay(EDisplay::kBlock);
-  text_block_style->SetUnique();
 
   if (!IsHTMLTextAreaElement(host)) {
     text_block_style->SetWhiteSpace(EWhiteSpace::kPre);
diff --git a/third_party/blink/renderer/core/inspector/browser_protocol.pdl b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
index 74401a0..b63737e1 100644
--- a/third_party/blink/renderer/core/inspector/browser_protocol.pdl
+++ b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
@@ -1800,7 +1800,10 @@
     parameters
       Page.FrameId frameId
     returns
-      NodeId nodeId
+      # Resulting node.
+      BackendNodeId backendNodeId
+      # Id of the node at given coordinates, only when enabled.
+      optional NodeId nodeId
 
   # Fired when `Element`'s attribute is modified.
   event attributeModified
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index d2c508e3..2fe328d 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -2231,8 +2231,10 @@
   return Response::OK();
 }
 
-protocol::Response InspectorDOMAgent::getFrameOwner(const String& frame_id,
-                                                    int* node_id) {
+protocol::Response InspectorDOMAgent::getFrameOwner(
+    const String& frame_id,
+    int* backend_node_id,
+    protocol::Maybe<int>* node_id) {
   Frame* frame = inspected_frames_->Root();
   for (; frame; frame = frame->Tree().TraverseNext(inspected_frames_->Root())) {
     if (IdentifiersFactory::FrameId(frame) == frame_id)
@@ -2243,8 +2245,14 @@
   HTMLFrameOwnerElement* frame_owner = ToHTMLFrameOwnerElement(frame->Owner());
   if (!frame_owner)
     return Response::Error("No iframe owner for given node");
-  *node_id =
-      PushNodePathToFrontend(frame_owner, document_node_to_id_map_.Get());
+
+  *backend_node_id = DOMNodeIds::IdForNode(frame_owner);
+  if (enabled_.Get()) {
+    Response response = PushDocumentUponHandlelessOperation();
+    if (!response.isSuccess())
+      return response;
+    *node_id = PushNodePathToFrontend(frame_owner);
+  }
   return Response::OK();
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
index f9da144..7042f4ec 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
@@ -213,7 +213,8 @@
       std::unique_ptr<protocol::DOM::Node>*) override;
 
   protocol::Response getFrameOwner(const String& frame_id,
-                                   int* node_id) override;
+                                   int* backend_node_id,
+                                   protocol::Maybe<int>* node_id) override;
 
   bool Enabled() const;
   void ReleaseDanglingNodes();
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index 9ce99e5..94fbd74 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -581,7 +581,7 @@
     protocol = response.GetResourceLoadInfo()->npn_negotiated_protocol;
   if (protocol.IsEmpty() || protocol == "unknown") {
     if (response.WasFetchedViaSPDY()) {
-      protocol = "spdy";
+      protocol = "h2";
     } else if (response.IsHTTP()) {
       protocol = "http";
       if (response.HttpVersion() ==
diff --git a/third_party/blink/renderer/core/inspector/inspector_session.cc b/third_party/blink/renderer/core/inspector/inspector_session.cc
index 0b997958..b13a610 100644
--- a/third_party/blink/renderer/core/inspector/inspector_session.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_session.cc
@@ -7,6 +7,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/use_counter.h"
+#include "third_party/blink/renderer/core/inspector/inspected_frames.h"
 #include "third_party/blink/renderer/core/inspector/inspector_base_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_session_state.h"
 #include "third_party/blink/renderer/core/inspector/protocol/Protocol.h"
@@ -35,6 +36,7 @@
 InspectorSession::InspectorSession(
     Client* client,
     CoreProbeSink* instrumenting_agents,
+    InspectedFrames* inspected_frames,
     int session_id,
     v8_inspector::V8Inspector* inspector,
     int context_group_id,
@@ -44,6 +46,7 @@
       session_id_(session_id),
       disposed_(false),
       instrumenting_agents_(instrumenting_agents),
+      inspected_frames_(inspected_frames),
       inspector_backend_dispatcher_(new protocol::UberDispatcher(this)),
       session_state_(std::move(reattach_session_state)),
       v8_session_state_(kV8StateKey),
@@ -56,6 +59,8 @@
   v8_session_ =
       inspector->connect(context_group_id, /*channel*/ this,
                          ToV8InspectorStringView(v8_session_state_json_.Get()));
+
+  instrumenting_agents_->addInspectorSession(this);
 }
 
 InspectorSession::~InspectorSession() {
@@ -77,6 +82,7 @@
 void InspectorSession::Dispose() {
   DCHECK(!disposed_);
   disposed_ = true;
+  instrumenting_agents_->removeInspectorSession(this);
   inspector_backend_dispatcher_.reset();
   for (wtf_size_t i = agents_.size(); i > 0; i--)
     agents_[i - 1]->Dispose();
@@ -116,9 +122,23 @@
   }
 }
 
+void InspectorSession::DidStartProvisionalLoad(LocalFrame* frame) {
+  if (inspected_frames_->Root() == frame) {
+    v8_session_->setSkipAllPauses(true);
+    v8_session_->resume();
+  }
+}
+
+void InspectorSession::DidFailProvisionalLoad(LocalFrame* frame) {
+  if (inspected_frames_->Root() == frame)
+    v8_session_->setSkipAllPauses(false);
+}
+
 void InspectorSession::DidCommitLoadForLocalFrame(LocalFrame* frame) {
   for (wtf_size_t i = 0; i < agents_.size(); i++)
     agents_[i]->DidCommitLoadForLocalFrame(frame);
+  if (inspected_frames_->Root() == frame)
+    v8_session_->setSkipAllPauses(false);
 }
 
 void InspectorSession::sendProtocolResponse(
@@ -227,6 +247,7 @@
 
 void InspectorSession::Trace(blink::Visitor* visitor) {
   visitor->Trace(instrumenting_agents_);
+  visitor->Trace(inspected_frames_);
   visitor->Trace(agents_);
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_session.h b/third_party/blink/renderer/core/inspector/inspector_session.h
index 8474140..fc3e45dce 100644
--- a/third_party/blink/renderer/core/inspector/inspector_session.h
+++ b/third_party/blink/renderer/core/inspector/inspector_session.h
@@ -19,6 +19,7 @@
 namespace blink {
 
 class InspectorAgent;
+class InspectedFrames;
 class CoreProbeSink;
 class LocalFrame;
 
@@ -44,6 +45,7 @@
   InspectorSession(
       Client*,
       CoreProbeSink*,
+      InspectedFrames*,
       int session_id,
       v8_inspector::V8Inspector*,
       int context_group_id,
@@ -57,6 +59,8 @@
   void Append(InspectorAgent*);
   void Restore();
   void Dispose();
+  void DidStartProvisionalLoad(LocalFrame*);
+  void DidFailProvisionalLoad(LocalFrame*);
   void DidCommitLoadForLocalFrame(LocalFrame*);
   void DispatchProtocolMessage(int call_id,
                                const String& method,
@@ -93,6 +97,7 @@
   int session_id_;
   bool disposed_;
   Member<CoreProbeSink> instrumenting_agents_;
+  Member<InspectedFrames> inspected_frames_;
   std::unique_ptr<protocol::UberDispatcher> inspector_backend_dispatcher_;
   InspectorSessionState session_state_;
   HeapVector<Member<InspectorAgent>> agents_;
diff --git a/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc b/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
index a447ee2..83e69621 100644
--- a/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
+++ b/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
@@ -83,13 +83,13 @@
   if (sessions_.find(session_id) != sessions_.end())
     return;
 
+  InspectedFrames* inspected_frames = new InspectedFrames(nullptr);
   InspectorSession* session = new InspectorSession(
-      this, probe_sink_.Get(), session_id, debugger_->GetV8Inspector(),
-      debugger_->ContextGroupId(thread_), nullptr);
+      this, probe_sink_.Get(), inspected_frames, session_id,
+      debugger_->GetV8Inspector(), debugger_->ContextGroupId(thread_), nullptr);
   session->Append(new InspectorLogAgent(thread_->GetConsoleMessageStorage(),
                                         nullptr, session->V8Session()));
   if (thread_->GlobalScope()->IsWorkerGlobalScope()) {
-    InspectedFrames* inspected_frames = new InspectedFrames(nullptr);
     WorkerGlobalScope* worker_global_scope =
         ToWorkerGlobalScope(thread_->GlobalScope());
     DCHECK(worker_global_scope->EnsureFetcher());
diff --git a/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc b/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
index 1c8a55b..da61f2c 100644
--- a/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
@@ -359,15 +359,10 @@
 }
 
 FlexLayoutAlgorithm::FlexLayoutAlgorithm(const ComputedStyle* style,
-                                         LayoutUnit line_break_length,
-                                         FlexItemVector& all_items)
+                                         LayoutUnit line_break_length)
     : style_(style),
       line_break_length_(line_break_length),
-      all_items_(all_items),
-      next_item_index_(0) {
-  for (FlexItem& item : all_items_)
-    item.algorithm = this;
-}
+      next_item_index_(0) {}
 
 FlexLine* FlexLayoutAlgorithm::ComputeNextFlexLine(
     LayoutUnit container_logical_width) {
diff --git a/third_party/blink/renderer/core/layout/flexible_box_algorithm.h b/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
index d6a6b40..bae8b0f 100644
--- a/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
+++ b/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
@@ -243,12 +243,11 @@
 //   https://drafts.csswg.org/css-flexbox/
 //
 // Expected usage is as follows:
-//    FlexItemVector flex_items;
-//    for (each child) {
-//       flex_items.emplace_back(...caller must compute these values...)
+//     FlexLayoutAlgorithm algorithm(Style(), MainAxisLength(), flex_items);
+//     for (each child) {
+//       algorithm.emplace_back(...caller must compute these values...)
 //     }
 //     LayoutUnit cross_axis_offset = border + padding;
-//     FlexLayoutAlgorithm algorithm(Style(), MainAxisLength(), flex_items);
 //     while ((FlexLine* line = algorithm.ComputenextLine(LogicalWidth()))) {
 //       // Compute main axis size, using sum_hypothetical_main_size if
 //       // indefinite
@@ -264,9 +263,14 @@
 //     // The final position of each flex item is in item.desired_location
 class FlexLayoutAlgorithm {
  public:
-  FlexLayoutAlgorithm(const ComputedStyle*,
-                      LayoutUnit line_break_length,
-                      FlexItemVector& all_items);
+  FlexLayoutAlgorithm(const ComputedStyle*, LayoutUnit line_break_length);
+
+  template <typename... Args>
+  FlexItem& emplace_back(Args&&... args) {
+    FlexItem& item = all_items_.emplace_back(std::forward<Args>(args)...);
+    item.algorithm = this;
+    return item;
+  }
 
   const ComputedStyle* Style() const { return style_; }
   const ComputedStyle& StyleRef() const { return *style_; }
@@ -305,7 +309,7 @@
 
   const ComputedStyle* style_;
   const LayoutUnit line_break_length_;
-  FlexItemVector& all_items_;
+  FlexItemVector all_items_;
   Vector<FlexLine> flex_lines_;
   size_t next_item_index_;
   DISALLOW_COPY_AND_ASSIGN(FlexLayoutAlgorithm);
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index b37b3b1..8625714 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -486,20 +486,48 @@
   ClearNeedsLayout();
 }
 
-void LayoutBlock::AddOverflowFromChildren() {
+void LayoutBlock::AddVisualOverflowFromChildren() {
   if (ChildrenInline())
-    ToLayoutBlockFlow(this)->AddOverflowFromInlineChildren();
+    ToLayoutBlockFlow(this)->AddVisualOverflowFromInlineChildren();
   else
-    AddOverflowFromBlockChildren();
+    AddVisualOverflowFromBlockChildren();
+}
+
+void LayoutBlock::AddLayoutOverflowFromChildren() {
+  if (ChildrenInline())
+    ToLayoutBlockFlow(this)->AddLayoutOverflowFromInlineChildren();
+  else
+    AddLayoutOverflowFromBlockChildren();
+}
+
+void LayoutBlock::ComputeOverflow(LayoutUnit old_client_after_edge,
+                                  bool recompute_floats) {
+  LayoutRect previous_visual_overflow_rect = VisualOverflowRect();
+  overflow_.reset();
+  ComputeLayoutOverflow(old_client_after_edge, recompute_floats);
+  ComputeVisualOverflow(previous_visual_overflow_rect, recompute_floats);
+}
+
+void LayoutBlock::ComputeVisualOverflow(
+    const LayoutRect& previous_visual_overflow_rect,
+    bool) {
+  AddVisualOverflowFromChildren();
+
+  AddVisualEffectOverflow();
+  AddVisualOverflowFromTheme();
+
+  if (VisualOverflowRect() != previous_visual_overflow_rect) {
+    if (Layer())
+      Layer()->SetNeedsCompositingInputsUpdate();
+    GetFrameView()->SetIntersectionObservationState(LocalFrameView::kDesired);
+  }
 }
 
 DISABLE_CFI_PERF
-void LayoutBlock::ComputeOverflow(LayoutUnit old_client_after_edge, bool) {
-  LayoutRect previous_visual_overflow_rect = VisualOverflowRect();
-  overflow_.reset();
-
-  AddOverflowFromChildren();
-  AddOverflowFromPositionedObjects();
+void LayoutBlock::ComputeLayoutOverflow(LayoutUnit old_client_after_edge,
+                                        bool) {
+  AddLayoutOverflowFromChildren();
+  AddLayoutOverflowFromPositionedObjects();
 
   if (HasOverflowClip()) {
     // When we have overflow clip, propagate the original spillout since it will
@@ -521,18 +549,9 @@
     if (HasOverflowModel())
       overflow_->SetLayoutClientAfterEdge(old_client_after_edge);
   }
-
-  AddVisualEffectOverflow();
-  AddVisualOverflowFromTheme();
-
-  if (VisualOverflowRect() != previous_visual_overflow_rect) {
-    if (Layer())
-      Layer()->SetNeedsCompositingInputsUpdate();
-    GetFrameView()->SetIntersectionObservationState(LocalFrameView::kDesired);
-  }
 }
 
-void LayoutBlock::AddOverflowFromBlockChildren() {
+void LayoutBlock::AddVisualOverflowFromBlockChildren() {
   for (LayoutBox* child = FirstChildBox(); child;
        child = child->NextSiblingBox()) {
     if (child->IsFloatingOrOutOfFlowPositioned() || child->IsColumnSpanAll())
@@ -545,23 +564,45 @@
     // the outline which may enclose continuations.
     if (child->IsLayoutBlockFlow() &&
         ToLayoutBlockFlow(child)->ContainsInlineWithOutlineAndContinuation())
-      ToLayoutBlockFlow(child)->AddOverflowFromInlineChildren();
+      ToLayoutBlockFlow(child)->AddVisualOverflowFromInlineChildren();
 
-    AddOverflowFromChild(*child);
+    AddVisualOverflowFromChild(*child);
   }
 }
 
-void LayoutBlock::AddOverflowFromPositionedObjects() {
+void LayoutBlock::AddLayoutOverflowFromBlockChildren() {
+  for (LayoutBox* child = FirstChildBox(); child;
+       child = child->NextSiblingBox()) {
+    if (child->IsFloatingOrOutOfFlowPositioned() || child->IsColumnSpanAll())
+      continue;
+
+    // If the child contains inline with outline and continuation, its
+    // visual overflow computed during its layout might be inaccurate because
+    // the layout of continuations might not be up-to-date at that time.
+    // Re-add overflow from inline children to ensure its overflow covers
+    // the outline which may enclose continuations.
+    if (child->IsLayoutBlockFlow() &&
+        ToLayoutBlockFlow(child)->ContainsInlineWithOutlineAndContinuation())
+      ToLayoutBlockFlow(child)->AddLayoutOverflowFromInlineChildren();
+
+    AddLayoutOverflowFromChild(*child);
+  }
+}
+
+void LayoutBlock::AddLayoutOverflowFromPositionedObjects() {
   TrackedLayoutBoxListHashSet* positioned_descendants = PositionedObjects();
   if (!positioned_descendants)
     return;
 
   for (auto* positioned_object : *positioned_descendants) {
-    // Fixed positioned elements don't contribute to layout overflow, since they
-    // don't scroll with the content.
-    if (positioned_object->StyleRef().GetPosition() != EPosition::kFixed)
-      AddOverflowFromChild(*positioned_object,
-                           ToLayoutSize(positioned_object->Location()));
+    // Fixed positioned elements whose containing block is the LayoutView
+    // don't contribute to layout overflow, since they don't scroll with the
+    // content.
+    if (!IsLayoutView() ||
+        positioned_object->StyleRef().GetPosition() != EPosition::kFixed) {
+      AddLayoutOverflowFromChild(*positioned_object,
+                                 ToLayoutSize(positioned_object->Location()));
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_block.h b/third_party/blink/renderer/core/layout/layout_block.h
index 847919f..fb9ba1b 100644
--- a/third_party/blink/renderer/core/layout/layout_block.h
+++ b/third_party/blink/renderer/core/layout/layout_block.h
@@ -395,6 +395,9 @@
                              const LayoutPoint& paint_offset) const;
   void UpdateAfterLayout() override;
 
+  void ComputeOverflow(LayoutUnit old_client_after_edge,
+                       bool recompute_floats = false);
+
  protected:
   virtual void AdjustInlineDirectionLineBounds(
       unsigned /* expansionOpportunityCount */,
@@ -444,15 +447,21 @@
   bool SimplifiedLayout();
   virtual void SimplifiedNormalFlowLayout();
 
- public:
-  virtual void ComputeOverflow(LayoutUnit old_client_after_edge,
-                               bool recompute_floats = false);
+ private:
+  void AddVisualOverflowFromBlockChildren();
+  void AddVisualOverflowFromTheme();
+  void AddLayoutOverflowFromPositionedObjects();
+  void AddLayoutOverflowFromBlockChildren();
 
  protected:
-  virtual void AddOverflowFromChildren();
-  void AddOverflowFromPositionedObjects();
-  void AddOverflowFromBlockChildren();
-  void AddVisualOverflowFromTheme();
+  virtual void ComputeVisualOverflow(
+      const LayoutRect& previous_visual_overflow_rect,
+      bool recompute_floats);
+  virtual void ComputeLayoutOverflow(LayoutUnit old_client_after_edge,
+                                     bool recompute_floats);
+
+  virtual void AddLayoutOverflowFromChildren();
+  virtual void AddVisualOverflowFromChildren();
 
   void AddOutlineRects(Vector<LayoutRect>&,
                        const LayoutPoint& additional_offset,
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.cc b/third_party/blink/renderer/core/layout/layout_block_flow.cc
index 3eada8a..8759488 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.cc
@@ -2509,20 +2509,31 @@
                     : EBreakBetween::kAuto;
 }
 
-void LayoutBlockFlow::AddOverflowFromFloats() {
+void LayoutBlockFlow::AddVisualOverflowFromFloats() {
   if (!floating_objects_)
     return;
 
-  const FloatingObjectSet& floating_object_set = floating_objects_->Set();
-  FloatingObjectSetIterator end = floating_object_set.end();
-  for (FloatingObjectSetIterator it = floating_object_set.begin(); it != end;
-       ++it) {
-    const FloatingObject& floating_object = *it->get();
-    if (floating_object.IsDescendant())
-      AddOverflowFromChild(
-          *floating_object.GetLayoutObject(),
-          LayoutSize(XPositionForFloatIncludingMargin(floating_object),
-                     YPositionForFloatIncludingMargin(floating_object)));
+  for (auto& floating_object : floating_objects_->Set()) {
+    if (floating_object->IsDescendant()) {
+      AddVisualOverflowFromChild(
+          *floating_object->GetLayoutObject(),
+          LayoutSize(XPositionForFloatIncludingMargin(*floating_object),
+                     YPositionForFloatIncludingMargin(*floating_object)));
+    }
+  }
+}
+
+void LayoutBlockFlow::AddLayoutOverflowFromFloats() {
+  if (!floating_objects_)
+    return;
+
+  for (auto& floating_object : floating_objects_->Set()) {
+    if (floating_object->IsDescendant()) {
+      AddLayoutOverflowFromChild(
+          *floating_object->GetLayoutObject(),
+          LayoutSize(XPositionForFloatIncludingMargin(*floating_object),
+                     YPositionForFloatIncludingMargin(*floating_object)));
+    }
   }
 }
 
@@ -2552,12 +2563,24 @@
     scoped_refptr<const NGPhysicalFragment>,
     NGPhysicalOffset) {}
 
-void LayoutBlockFlow::ComputeOverflow(LayoutUnit old_client_after_edge,
-                                      bool recompute_floats) {
-  LayoutBlock::ComputeOverflow(old_client_after_edge, recompute_floats);
+void LayoutBlockFlow::ComputeVisualOverflow(
+    const LayoutRect& previous_visual_overflow_rect,
+    bool recompute_floats) {
+  LayoutBlock::ComputeVisualOverflow(previous_visual_overflow_rect,
+                                     recompute_floats);
   if (recompute_floats || CreatesNewFormattingContext() ||
       HasSelfPaintingLayer())
-    AddOverflowFromFloats();
+    AddVisualOverflowFromFloats();
+}
+
+void LayoutBlockFlow::ComputeLayoutOverflow(LayoutUnit old_client_after_edge,
+                                            bool recompute_floats) {
+  LayoutBlock::ComputeLayoutOverflow(old_client_after_edge, recompute_floats);
+  // TODO(chrishtr): why does it check for a self-painting layer? That should
+  // only apply to visual overflow.
+  if (recompute_floats || CreatesNewFormattingContext() ||
+      HasSelfPaintingLayer())
+    AddLayoutOverflowFromFloats();
 }
 
 void LayoutBlockFlow::ComputeSelfHitTestRects(
@@ -4134,11 +4157,18 @@
 
       // Since the float doesn't overhang, it didn't get put into our list. We
       // need to go ahead and add its overflow in to the child now.
-      if (floating_object.IsDescendant())
-        child->AddOverflowFromChild(
+      if (floating_object.IsDescendant()) {
+        // TODO(chrishtr): this looks weird, is it correct? Also, do we need
+        // both types of overflow?
+        child->AddVisualOverflowFromChild(
             *floating_object.GetLayoutObject(),
             LayoutSize(XPositionForFloatIncludingMargin(floating_object),
                        YPositionForFloatIncludingMargin(floating_object)));
+        child->AddLayoutOverflowFromChild(
+            *floating_object.GetLayoutObject(),
+            LayoutSize(XPositionForFloatIncludingMargin(floating_object),
+                       YPositionForFloatIncludingMargin(floating_object)));
+      }
     }
   }
 }
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.h b/third_party/blink/renderer/core/layout/layout_block_flow.h
index 032f6c31..833be2902 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.h
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.h
@@ -111,8 +111,9 @@
 
   void UpdateBlockLayout(bool relayout_children) override;
 
-  void ComputeOverflow(LayoutUnit old_client_after_edge,
-                       bool recompute_floats = false) override;
+  void ComputeVisualOverflow(const LayoutRect&, bool recompute_floats) override;
+  void ComputeLayoutOverflow(LayoutUnit old_client_after_edge,
+                             bool recompute_floats) override;
 
   void DeleteLineBoxTree();
 
@@ -318,7 +319,9 @@
       rare_data_->multi_column_flow_thread_ = nullptr;
   }
 
-  void AddOverflowFromInlineChildren();
+  void AddVisualOverflowFromInlineChildren();
+
+  void AddLayoutOverflowFromInlineChildren();
 
   // FIXME: This should be const to avoid a const_cast, but can modify child
   // dirty bits and LayoutTextCombine.
@@ -443,9 +446,10 @@
     is_self_collapsing_ = CheckIfIsSelfCollapsingBlock();
   }
 
-  // This function is only public so we can call it from NGBlockNode while we're
-  // still working on LayoutNG.
-  void AddOverflowFromFloats();
+  // These functions are only public so we can call it from NGBlockNode while
+  // we're still working on LayoutNG.
+  void AddVisualOverflowFromFloats();
+  void AddLayoutOverflowFromFloats();
 
   virtual NGInlineNodeData* TakeNGInlineNodeData() { return nullptr; }
   virtual NGInlineNodeData* GetNGInlineNodeData() const { return nullptr; }
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
index 0ccc2f3..a27e1170 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
@@ -2312,7 +2312,7 @@
   return !it.AtEnd();
 }
 
-void LayoutBlockFlow::AddOverflowFromInlineChildren() {
+void LayoutBlockFlow::AddVisualOverflowFromInlineChildren() {
   LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit();
   // FIXME: Need to find another way to do this, since scrollbars could show
   // when we don't want them to.
@@ -2320,7 +2320,6 @@
       IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection())
     end_padding = LayoutUnit(1);
   for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) {
-    AddLayoutOverflow(curr->PaddedLayoutOverflowRect(end_padding));
     LayoutRect visual_overflow =
         curr->VisualOverflowRect(curr->LineTop(), curr->LineBottom());
     AddContentsVisualOverflow(visual_overflow);
@@ -2351,6 +2350,17 @@
   AddContentsVisualOverflow(outline_bounds_of_all_continuations);
 }
 
+void LayoutBlockFlow::AddLayoutOverflowFromInlineChildren() {
+  LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit();
+  // FIXME: Need to find another way to do this, since scrollbars could show
+  // when we don't want them to.
+  if (HasOverflowClip() && !end_padding && GetNode() &&
+      IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection())
+    end_padding = LayoutUnit(1);
+  for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox())
+    AddLayoutOverflow(curr->PaddedLayoutOverflowRect(end_padding));
+}
+
 void LayoutBlockFlow::DeleteEllipsisLineBoxes() {
   ETextAlign text_align = StyleRef().GetTextAlign();
   IndentTextOrNot indent_text = kIndentText;
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index cdcaa07..9e26e570 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -5266,9 +5266,27 @@
   return outsets;
 }
 
+void LayoutBox::AddVisualOverflowFromChild(const LayoutBox& child,
+                                           const LayoutSize& delta) {
+  // Never allow flow threads to propagate overflow up to a parent.
+  if (child.IsLayoutFlowThread())
+    return;
+
+  // Add in visual overflow from the child.  Even if the child clips its
+  // overflow, it may still have visual overflow of its own set from box shadows
+  // or reflections. It is unnecessary to propagate this overflow if we are
+  // clipping our own overflow.
+  if (child.HasSelfPaintingLayer())
+    return;
+  LayoutRect child_visual_overflow_rect =
+      child.VisualOverflowRectForPropagation();
+  child_visual_overflow_rect.Move(delta);
+  AddContentsVisualOverflow(child_visual_overflow_rect);
+}
+
 DISABLE_CFI_PERF
-void LayoutBox::AddOverflowFromChild(const LayoutBox& child,
-                                     const LayoutSize& delta) {
+void LayoutBox::AddLayoutOverflowFromChild(const LayoutBox& child,
+                                           const LayoutSize& delta) {
   // Never allow flow threads to propagate overflow up to a parent.
   if (child.IsLayoutFlowThread())
     return;
@@ -5281,17 +5299,6 @@
       child.LayoutOverflowRectForPropagation(this);
   child_layout_overflow_rect.Move(delta);
   AddLayoutOverflow(child_layout_overflow_rect);
-
-  // Add in visual overflow from the child.  Even if the child clips its
-  // overflow, it may still have visual overflow of its own set from box shadows
-  // or reflections. It is unnecessary to propagate this overflow if we are
-  // clipping our own overflow.
-  if (child.HasSelfPaintingLayer())
-    return;
-  LayoutRect child_visual_overflow_rect =
-      child.VisualOverflowRectForPropagation();
-  child_visual_overflow_rect.Move(delta);
-  AddContentsVisualOverflow(child_visual_overflow_rect);
 }
 
 bool LayoutBox::HasTopOverflow() const {
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index 17800725d..ed52e86 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -537,10 +537,16 @@
 
   void AddVisualEffectOverflow();
   LayoutRectOutsets ComputeVisualEffectOverflowOutsets();
-  void AddOverflowFromChild(const LayoutBox& child) {
-    AddOverflowFromChild(child, child.LocationOffset());
+  void AddVisualOverflowFromChild(const LayoutBox& child) {
+    AddVisualOverflowFromChild(child, child.LocationOffset());
   }
-  void AddOverflowFromChild(const LayoutBox& child, const LayoutSize& delta);
+  void AddLayoutOverflowFromChild(const LayoutBox& child) {
+    AddLayoutOverflowFromChild(child, child.LocationOffset());
+  }
+  void AddVisualOverflowFromChild(const LayoutBox& child,
+                                  const LayoutSize& delta);
+  void AddLayoutOverflowFromChild(const LayoutBox& child,
+                                  const LayoutSize& delta);
   void ClearLayoutOverflow();
   void ClearAllOverflows() { overflow_.reset(); }
 
diff --git a/third_party/blink/renderer/core/layout/layout_flexible_box.cc b/third_party/blink/renderer/core/layout/layout_flexible_box.cc
index 0c7748b..c24084b 100644
--- a/third_party/blink/renderer/core/layout/layout_flexible_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_flexible_box.cc
@@ -818,7 +818,8 @@
   // TODO(cbiesinger): That second part is not yet true.
   ChildLayoutType layout_type =
       relayout_children ? kForceLayout : kLayoutIfNeeded;
-  FlexItemVector all_items;
+  const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
+  FlexLayoutAlgorithm flex_algorithm(Style(), line_break_length);
   order_iterator_.First();
   for (LayoutBox* child = order_iterator_.CurrentChild(); child;
        child = order_iterator_.Next()) {
@@ -828,11 +829,9 @@
       continue;
     }
 
-    all_items.push_back(ConstructFlexItem(*child, layout_type));
+    flex_algorithm.emplace_back(ConstructFlexItem(*child, layout_type));
   }
 
-  const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
-  FlexLayoutAlgorithm flex_algorithm(Style(), line_break_length, all_items);
   LayoutUnit cross_axis_offset = FlowAwareContentInsetBefore();
   LayoutUnit logical_width = LogicalWidth();
   FlexLine* current_line;
diff --git a/third_party/blink/renderer/core/layout/layout_list_item.cc b/third_party/blink/renderer/core/layout/layout_list_item.cc
index 1a1b193..f2a7ed1 100644
--- a/third_party/blink/renderer/core/layout/layout_list_item.cc
+++ b/third_party/blink/renderer/core/layout/layout_list_item.cc
@@ -289,9 +289,14 @@
   return false;
 }
 
-void LayoutListItem::AddOverflowFromChildren() {
-  LayoutBlockFlow::AddOverflowFromChildren();
-  PositionListMarker();
+void LayoutListItem::AddVisualOverflowFromChildren() {
+  LayoutBlockFlow::AddVisualOverflowFromChildren();
+  UpdateOverflow(Visual);
+}
+
+void LayoutListItem::AddLayoutOverflowFromChildren() {
+  LayoutBlockFlow::AddLayoutOverflowFromChildren();
+  UpdateOverflow(Layout);
 }
 
 // Align marker_inline_box in block direction according to line_box_root's
@@ -365,53 +370,54 @@
   }
 }
 
-void LayoutListItem::PositionListMarker() {
-  if (marker_ && marker_->Parent() && marker_->Parent()->IsBox() &&
-      !marker_->IsInside() && marker_->InlineBoxWrapper()) {
-    if (need_block_direction_align_)
-      AlignMarkerInBlockDirection();
+void LayoutListItem::UpdateOverflow(OverflowType overflow_type) {
+  if (!marker_ || !marker_->Parent() || !marker_->Parent()->IsBox() ||
+      marker_->IsInside() || !marker_->InlineBoxWrapper())
+    return;
 
-    LayoutUnit marker_old_logical_left = marker_->LogicalLeft();
-    LayoutUnit block_offset;
-    LayoutUnit line_offset;
-    for (LayoutBox* o = marker_->ParentBox(); o != this; o = o->ParentBox()) {
-      block_offset += o->LogicalTop();
-      line_offset += o->LogicalLeft();
-    }
+  if (need_block_direction_align_)
+    AlignMarkerInBlockDirection();
 
-    bool adjust_overflow = false;
-    LayoutUnit marker_logical_left;
-    InlineBox* marker_inline_box = marker_->InlineBoxWrapper();
-    RootInlineBox& root = marker_inline_box->Root();
-    bool hit_self_painting_layer = false;
+  LayoutUnit marker_old_logical_left = marker_->LogicalLeft();
+  LayoutUnit block_offset;
+  LayoutUnit line_offset;
+  for (LayoutBox* o = marker_->ParentBox(); o != this; o = o->ParentBox()) {
+    block_offset += o->LogicalTop();
+    line_offset += o->LogicalLeft();
+  }
 
-    LayoutUnit line_top = root.LineTop();
-    LayoutUnit line_bottom = root.LineBottom();
+  bool adjust_overflow = false;
+  LayoutUnit marker_logical_left;
+  InlineBox* marker_inline_box = marker_->InlineBoxWrapper();
+  RootInlineBox& root = marker_inline_box->Root();
+  bool hit_self_painting_layer = false;
 
-    // We figured out the inline position of the marker before laying out the
-    // line so that floats later in the line don't interfere with it. However
-    // if the line has shifted down then that position will be too far out.
-    // So we always take the lowest value of (1) the position of the marker
-    // if we calculate it now and (2) the inline position we calculated before
-    // laying out the line.
-    // TODO(jchaffraix): Propagating the overflow to the line boxes seems
-    // pretty wrong (https://crbug.com/554160).
-    // FIXME: Need to account for relative positioning in the layout overflow.
-    if (StyleRef().IsLeftToRightDirection()) {
-      LayoutUnit marker_line_offset =
-          std::min(marker_->LineOffset(),
-                   LogicalLeftOffsetForLine(marker_->LogicalTop(),
-                                            kDoNotIndentText, LayoutUnit()));
-      marker_logical_left = marker_line_offset - line_offset - PaddingStart() -
-                            BorderStart() + marker_->MarginStart();
-      marker_inline_box->MoveInInlineDirection(marker_logical_left -
-                                               marker_old_logical_left);
-      for (InlineFlowBox* box = marker_inline_box->Parent(); box;
-           box = box->Parent()) {
+  LayoutUnit line_top = root.LineTop();
+  LayoutUnit line_bottom = root.LineBottom();
+
+  // We figured out the inline position of the marker before laying out the
+  // line so that floats later in the line don't interfere with it. However
+  // if the line has shifted down then that position will be too far out.
+  // So we always take the lowest value of (1) the position of the marker
+  // if we calculate it now and (2) the inline position we calculated before
+  // laying out the line.
+  // TODO(jchaffraix): Propagating the overflow to the line boxes seems
+  // pretty wrong (https://crbug.com/554160).
+  // FIXME: Need to account for relative positioning in the layout overflow.
+  if (StyleRef().IsLeftToRightDirection()) {
+    LayoutUnit marker_line_offset =
+        std::min(marker_->LineOffset(),
+                 LogicalLeftOffsetForLine(marker_->LogicalTop(),
+                                          kDoNotIndentText, LayoutUnit()));
+    marker_logical_left = marker_line_offset - line_offset - PaddingStart() -
+                          BorderStart() + marker_->MarginStart();
+    marker_inline_box->MoveInInlineDirection(marker_logical_left -
+                                             marker_old_logical_left);
+    for (InlineFlowBox* box = marker_inline_box->Parent(); box;
+         box = box->Parent()) {
+      if (overflow_type == Visual) {
         LayoutRect new_logical_visual_overflow_rect =
             box->LogicalVisualOverflowRect(line_top, line_bottom);
-        LayoutRect new_logical_layout_overflow_rect =
-            box->LogicalLayoutOverflowRect(line_top, line_bottom);
         if (marker_logical_left < new_logical_visual_overflow_rect.X() &&
             !hit_self_painting_layer) {
           new_logical_visual_overflow_rect.SetWidth(
@@ -420,6 +426,14 @@
           if (box == root)
             adjust_overflow = true;
         }
+        box->OverrideVisualOverflowFromLogicalRect(
+            new_logical_visual_overflow_rect, line_top, line_bottom);
+
+        if (box->BoxModelObject().HasSelfPaintingLayer())
+          hit_self_painting_layer = true;
+      } else {
+        LayoutRect new_logical_layout_overflow_rect =
+            box->LogicalLayoutOverflowRect(line_top, line_bottom);
         if (marker_logical_left < new_logical_layout_overflow_rect.X()) {
           new_logical_layout_overflow_rect.SetWidth(
               new_logical_layout_overflow_rect.MaxX() - marker_logical_left);
@@ -427,27 +441,24 @@
           if (box == root)
             adjust_overflow = true;
         }
-        box->OverrideOverflowFromLogicalRects(new_logical_layout_overflow_rect,
-                                              new_logical_visual_overflow_rect,
-                                              line_top, line_bottom);
-        if (box->BoxModelObject().HasSelfPaintingLayer())
-          hit_self_painting_layer = true;
+        box->OverrideLayoutOverflowFromLogicalRect(
+            new_logical_layout_overflow_rect, line_top, line_bottom);
       }
-    } else {
-      LayoutUnit marker_line_offset =
-          std::max(marker_->LineOffset(),
-                   LogicalRightOffsetForLine(marker_->LogicalTop(),
-                                             kDoNotIndentText, LayoutUnit()));
-      marker_logical_left = marker_line_offset - line_offset + PaddingStart() +
-                            BorderStart() + marker_->MarginEnd();
-      marker_inline_box->MoveInInlineDirection(marker_logical_left -
-                                               marker_old_logical_left);
-      for (InlineFlowBox* box = marker_inline_box->Parent(); box;
-           box = box->Parent()) {
+    }
+  } else {
+    LayoutUnit marker_line_offset =
+        std::max(marker_->LineOffset(),
+                 LogicalRightOffsetForLine(marker_->LogicalTop(),
+                                           kDoNotIndentText, LayoutUnit()));
+    marker_logical_left = marker_line_offset - line_offset + PaddingStart() +
+                          BorderStart() + marker_->MarginEnd();
+    marker_inline_box->MoveInInlineDirection(marker_logical_left -
+                                             marker_old_logical_left);
+    for (InlineFlowBox* box = marker_inline_box->Parent(); box;
+         box = box->Parent()) {
+      if (overflow_type == Visual) {
         LayoutRect new_logical_visual_overflow_rect =
             box->LogicalVisualOverflowRect(line_top, line_bottom);
-        LayoutRect new_logical_layout_overflow_rect =
-            box->LogicalLayoutOverflowRect(line_top, line_bottom);
         if (marker_logical_left + marker_->LogicalWidth() >
                 new_logical_visual_overflow_rect.MaxX() &&
             !hit_self_painting_layer) {
@@ -457,6 +468,14 @@
           if (box == root)
             adjust_overflow = true;
         }
+        box->OverrideVisualOverflowFromLogicalRect(
+            new_logical_visual_overflow_rect, line_top, line_bottom);
+
+        if (box->BoxModelObject().HasSelfPaintingLayer())
+          hit_self_painting_layer = true;
+      } else {
+        LayoutRect new_logical_layout_overflow_rect =
+            box->LogicalLayoutOverflowRect(line_top, line_bottom);
         if (marker_logical_left + marker_->LogicalWidth() >
             new_logical_layout_overflow_rect.MaxX()) {
           new_logical_layout_overflow_rect.SetWidth(
@@ -465,45 +484,43 @@
           if (box == root)
             adjust_overflow = true;
         }
-        box->OverrideOverflowFromLogicalRects(new_logical_layout_overflow_rect,
-                                              new_logical_visual_overflow_rect,
-                                              line_top, line_bottom);
-
-        if (box->BoxModelObject().HasSelfPaintingLayer())
-          hit_self_painting_layer = true;
+        box->OverrideLayoutOverflowFromLogicalRect(
+            new_logical_layout_overflow_rect, line_top, line_bottom);
       }
     }
+  }
 
-    if (adjust_overflow) {
-      // AlignMarkerInBlockDirection and pagination_strut might move root or
-      // marker_inline_box in block direction. We should add marker_inline_box
-      // top when propagate overflow.
-      LayoutRect marker_rect(
-          LayoutPoint(marker_logical_left + line_offset,
-                      block_offset + marker_inline_box->LogicalTop()),
-          marker_->Size());
-      if (!StyleRef().IsHorizontalWritingMode())
-        marker_rect = marker_rect.TransposedRect();
-      LayoutBox* o = marker_;
-      bool propagate_visual_overflow = true;
-      bool propagate_layout_overflow = true;
+  if (adjust_overflow) {
+    // AlignMarkerInBlockDirection and pagination_strut might move root or
+    // marker_inline_box in block direction. We should add marker_inline_box
+    // top when propagate overflow.
+    LayoutRect marker_rect(
+        LayoutPoint(marker_logical_left + line_offset,
+                    block_offset + marker_inline_box->LogicalTop()),
+        marker_->Size());
+    if (!StyleRef().IsHorizontalWritingMode())
+      marker_rect = marker_rect.TransposedRect();
+    LayoutBox* o = marker_;
+
+    if (overflow_type == Visual) {
+      do {
+        o = o->ParentBox();
+        if (o->IsLayoutBlock())
+          ToLayoutBlock(o)->AddContentsVisualOverflow(marker_rect);
+        if (o->HasOverflowClip() || o->HasSelfPaintingLayer())
+          break;
+        marker_rect.MoveBy(-o->Location());
+      } while (o != this);
+    } else {
       do {
         o = o->ParentBox();
         if (o->IsLayoutBlock()) {
-          if (propagate_visual_overflow)
-            ToLayoutBlock(o)->AddContentsVisualOverflow(marker_rect);
-          if (propagate_layout_overflow)
-            ToLayoutBlock(o)->AddLayoutOverflow(marker_rect);
+          ToLayoutBlock(o)->AddLayoutOverflow(marker_rect);
         }
-        if (o->HasOverflowClip()) {
-          propagate_layout_overflow = false;
-          propagate_visual_overflow = false;
-        }
-        if (o->HasSelfPaintingLayer())
-          propagate_visual_overflow = false;
+        if (o->HasOverflowClip())
+          break;
         marker_rect.MoveBy(-o->Location());
-      } while (o != this && propagate_visual_overflow &&
-               propagate_layout_overflow);
+      } while (o != this);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/layout/layout_list_item.h b/third_party/blink/renderer/core/layout/layout_list_item.h
index a1bd9f9..e180df75 100644
--- a/third_party/blink/renderer/core/layout/layout_list_item.h
+++ b/third_party/blink/renderer/core/layout/layout_list_item.h
@@ -65,11 +65,13 @@
   // Returns true if we re-attached and updated the location of the marker.
   bool UpdateMarkerLocation();
 
-  void PositionListMarker();
+  enum OverflowType { Layout, Visual };
+  void UpdateOverflow(OverflowType);
 
   void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override;
 
-  void AddOverflowFromChildren() override;
+  void AddVisualOverflowFromChildren() override;
+  void AddLayoutOverflowFromChildren() override;
 
   void AlignMarkerInBlockDirection();
 
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_set.cc b/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
index 5d3fcefb..a7ae137 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_set.cc
@@ -520,7 +520,21 @@
   return result;
 }
 
-void LayoutMultiColumnSet::AddOverflowFromChildren() {
+void LayoutMultiColumnSet::AddVisualOverflowFromChildren() {
+  // It's useless to calculate overflow if we haven't determined the page
+  // logical height yet.
+  if (!IsPageLogicalHeightKnown())
+    return;
+  LayoutRect overflow_rect;
+  for (const auto& group : fragmentainer_groups_) {
+    LayoutRect rect = group.CalculateOverflow();
+    rect.Move(group.OffsetFromColumnSet());
+    overflow_rect.Unite(rect);
+  }
+  AddContentsVisualOverflow(overflow_rect);
+}
+
+void LayoutMultiColumnSet::AddLayoutOverflowFromChildren() {
   // It's useless to calculate overflow if we haven't determined the page
   // logical height yet.
   if (!IsPageLogicalHeightKnown())
@@ -532,7 +546,6 @@
     overflow_rect.Unite(rect);
   }
   AddLayoutOverflow(overflow_rect);
-  AddContentsVisualOverflow(overflow_rect);
 }
 
 void LayoutMultiColumnSet::InsertedIntoTree() {
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_set.h b/third_party/blink/renderer/core/layout/layout_multi_column_set.h
index 91612c05..8dbe3d27 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_set.h
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_set.h
@@ -255,7 +255,8 @@
   void PaintObject(const PaintInfo&,
                    const LayoutPoint& paint_offset) const override;
 
-  void AddOverflowFromChildren() override;
+  void AddVisualOverflowFromChildren() override;
+  void AddLayoutOverflowFromChildren() override;
 
   MultiColumnFragmentainerGroupList fragmentainer_groups_;
   LayoutFlowThread* flow_thread_;
diff --git a/third_party/blink/renderer/core/layout/layout_table.cc b/third_party/blink/renderer/core/layout/layout_table.cc
index 1625cca8..c28b0e7 100644
--- a/third_party/blink/renderer/core/layout/layout_table.cc
+++ b/third_party/blink/renderer/core/layout/layout_table.cc
@@ -910,7 +910,38 @@
   }
 }
 
-void LayoutTable::AddOverflowFromChildren() {
+void LayoutTable::AddVisualOverflowFromChildren() {
+  // Add overflow from borders.
+  // Technically it's odd that we are incorporating the borders into layout
+  // overflow, which is only supposed to be about overflow from our
+  // descendant objects, but since tables don't support overflow:auto, this
+  // works out fine.
+  UpdateCollapsedOuterBorders();
+  if (ShouldCollapseBorders() && (collapsed_outer_border_start_overflow_ ||
+                                  collapsed_outer_border_end_overflow_)) {
+    LogicalToPhysical<LayoutUnit> physical_border_overflow(
+        StyleRef().GetWritingMode(), StyleRef().Direction(),
+        LayoutUnit(collapsed_outer_border_start_overflow_),
+        LayoutUnit(collapsed_outer_border_end_overflow_), LayoutUnit(),
+        LayoutUnit());
+    LayoutRect border_overflow(PixelSnappedBorderBoxRect());
+    border_overflow.Expand(LayoutRectOutsets(
+        physical_border_overflow.Top(), physical_border_overflow.Right(),
+        physical_border_overflow.Bottom(), physical_border_overflow.Left()));
+    AddSelfVisualOverflow(border_overflow);
+  }
+
+  // Add overflow from our caption.
+  for (auto* caption : captions_)
+    AddVisualOverflowFromChild(*caption);
+
+  // Add overflow from our sections.
+  for (LayoutTableSection* section = TopSection(); section;
+       section = SectionBelow(section))
+    AddVisualOverflowFromChild(*section);
+}
+
+void LayoutTable::AddLayoutOverflowFromChildren() {
   // Add overflow from borders.
   // Technically it's odd that we are incorporating the borders into layout
   // overflow, which is only supposed to be about overflow from our
@@ -929,17 +960,16 @@
         physical_border_overflow.Top(), physical_border_overflow.Right(),
         physical_border_overflow.Bottom(), physical_border_overflow.Left()));
     AddLayoutOverflow(border_overflow);
-    AddSelfVisualOverflow(border_overflow);
   }
 
   // Add overflow from our caption.
   for (unsigned i = 0; i < captions_.size(); i++)
-    AddOverflowFromChild(*captions_[i]);
+    AddLayoutOverflowFromChild(*captions_[i]);
 
   // Add overflow from our sections.
   for (LayoutTableSection* section = TopSection(); section;
        section = SectionBelow(section))
-    AddOverflowFromChild(*section);
+    AddLayoutOverflowFromChild(*section);
 }
 
 void LayoutTable::PaintObject(const PaintInfo& paint_info,
diff --git a/third_party/blink/renderer/core/layout/layout_table.h b/third_party/blink/renderer/core/layout/layout_table.h
index 87340b08..48af1bc 100644
--- a/third_party/blink/renderer/core/layout/layout_table.h
+++ b/third_party/blink/renderer/core/layout/layout_table.h
@@ -474,7 +474,8 @@
       OverlayScrollbarClipBehavior =
           kIgnorePlatformOverlayScrollbarSize) const override;
 
-  void AddOverflowFromChildren() override;
+  void AddVisualOverflowFromChildren() override;
+  void AddLayoutOverflowFromChildren() override;
 
   void RecalcSections() const;
 
diff --git a/third_party/blink/renderer/core/layout/layout_table_cell.cc b/third_party/blink/renderer/core/layout/layout_table_cell.cc
index 9c7f0761f..9c85ac7 100644
--- a/third_party/blink/renderer/core/layout/layout_table_cell.cc
+++ b/third_party/blink/renderer/core/layout/layout_table_cell.cc
@@ -381,9 +381,11 @@
   }
 }
 
-void LayoutTableCell::ComputeOverflow(LayoutUnit old_client_after_edge,
-                                      bool recompute_floats) {
-  LayoutBlockFlow::ComputeOverflow(old_client_after_edge, recompute_floats);
+void LayoutTableCell::ComputeVisualOverflow(
+    const LayoutRect& previous_visual_overflow_rect,
+    bool recompute_floats) {
+  LayoutBlockFlow::ComputeVisualOverflow(previous_visual_overflow_rect,
+                                         recompute_floats);
 
   UpdateCollapsedBorderValues();
   if (!collapsed_border_values_)
diff --git a/third_party/blink/renderer/core/layout/layout_table_cell.h b/third_party/blink/renderer/core/layout/layout_table_cell.h
index 44dedd1..e369526c 100644
--- a/third_party/blink/renderer/core/layout/layout_table_cell.h
+++ b/third_party/blink/renderer/core/layout/layout_table_cell.h
@@ -341,8 +341,7 @@
     return is_spanning_collapsed_column_;
   }
 
-  void ComputeOverflow(LayoutUnit old_client_after_edge,
-                       bool recompute_floats = false) override;
+  void ComputeVisualOverflow(const LayoutRect&, bool recompute_floats) override;
 
  protected:
   void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override;
diff --git a/third_party/blink/renderer/core/layout/layout_table_section.cc b/third_party/blink/renderer/core/layout/layout_table_section.cc
index bb44faf4..fe70f5d 100644
--- a/third_party/blink/renderer/core/layout/layout_table_section.cc
+++ b/third_party/blink/renderer/core/layout/layout_table_section.cc
@@ -1361,6 +1361,22 @@
 }
 
 void LayoutTableSection::ComputeOverflowFromDescendants() {
+  auto old_self_visual_overflow_rect = SelfVisualOverflowRect();
+  overflow_.reset();
+  overflowing_cells_.clear();
+  force_full_paint_ = false;
+
+  ComputeVisualOverflowFromDescendants();
+
+  // Overflow rect contributes to the visual rect, so if it has changed then we
+  // need to signal a possible paint invalidation.
+  if (old_self_visual_overflow_rect != SelfVisualOverflowRect())
+    SetShouldCheckForPaintInvalidation();
+
+  ComputeLayoutOverflowFromDescendants();
+}
+
+void LayoutTableSection::ComputeVisualOverflowFromDescendants() {
   // These 2 variables are used to balance the memory consumption vs the paint
   // time on big sections with overflowing cells:
   // 1. For small sections, don't track overflowing cells because for them the
@@ -1379,17 +1395,13 @@
       total_cell_count < kMinCellCountToUsePartialPaint
           ? 0
           : kMaxOverflowingCellRatioForPartialPaint * total_cell_count;
-  auto old_overflow_rect = SelfVisualOverflowRect();
 
-  overflow_.reset();
-  overflowing_cells_.clear();
-  force_full_paint_ = false;
 #if DCHECK_IS_ON()
   bool has_overflowing_cell = false;
 #endif
 
   for (auto* row = FirstRow(); row; row = row->NextRow()) {
-    AddOverflowFromChild(*row);
+    AddVisualOverflowFromChild(*row);
 
     for (auto* cell = row->FirstCell(); cell; cell = cell->NextCell()) {
       // Let the section's self visual overflow cover the cell's whole collapsed
@@ -1422,16 +1434,17 @@
       overflowing_cells_.insert(cell);
     }
   }
-  // Overflow rect contributes to the visual rect, so if it has changed then we
-  // need to signal a possible paint invalidation.
-  if (old_overflow_rect != SelfVisualOverflowRect())
-    SetShouldCheckForPaintInvalidation();
 
 #if DCHECK_IS_ON()
   DCHECK_EQ(has_overflowing_cell, HasOverflowingCell());
 #endif
 }
 
+void LayoutTableSection::ComputeLayoutOverflowFromDescendants() {
+  for (auto* row = FirstRow(); row; row = row->NextRow())
+    AddLayoutOverflowFromChild(*row);
+}
+
 bool LayoutTableSection::RecalcOverflow() {
   if (!ChildNeedsOverflowRecalc())
     return false;
diff --git a/third_party/blink/renderer/core/layout/layout_table_section.h b/third_party/blink/renderer/core/layout/layout_table_section.h
index 438a0dd..364313c 100644
--- a/third_party/blink/renderer/core/layout/layout_table_section.h
+++ b/third_party/blink/renderer/core/layout/layout_table_section.h
@@ -299,6 +299,9 @@
                    HitTestAction) override;
 
  private:
+  void ComputeVisualOverflowFromDescendants();
+  void ComputeLayoutOverflowFromDescendants();
+
   bool IsOfType(LayoutObjectType type) const override {
     return type == kLayoutObjectTableSection || LayoutBox::IsOfType(type);
   }
diff --git a/third_party/blink/renderer/core/layout/layout_text_control_single_line.cc b/third_party/blink/renderer/core/layout/layout_text_control_single_line.cc
index 4774cfd..79a43d1 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control_single_line.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_control_single_line.cc
@@ -313,12 +313,6 @@
     InnerEditorElement()->setScrollTop(new_top);
 }
 
-void LayoutTextControlSingleLine::AddOverflowFromChildren() {
-  // If the INPUT content height is smaller than the font height, the
-  // inner-editor element overflows the INPUT box intentionally, however it
-  // shouldn't affect outside of the INPUT box.  So we ignore child overflow.
-}
-
 HTMLInputElement* LayoutTextControlSingleLine::InputElement() const {
   return ToHTMLInputElement(GetNode());
 }
diff --git a/third_party/blink/renderer/core/layout/layout_text_control_single_line.h b/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
index 02347ad0..9132a9e 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
+++ b/third_party/blink/renderer/core/layout/layout_text_control_single_line.h
@@ -77,7 +77,12 @@
   LayoutUnit ComputeControlLogicalHeight(
       LayoutUnit line_height,
       LayoutUnit non_content_height) const override;
-  void AddOverflowFromChildren() final;
+
+  // If the INPUT content height is smaller than the font height, the
+  // inner-editor element overflows the INPUT box intentionally, however it
+  // shouldn't affect outside of the INPUT box.  So we ignore child overflow.
+  void AddVisualOverflowFromChildren() final {}
+  void AddLayoutOverflowFromChildren() final {}
 
   bool AllowsOverflowClip() const override { return false; }
 
diff --git a/third_party/blink/renderer/core/layout/line/inline_flow_box.cc b/third_party/blink/renderer/core/layout/line/inline_flow_box.cc
index b1ac299..46ff879 100644
--- a/third_party/blink/renderer/core/layout/line/inline_flow_box.cc
+++ b/third_party/blink/renderer/core/layout/line/inline_flow_box.cc
@@ -918,6 +918,28 @@
   }
 }
 
+void InlineFlowBox::OverrideVisualOverflowFromLogicalRect(
+    const LayoutRect& logical_visual_overflow,
+    LayoutUnit line_top,
+    LayoutUnit line_bottom) {
+  // If we are setting an overflow, then we can't pretend not to have an
+  // overflow.
+  ClearKnownToHaveNoOverflow();
+  SetVisualOverflowFromLogicalRect(logical_visual_overflow, line_top,
+                                   line_bottom);
+}
+
+void InlineFlowBox::OverrideLayoutOverflowFromLogicalRect(
+    const LayoutRect& logical_layout_overflow,
+    LayoutUnit line_top,
+    LayoutUnit line_bottom) {
+  // If we are setting an overflow, then we can't pretend not to have an
+  // overflow.
+  ClearKnownToHaveNoOverflow();
+  SetLayoutOverflowFromLogicalRect(logical_layout_overflow, line_top,
+                                   line_bottom);
+}
+
 LayoutUnit InlineFlowBox::FarthestPositionForUnderline(
     LineLayoutItem decorating_box,
     FontVerticalPositionType position_type,
@@ -1233,8 +1255,10 @@
     }
   }
 
-  SetOverflowFromLogicalRects(logical_layout_overflow, logical_visual_overflow,
-                              line_top, line_bottom);
+  SetLayoutOverflowFromLogicalRect(logical_layout_overflow, line_top,
+                                   line_bottom);
+  SetVisualOverflowFromLogicalRect(logical_visual_overflow, line_top,
+                                   line_bottom);
 }
 
 void InlineFlowBox::SetLayoutOverflow(const LayoutRect& rect,
@@ -1261,23 +1285,29 @@
   overflow_->SetVisualOverflow(rect);
 }
 
-void InlineFlowBox::SetOverflowFromLogicalRects(
-    const LayoutRect& logical_layout_overflow,
+void InlineFlowBox::SetVisualOverflowFromLogicalRect(
     const LayoutRect& logical_visual_overflow,
     LayoutUnit line_top,
     LayoutUnit line_bottom) {
   DCHECK(!KnownToHaveNoOverflow());
   LayoutRect frame_box = FrameRectIncludingLineHeight(line_top, line_bottom);
+  LayoutRect visual_overflow(IsHorizontal()
+                                 ? logical_visual_overflow
+                                 : logical_visual_overflow.TransposedRect());
+  SetVisualOverflow(visual_overflow, frame_box);
+}
+
+void InlineFlowBox::SetLayoutOverflowFromLogicalRect(
+    const LayoutRect& logical_layout_overflow,
+    LayoutUnit line_top,
+    LayoutUnit line_bottom) {
+  DCHECK(!KnownToHaveNoOverflow());
+  LayoutRect frame_box = FrameRectIncludingLineHeight(line_top, line_bottom);
 
   LayoutRect layout_overflow(IsHorizontal()
                                  ? logical_layout_overflow
                                  : logical_layout_overflow.TransposedRect());
   SetLayoutOverflow(layout_overflow, frame_box);
-
-  LayoutRect visual_overflow(IsHorizontal()
-                                 ? logical_visual_overflow
-                                 : logical_visual_overflow.TransposedRect());
-  SetVisualOverflow(visual_overflow, frame_box);
 }
 
 bool InlineFlowBox::NodeAtPoint(HitTestResult& result,
diff --git a/third_party/blink/renderer/core/layout/line/inline_flow_box.h b/third_party/blink/renderer/core/layout/line/inline_flow_box.h
index 0aa0f64..7891306c 100644
--- a/third_party/blink/renderer/core/layout/line/inline_flow_box.h
+++ b/third_party/blink/renderer/core/layout/line/inline_flow_box.h
@@ -378,19 +378,15 @@
     is_first_after_page_break_ = is_first_after_page_break;
   }
 
-  // Some callers (LayoutListItem) needs to set extra overflow on their line
-  // box.
-  void OverrideOverflowFromLogicalRects(
-      const LayoutRect& logical_layout_overflow,
+  void OverrideVisualOverflowFromLogicalRect(
       const LayoutRect& logical_visual_overflow,
       LayoutUnit line_top,
-      LayoutUnit line_bottom) {
-    // If we are setting an overflow, then we can't pretend not to have an
-    // overflow.
-    ClearKnownToHaveNoOverflow();
-    SetOverflowFromLogicalRects(logical_layout_overflow,
-                                logical_visual_overflow, line_top, line_bottom);
-  }
+      LayoutUnit line_bottom);
+
+  void OverrideLayoutOverflowFromLogicalRect(
+      const LayoutRect& logical_layout_overflow,
+      LayoutUnit line_top,
+      LayoutUnit line_bottom);
 
   LayoutUnit FarthestPositionForUnderline(LineLayoutItem decorating_box,
                                           FontVerticalPositionType,
@@ -433,10 +429,14 @@
   void SetLayoutOverflow(const LayoutRect&, const LayoutRect&);
   void SetVisualOverflow(const LayoutRect&, const LayoutRect&);
 
-  void SetOverflowFromLogicalRects(const LayoutRect& logical_layout_overflow,
-                                   const LayoutRect& logical_visual_overflow,
-                                   LayoutUnit line_top,
-                                   LayoutUnit line_bottom);
+  void SetLayoutOverflowFromLogicalRect(
+      const LayoutRect& logical_layout_overflow,
+      LayoutUnit line_top,
+      LayoutUnit line_bottom);
+  void SetVisualOverflowFromLogicalRect(
+      const LayoutRect& logical_visual_overflow,
+      LayoutUnit line_top,
+      LayoutUnit line_bottom);
 
  protected:
   std::unique_ptr<SimpleOverflowModel> overflow_;
diff --git a/third_party/blink/renderer/core/layout/min_max_size.cc b/third_party/blink/renderer/core/layout/min_max_size.cc
index c501516..e6980104 100644
--- a/third_party/blink/renderer/core/layout/min_max_size.cc
+++ b/third_party/blink/renderer/core/layout/min_max_size.cc
@@ -8,42 +8,6 @@
 
 namespace blink {
 
-void MinMaxSize::Encompass(const MinMaxSize& other) {
-  min_size = std::max(min_size, other.min_size);
-  max_size = std::max(max_size, other.max_size);
-}
-
-void MinMaxSize::Encompass(LayoutUnit value) {
-  min_size = std::max(min_size, value);
-  max_size = std::max(max_size, value);
-}
-
-void MinMaxSize::Constrain(LayoutUnit value) {
-  min_size = std::min(min_size, value);
-  max_size = std::min(max_size, value);
-}
-
-LayoutUnit MinMaxSize::ShrinkToFit(LayoutUnit available_size) const {
-  DCHECK_GE(max_size, min_size);
-  return std::min(max_size, std::max(min_size, available_size));
-}
-
-LayoutUnit MinMaxSize::ClampSizeToMinAndMax(LayoutUnit size) const {
-  return std::max(min_size, std::min(size, max_size));
-}
-
-MinMaxSize& MinMaxSize::operator+=(const LayoutUnit length) {
-  min_size += length;
-  max_size += length;
-  return *this;
-}
-
-MinMaxSize& MinMaxSize::operator-=(const LayoutUnit length) {
-  min_size -= length;
-  max_size -= length;
-  return *this;
-}
-
 std::ostream& operator<<(std::ostream& stream, const MinMaxSize& value) {
   return stream << "(" << value.min_size << ", " << value.max_size << ")";
 }
diff --git a/third_party/blink/renderer/core/layout/min_max_size.h b/third_party/blink/renderer/core/layout/min_max_size.h
index b17ecb6..b8069c11 100644
--- a/third_party/blink/renderer/core/layout/min_max_size.h
+++ b/third_party/blink/renderer/core/layout/min_max_size.h
@@ -18,21 +18,35 @@
   LayoutUnit max_size;
 
   // Make sure that our min/max sizes are at least as large as |other|.
-  void Encompass(const MinMaxSize& other);
+  void Encompass(const MinMaxSize& other) {
+    min_size = std::max(min_size, other.min_size);
+    max_size = std::max(max_size, other.max_size);
+  }
 
   // Make sure that our min/max sizes are at least as large as |value|.
-  void Encompass(LayoutUnit value);
+  void Encompass(LayoutUnit value) {
+    min_size = std::max(min_size, value);
+    max_size = std::max(max_size, value);
+  }
 
   // Make sure that our min/max sizes aren't larger than |value|.
-  void Constrain(LayoutUnit value);
+  void Constrain(LayoutUnit value) {
+    min_size = std::min(min_size, value);
+    max_size = std::min(max_size, value);
+  }
 
   // Interprets the sizes as a min-content/max-content pair and computes the
   // "shrink-to-fit" size based on them for the given available size.
-  LayoutUnit ShrinkToFit(LayoutUnit available_size) const;
+  LayoutUnit ShrinkToFit(LayoutUnit available_size) const {
+    DCHECK_GE(max_size, min_size);
+    return std::min(max_size, std::max(min_size, available_size));
+  }
 
   // Interprets the sizes as a {min-max}-size pair and clamps the given input
   // size to that.
-  LayoutUnit ClampSizeToMinAndMax(LayoutUnit) const;
+  LayoutUnit ClampSizeToMinAndMax(LayoutUnit size) const {
+    return std::max(min_size, std::min(size, max_size));
+  }
 
   bool operator==(const MinMaxSize& other) const {
     return min_size == other.min_size && max_size == other.max_size;
@@ -44,8 +58,16 @@
     max_size += extra.max_size;
     return *this;
   }
-  MinMaxSize& operator+=(const LayoutUnit);
-  MinMaxSize& operator-=(const LayoutUnit);
+  MinMaxSize& operator+=(const LayoutUnit length) {
+    min_size += length;
+    max_size += length;
+    return *this;
+  }
+  MinMaxSize& operator-=(const LayoutUnit length) {
+    min_size -= length;
+    max_size -= length;
+    return *this;
+  }
 };
 
 CORE_EXPORT std::ostream& operator<<(std::ostream&, const MinMaxSize&);
diff --git a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc
index c3b047c1..78822e0 100644
--- a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc
+++ b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.cc
@@ -9,20 +9,6 @@
 
 namespace blink {
 
-bool NGBoxStrut::IsEmpty() const {
-  return *this == NGBoxStrut();
-}
-
-bool NGBoxStrut::operator==(const NGBoxStrut& other) const {
-  return std::tie(other.inline_start, other.inline_end, other.block_start,
-                  other.block_end) ==
-         std::tie(inline_start, inline_end, block_start, block_end);
-}
-
-bool NGBoxStrut::operator!=(const NGBoxStrut& other) const {
-  return !(*this == other);
-}
-
 NGPhysicalBoxStrut NGBoxStrut::ConvertToPhysical(
     WritingMode writing_mode,
     TextDirection direction) const {
@@ -73,17 +59,6 @@
   return strut;
 }
 
-NGLineBoxStrut NGPhysicalBoxStrut::ConvertToLineLogical(
-    WritingMode writing_mode,
-    TextDirection direction) const {
-  return NGLineBoxStrut(ConvertToLogical(writing_mode, direction),
-                        IsFlippedLinesWritingMode(writing_mode));
-}
-
-LayoutRectOutsets NGPhysicalBoxStrut::ToLayoutRectOutsets() const {
-  return LayoutRectOutsets(top, right, bottom, left);
-}
-
 String NGBoxStrut::ToString() const {
   return String::Format("Inline: (%d %d) Block: (%d %d)", inline_start.ToInt(),
                         inline_end.ToInt(), block_start.ToInt(),
@@ -116,9 +91,8 @@
   }
 }
 
-bool NGLineBoxStrut::operator==(const NGLineBoxStrut& other) const {
-  return inline_start == other.inline_start && inline_end == other.inline_end &&
-         line_over == other.line_over && line_under == other.line_under;
+LayoutRectOutsets NGPhysicalBoxStrut::ToLayoutRectOutsets() const {
+  return LayoutRectOutsets(top, right, bottom, left);
 }
 
 std::ostream& operator<<(std::ostream& stream, const NGLineBoxStrut& value) {
@@ -127,9 +101,4 @@
                 << ") ";
 }
 
-NGPixelSnappedPhysicalBoxStrut NGPhysicalBoxStrut::SnapToDevicePixels() const {
-  return NGPixelSnappedPhysicalBoxStrut(top.Round(), right.Round(),
-                                        bottom.Round(), left.Round());
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
index f4610ed..038ec75 100644
--- a/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
+++ b/third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h
@@ -43,7 +43,7 @@
 
   NGLogicalOffset StartOffset() const { return {inline_start, block_start}; }
 
-  bool IsEmpty() const;
+  bool IsEmpty() const { return *this == NGBoxStrut(); }
 
   NGPhysicalBoxStrut ConvertToPhysical(WritingMode, TextDirection) const;
 
@@ -77,8 +77,12 @@
     return result;
   }
 
-  bool operator==(const NGBoxStrut& other) const;
-  bool operator!=(const NGBoxStrut& other) const;
+  bool operator==(const NGBoxStrut& other) const {
+    return std::tie(other.inline_start, other.inline_end, other.block_start,
+                    other.block_end) ==
+           std::tie(inline_start, inline_end, block_start, block_end);
+  }
+  bool operator!=(const NGBoxStrut& other) const { return !(*this == other); }
 
   String ToString() const;
 
@@ -112,7 +116,11 @@
   LayoutUnit InlineSum() const { return inline_start + inline_end; }
   LayoutUnit BlockSum() const { return line_over + line_under; }
 
-  bool operator==(const NGLineBoxStrut& other) const;
+  bool operator==(const NGLineBoxStrut& other) const {
+    return inline_start == other.inline_start &&
+           inline_end == other.inline_end && line_over == other.line_over &&
+           line_under == other.line_under;
+  }
 
   LayoutUnit inline_start;
   LayoutUnit inline_end;
@@ -122,7 +130,16 @@
 
 CORE_EXPORT std::ostream& operator<<(std::ostream&, const NGLineBoxStrut&);
 
-struct NGPixelSnappedPhysicalBoxStrut;
+// Struct to store pixel snapped physical dimensions.
+struct CORE_EXPORT NGPixelSnappedPhysicalBoxStrut {
+  NGPixelSnappedPhysicalBoxStrut() = default;
+  NGPixelSnappedPhysicalBoxStrut(int top, int right, int bottom, int left)
+      : top(top), right(right), bottom(bottom), left(left) {}
+  int top;
+  int right;
+  int bottom;
+  int left;
+};
 
 // Struct to store physical dimensions, independent of writing mode and
 // direction.
@@ -141,9 +158,16 @@
 
   // Converts physical dimensions to line-relative logical ones per
   // https://drafts.csswg.org/css-writing-modes-3/#line-directions
-  NGLineBoxStrut ConvertToLineLogical(WritingMode, TextDirection) const;
+  NGLineBoxStrut ConvertToLineLogical(WritingMode writing_mode,
+                                      TextDirection direction) const {
+    return NGLineBoxStrut(ConvertToLogical(writing_mode, direction),
+                          IsFlippedLinesWritingMode(writing_mode));
+  }
 
-  NGPixelSnappedPhysicalBoxStrut SnapToDevicePixels() const;
+  NGPixelSnappedPhysicalBoxStrut SnapToDevicePixels() const {
+    return NGPixelSnappedPhysicalBoxStrut(top.Round(), right.Round(),
+                                          bottom.Round(), left.Round());
+  }
 
   LayoutUnit HorizontalSum() const { return left + right; }
   LayoutUnit VerticalSum() const { return top + bottom; }
@@ -156,17 +180,6 @@
   LayoutUnit left;
 };
 
-// Struct to store pixel snapped physical dimensions.
-struct CORE_EXPORT NGPixelSnappedPhysicalBoxStrut {
-  NGPixelSnappedPhysicalBoxStrut() = default;
-  NGPixelSnappedPhysicalBoxStrut(int top, int right, int bottom, int left)
-      : top(top), right(right), bottom(bottom), left(left) {}
-  int top;
-  int right;
-  int bottom;
-  int left;
-};
-
 }  // namespace blink
 
 #endif  // NGBoxStrut_h
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
index a65589e..50e9985 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.cc
@@ -10,13 +10,9 @@
 
 namespace blink {
 
-bool operator==(const NGBaselineRequest& lhs, const NGBaselineRequest& rhs) {
-  return lhs.algorithm_type == rhs.algorithm_type &&
-         lhs.baseline_type == rhs.baseline_type;
-}
-
-bool operator!=(const NGBaselineRequest& lhs, const NGBaselineRequest& rhs) {
-  return !(lhs == rhs);
+bool NGBaselineRequest::operator==(const NGBaselineRequest& other) const {
+  return algorithm_type == other.algorithm_type &&
+         baseline_type == other.baseline_type;
 }
 
 bool NGBaseline::ShouldPropagateBaselines(const NGLayoutInputNode node) {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h b/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
index 20a8d99..cfcd9a21 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_baseline.h
@@ -25,10 +25,12 @@
 struct NGBaselineRequest {
   NGBaselineAlgorithmType algorithm_type;
   FontBaseline baseline_type;
-};
 
-bool operator==(const NGBaselineRequest&, const NGBaselineRequest&);
-bool operator!=(const NGBaselineRequest&, const NGBaselineRequest&);
+  bool operator==(const NGBaselineRequest& other) const;
+  bool operator!=(const NGBaselineRequest& other) const {
+    return !(*this == other);
+  }
+};
 
 // Represents a computed baseline position.
 struct NGBaseline {
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
index 95a6c324..ce4c4c1 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
@@ -66,12 +66,11 @@
 }
 
 template <typename Base>
-void LayoutNGMixin<Base>::AddOverflowFromChildren() {
+void LayoutNGMixin<Base>::AddVisualOverflowFromChildren() {
   // |ComputeOverflow()| calls this, which is called from
   // |CopyFragmentDataToLayoutBox()| and |RecalcOverflowAfterStyleChange()|.
   // Add overflow from the last layout cycle.
   if (const NGPhysicalBoxFragment* physical_fragment = CurrentFragment()) {
-    AddScrollingOverflowFromChildren();
     if (Base::ChildrenInline()) {
       Base::AddSelfVisualOverflow(
           physical_fragment->SelfInkOverflow().ToLayoutFlippedRect(
@@ -87,7 +86,19 @@
       // correctly without RootInlineBox though.
     }
   }
-  Base::AddOverflowFromChildren();
+  Base::AddVisualOverflowFromChildren();
+}
+
+template <typename Base>
+void LayoutNGMixin<Base>::AddLayoutOverflowFromChildren() {
+  // |ComputeOverflow()| calls this, which is called from
+  // |CopyFragmentDataToLayoutBox()| and |RecalcOverflow()|.
+  // Add overflow from the last layout cycle.
+  // TODO(chrishtr): do we need to condition on CurrentFragment()? Why?
+  if (CurrentFragment()) {
+    AddScrollingOverflowFromChildren();
+  }
+  Base::AddLayoutOverflowFromChildren();
 }
 
 template <typename Base>
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
index 3dc73c2..8f1e1e4 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
@@ -82,7 +82,8 @@
  protected:
   bool IsOfType(LayoutObject::LayoutObjectType) const override;
 
-  void AddOverflowFromChildren() final;
+  void AddVisualOverflowFromChildren() final;
+  void AddLayoutOverflowFromChildren() final;
 
  private:
   void AddScrollingOverflowFromChildren();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 738e260..26afc91 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -576,7 +576,7 @@
                                         physical_fragment);
     }
 
-    // |ComputeOverflow()| below calls |AddOverflowFromChildren()|, which
+    // |ComputeOverflow()| below calls |AddVisualOverflowFromChildren()|, which
     // computes visual overflow from |RootInlineBox| if |ChildrenInline()|
     block->ComputeOverflow(intrinsic_block_size - borders.block_end -
                            scrollbars.block_end);
@@ -592,8 +592,10 @@
     LayoutBlockFlow* block_flow = ToLayoutBlockFlow(box_);
     block_flow->UpdateIsSelfCollapsing();
 
-    if (constraint_space.IsNewFormattingContext())
-      block_flow->AddOverflowFromFloats();
+    if (constraint_space.IsNewFormattingContext()) {
+      block_flow->AddVisualOverflowFromFloats();
+      block_flow->AddLayoutOverflowFromFloats();
+    }
   }
 }
 
@@ -617,8 +619,10 @@
       CopyChildFragmentPosition(box_fragment, child_fragment.Offset(),
                                 offset_from_start);
     }
-    if (child_object->IsLayoutBlockFlow())
-      ToLayoutBlockFlow(child_object)->AddOverflowFromFloats();
+    if (child_object->IsLayoutBlockFlow()) {
+      ToLayoutBlockFlow(child_object)->AddVisualOverflowFromFloats();
+      ToLayoutBlockFlow(child_object)->AddLayoutOverflowFromFloats();
+    }
   }
 
   if (rendered_legend) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc
index 140a787..b4c9d8f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.cc
@@ -248,10 +248,6 @@
       .inline_size;
 }
 
-NGFragmentationType NGConstraintSpace::BlockFragmentationType() const {
-  return static_cast<NGFragmentationType>(block_direction_fragmentation_type_);
-}
-
 bool NGConstraintSpace::operator==(const NGConstraintSpace& other) const {
   return available_size_ == other.available_size_ &&
          percentage_resolution_size_ == other.percentage_resolution_size_ &&
@@ -280,10 +276,6 @@
          baseline_requests_ == other.baseline_requests_;
 }
 
-bool NGConstraintSpace::operator!=(const NGConstraintSpace& other) const {
-  return !(*this == other);
-}
-
 String NGConstraintSpace::ToString() const {
   return String::Format("Offset: %s,%s Size: %sx%s Clearance: %s",
                         bfc_offset_.line_offset.ToString().Ascii().data(),
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
index 377b9de..f2caee8 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
@@ -184,7 +184,10 @@
 
   // If specified a layout should produce a Fragment which fragments at the
   // blockSize if possible.
-  NGFragmentationType BlockFragmentationType() const;
+  NGFragmentationType BlockFragmentationType() const {
+    return static_cast<NGFragmentationType>(
+        block_direction_fragmentation_type_);
+  }
 
   // Return true if this constraint space participates in a fragmentation
   // context.
@@ -278,7 +281,9 @@
   }
 
   bool operator==(const NGConstraintSpace&) const;
-  bool operator!=(const NGConstraintSpace&) const;
+  bool operator!=(const NGConstraintSpace& other) const {
+    return !(*this == other);
+  }
 
   String ToString() const;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc
index cf71646..6b9a510 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.cc
@@ -8,24 +8,6 @@
 
 namespace blink {
 
-NGConstraintSpaceBuilder::NGConstraintSpaceBuilder(
-    const NGConstraintSpace& parent_space)
-    : NGConstraintSpaceBuilder(parent_space.GetWritingMode(),
-                               parent_space.InitialContainingBlockSize()) {
-  parent_percentage_resolution_size_ = parent_space.PercentageResolutionSize();
-
-  flags_ = NGConstraintSpace::kFixedSizeBlockIsDefinite;
-  if (parent_space.IsIntermediateLayout())
-    flags_ |= NGConstraintSpace::kIntermediateLayout;
-}
-
-NGConstraintSpaceBuilder::NGConstraintSpaceBuilder(WritingMode writing_mode,
-                                                   NGPhysicalSize icb_size)
-    : initial_containing_block_size_(icb_size),
-      parent_writing_mode_(writing_mode) {
-  flags_ = NGConstraintSpace::kFixedSizeBlockIsDefinite;
-}
-
 NGConstraintSpaceBuilder& NGConstraintSpaceBuilder::AddBaselineRequest(
     const NGBaselineRequest& request) {
   for (const auto& existing : baseline_requests_) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
index e8ac08b..9cd328c 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
@@ -25,11 +25,23 @@
   // NOTE: This constructor doesn't act like a copy-constructor, it uses the
   // writing_mode and icb_size from the parent constraint space, and passes
   // them to the constructor below.
-  NGConstraintSpaceBuilder(const NGConstraintSpace& parent_space);
+  NGConstraintSpaceBuilder(const NGConstraintSpace& parent_space)
+      : NGConstraintSpaceBuilder(parent_space.GetWritingMode(),
+                                 parent_space.InitialContainingBlockSize()) {
+    parent_percentage_resolution_size_ =
+        parent_space.PercentageResolutionSize();
+    flags_ = NGConstraintSpace::kFixedSizeBlockIsDefinite;
+    if (parent_space.IsIntermediateLayout())
+      flags_ |= NGConstraintSpace::kIntermediateLayout;
+  }
 
   // writing_mode is the writing mode that the logical sizes passed to the
   // setters are in.
-  NGConstraintSpaceBuilder(WritingMode writing_mode, NGPhysicalSize icb_size);
+  NGConstraintSpaceBuilder(WritingMode writing_mode, NGPhysicalSize icb_size)
+      : initial_containing_block_size_(icb_size),
+        parent_writing_mode_(writing_mode) {
+    flags_ = NGConstraintSpace::kFixedSizeBlockIsDefinite;
+  }
 
   NGConstraintSpaceBuilder& SetAvailableSize(NGLogicalSize available_size) {
     available_size_ = available_size;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
index 6c6250c4..cd7b3ea 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
@@ -36,7 +36,7 @@
   LayoutUnit flex_container_content_inline_size =
       flex_container_content_box_size.inline_size;
 
-  FlexItemVector flex_items;
+  FlexLayoutAlgorithm algorithm(&Style(), flex_container_content_inline_size);
   for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child;
        generic_child = generic_child.NextSibling()) {
     NGBlockNode child = ToNGBlockNode(generic_child);
@@ -94,15 +94,13 @@
     // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
     MinMaxSize min_max_sizes_in_main_axis_direction{LayoutUnit(),
                                                     LayoutUnit::Max()};
-    flex_items.emplace_back(child.GetLayoutBox(), flex_base_content_size,
-                            min_max_sizes_in_main_axis_direction,
-                            main_axis_border_and_padding, main_axis_margin);
-    flex_items.back().ng_input_node = child;
+    algorithm
+        .emplace_back(child.GetLayoutBox(), flex_base_content_size,
+                      min_max_sizes_in_main_axis_direction,
+                      main_axis_border_and_padding, main_axis_margin)
+        .ng_input_node = child;
   }
 
-  FlexLayoutAlgorithm algorithm(&Style(), flex_container_content_inline_size,
-                                flex_items);
-
   NGBoxStrut borders_scrollbar_padding =
       CalculateBorderScrollbarPadding(ConstraintSpace(), Node());
   LayoutUnit main_axis_offset = borders_scrollbar_padding.inline_start;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.cc b/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.cc
index 86a72b6e..66f347ea 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.cc
@@ -6,7 +6,6 @@
 
 #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
-#include "third_party/blink/renderer/core/style/computed_style.h"
 
 namespace blink {
 
@@ -18,16 +17,4 @@
 
 NGUnpositionedFloat::~NGUnpositionedFloat() = default;
 
-bool NGUnpositionedFloat::IsLeft() const {
-  return node.Style().Floating() == EFloat::kLeft;
-}
-
-bool NGUnpositionedFloat::IsRight() const {
-  return node.Style().Floating() == EFloat::kRight;
-}
-
-EClear NGUnpositionedFloat::ClearType() const {
-  return node.Style().Clear();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h b/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h
index b479a75..022e1d5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 
 namespace blink {
@@ -35,9 +36,9 @@
   scoped_refptr<NGLayoutResult> layout_result;
   NGBoxStrut margins;
 
-  bool IsLeft() const;
-  bool IsRight() const;
-  EClear ClearType() const;
+  bool IsLeft() const { return node.Style().Floating() == EFloat::kLeft; }
+  bool IsRight() const { return node.Style().Floating() == EFloat::kRight; }
+  EClear ClearType() const { return node.Style().Clear(); }
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index b87a9a6..5f5effe7 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -444,6 +444,7 @@
       state_ = kSentDidFinishLoad;
       GetLocalFrameClient().DispatchDidFailProvisionalLoad(error,
                                                            history_commit_type);
+      probe::didFailProvisionalLoad(frame_);
       if (frame_)
         GetFrameLoader().DetachProvisionalDocumentLoader(this);
       break;
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index c51ca2e..880ce05 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -995,6 +995,7 @@
   // the DidStartProvisionalLoad() notification.
   Client()->DispatchDidStartProvisionalLoad(provisional_document_loader_,
                                             resource_request);
+  probe::didStartProvisionalLoad(frame_);
   DCHECK(provisional_document_loader_);
   TakeObjectSnapshot();
 }
@@ -1088,6 +1089,7 @@
   frame_->GetFrameScheduler()->DidStartProvisionalLoad(frame_->IsMainFrame());
   Client()->DispatchDidStartProvisionalLoad(provisional_document_loader_,
                                             resource_request);
+  probe::didStartProvisionalLoad(frame_);
 
   provisional_document_loader_->StartLoading();
   TakeObjectSnapshot();
diff --git a/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc b/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
index 119f7ba3..254d079 100644
--- a/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/snap_coordinator_test.cc
@@ -620,10 +620,10 @@
       ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
       gfx::RectF(10, 10, width - 20, height - 20),
       gfx::ScrollOffset(max_position.X(), max_position.Y()));
-  // Under vertical-rl writing mode, 'start' should align to the right, so the
-  // alignment on x should be reversed.
+  // Under vertical-rl writing mode, 'start' should align to the right
+  // and 'end' should align to the left.
   SnapAreaData expected_area(
-      ScrollSnapAlign(SnapAlignment::kEnd, SnapAlignment::kStart),
+      ScrollSnapAlign(SnapAlignment::kStart, SnapAlignment::kEnd),
       gfx::RectF(192, 192, 116, 116), false);
   expected_container.AddSnapAreaData(expected_area);
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index f44b711..967b42f 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -955,7 +955,7 @@
             ->GetFrame()
             ->GetPage()
             ->GetVisualViewport()
-            .SetNeedsPaintPropertiesUpdate();
+            .SetNeedsPaintPropertyUpdate();
       }
     }
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index f02567d..5fd0b75 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -316,6 +316,13 @@
   return object.HasLayer() && object.ShouldApplyPaintContainment();
 }
 
+static bool NeedsStickyTranslation(const LayoutObject& object) {
+  if (!object.IsBoxModelObject())
+    return false;
+
+  return object.StyleRef().HasStickyConstrainedPosition();
+}
+
 static bool NeedsPaintOffsetTranslation(const LayoutObject& object) {
   if (!object.IsBoxModelObject())
     return false;
@@ -349,6 +356,8 @@
   }
   if (NeedsScrollOrScrollTranslation(object))
     return true;
+  if (NeedsStickyTranslation(object))
+    return true;
   if (NeedsPaintOffsetTranslationForScrollbars(box_model))
     return true;
   if (NeedsReplacedContentTransform(object))
@@ -435,13 +444,6 @@
   }
 }
 
-static bool NeedsStickyTranslation(const LayoutObject& object) {
-  if (!object.IsBoxModelObject())
-    return false;
-
-  return object.StyleRef().HasStickyConstrainedPosition();
-}
-
 void FragmentPaintPropertyTreeBuilder::UpdateStickyTranslation() {
   DCHECK(properties_);
 
@@ -709,9 +711,6 @@
     return false;
 
   if (object.IsSVG()) {
-    if (object.IsSVGRoot() && is_css_isolated_group &&
-        object.HasNonIsolatedBlendingDescendants())
-      return true;
     if (SVGLayoutSupport::IsIsolationRequired(&object))
       return true;
     if (SVGResources* resources =
@@ -720,11 +719,31 @@
         return true;
       }
     }
-  } else if (object.IsBoxModelObject()) {
-    if (PaintLayer* layer = ToLayoutBoxModelObject(object).Layer()) {
-      if (layer->HasNonIsolatedDescendantWithBlendMode())
-        return true;
-    }
+  }
+
+  if (is_css_isolated_group) {
+    if (object.IsSVGRoot() && object.HasNonIsolatedBlendingDescendants())
+      return true;
+
+    const auto* layer = ToLayoutBoxModelObject(object).Layer();
+    DCHECK(layer);
+
+    if (layer->HasNonIsolatedDescendantWithBlendMode())
+      return true;
+
+    // In SPv1* a mask layer can be created for clip-path in absence of mask,
+    // and a mask effect node must be created whether the clip-path is
+    // path-based or not.
+    if (layer->GetCompositedLayerMapping() &&
+        layer->GetCompositedLayerMapping()->MaskLayer())
+      return true;
+
+    // An effect node is required by cc if the layer flattens its subtree but it
+    // is treated as a 3D object by its parent.
+    if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
+        !layer->Preserves3D() && layer->HasSelfPaintingLayerDescendant() &&
+        layer->Parent() && layer->Parent()->Preserves3D())
+      return true;
   }
 
   SkBlendMode blend_mode = object.IsBlendingAllowed()
@@ -743,18 +762,6 @@
   if (object.StyleRef().HasMask())
     return true;
 
-  if (object.HasLayer() &&
-      ToLayoutBoxModelObject(object).Layer()->GetCompositedLayerMapping() &&
-      ToLayoutBoxModelObject(object)
-          .Layer()
-          ->GetCompositedLayerMapping()
-          ->MaskLayer()) {
-    // In SPv1* a mask layer can be created for clip-path in absence of mask,
-    // and a mask effect node must be created whether the clip-path is
-    // path-based or not.
-    return true;
-  }
-
   if (object.StyleRef().ClipPath() &&
       object.FirstFragment().ClipPathBoundingBox() &&
       !object.FirstFragment().ClipPathPath()) {
@@ -763,16 +770,6 @@
     return true;
   }
 
-  // An effect node is required by cc if the layer flattens its subtree but it
-  // is treated as a 3D object by its parent.
-  if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
-      object.HasLayer()) {
-    PaintLayer* layer = ToLayoutBoxModelObject(object).Layer();
-    if (!layer->Preserves3D() && layer->HasSelfPaintingLayerDescendant() &&
-        layer->Parent() && layer->Parent()->Preserves3D())
-      return true;
-  }
-
   return false;
 }
 
@@ -814,7 +811,6 @@
   DCHECK(properties_);
   const ComputedStyle& style = object_.StyleRef();
 
-  // TODO(trchen): Can't omit effect node if we have 3D children.
   if (NeedsPaintPropertyUpdate()) {
     if (NeedsEffect(object_)) {
       base::Optional<IntRect> mask_clip = CSSMaskPainter::MaskBoundingBox(
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 897ac7f..d8b04e0 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -6390,4 +6390,78 @@
   EXPECT_EQ(nullptr, PaintPropertiesForElement("col"));
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, SVGRootCompositedClipPath) {
+  SetBodyInnerHTML(R"HTML(
+    <svg id='svg' style='clip-path: circle(); will-change: transform'></svg>
+  )HTML");
+
+  const auto* properties = PaintPropertiesForElement("svg");
+
+  ASSERT_NE(nullptr, properties->PaintOffsetTranslation());
+  const auto* transform = properties->Transform();
+  ASSERT_NE(nullptr, transform);
+  EXPECT_EQ(properties->PaintOffsetTranslation(), transform->Parent());
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
+      RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
+    EXPECT_TRUE(transform->HasDirectCompositingReasons());
+
+  if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
+    EXPECT_EQ(nullptr, properties->MaskClip());
+
+    const auto* clip_path_clip = properties->ClipPathClip();
+    ASSERT_NE(nullptr, clip_path_clip);
+    EXPECT_EQ(DocContentClip(), clip_path_clip->Parent());
+    EXPECT_EQ(FloatRect(75, 0, 150, 150), clip_path_clip->ClipRect().Rect());
+    EXPECT_EQ(transform, clip_path_clip->LocalTransformSpace());
+    EXPECT_NE(nullptr, clip_path_clip->ClipPath());
+
+    const auto* overflow_clip = properties->OverflowClip();
+    ASSERT_NE(nullptr, overflow_clip);
+    EXPECT_EQ(clip_path_clip, overflow_clip->Parent());
+    EXPECT_EQ(FloatRect(0, 0, 300, 150), overflow_clip->ClipRect().Rect());
+    EXPECT_EQ(transform, overflow_clip->LocalTransformSpace());
+
+    // TODO(wangxianzhu): Are the following correct?
+    EXPECT_EQ(nullptr, properties->Effect());
+    EXPECT_EQ(nullptr, properties->Mask());
+    EXPECT_EQ(nullptr, properties->ClipPath());
+  } else {
+    const auto* mask_clip = properties->MaskClip();
+    ASSERT_NE(nullptr, mask_clip);
+    EXPECT_EQ(DocContentClip(), mask_clip->Parent());
+    EXPECT_EQ(FloatRect(75, 0, 150, 150), mask_clip->ClipRect().Rect());
+    EXPECT_EQ(nullptr, mask_clip->ClipPath());
+    EXPECT_EQ(transform, mask_clip->LocalTransformSpace());
+
+    const auto* clip_path_clip = properties->ClipPathClip();
+    ASSERT_NE(nullptr, clip_path_clip);
+    EXPECT_EQ(mask_clip, clip_path_clip->Parent());
+    EXPECT_EQ(FloatRect(75, 0, 150, 150), clip_path_clip->ClipRect().Rect());
+    EXPECT_EQ(transform, clip_path_clip->LocalTransformSpace());
+    EXPECT_NE(nullptr, clip_path_clip->ClipPath());
+
+    const auto* overflow_clip = properties->OverflowClip();
+    ASSERT_NE(nullptr, overflow_clip);
+    EXPECT_EQ(mask_clip, overflow_clip->Parent());
+    EXPECT_EQ(FloatRect(0, 0, 300, 150), overflow_clip->ClipRect().Rect());
+    EXPECT_EQ(transform, overflow_clip->LocalTransformSpace());
+
+    const auto* effect = properties->Effect();
+    ASSERT_NE(nullptr, effect);
+    EXPECT_EQ(&EffectPaintPropertyNode::Root(), effect->Parent());
+    EXPECT_EQ(transform, effect->LocalTransformSpace());
+    EXPECT_EQ(mask_clip, effect->OutputClip());
+    EXPECT_EQ(SkBlendMode::kSrcOver, effect->BlendMode());
+
+    const auto* mask = properties->Mask();
+    ASSERT_NE(nullptr, mask);
+    EXPECT_EQ(effect, mask->Parent());
+    EXPECT_EQ(transform, mask->LocalTransformSpace());
+    EXPECT_EQ(mask_clip, mask->OutputClip());
+    EXPECT_EQ(SkBlendMode::kDstIn, mask->BlendMode());
+
+    EXPECT_EQ(nullptr, properties->ClipPath());
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index b5389d8..4833959 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h"
 #include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
+#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 
 namespace blink {
 
@@ -1367,4 +1368,54 @@
   EXPECT_EQ(nullptr, effect_properties->Effect()->OutputClip());
 }
 
+TEST_P(PaintPropertyTreeUpdateTest, ForwardReferencedSVGElementUpdate) {
+  SetBodyInnerHTML(R"HTML(
+    <svg id="svg1" filter="url(#filter)">
+      <filter id="filter">
+        <feImage id="image" href="#rect"/>
+      </filter>
+    </svg>
+    <svg id="svg2" style="perspective: 10px">
+      <rect id="rect" width="100" height="100" transform="translate(1)"/>
+    </svg>
+  )HTML");
+
+  const auto* svg2_properties = PaintPropertiesForElement("svg2");
+  EXPECT_NE(nullptr, svg2_properties->PaintOffsetTranslation());
+  EXPECT_EQ(nullptr, svg2_properties->Transform());
+  EXPECT_NE(nullptr, svg2_properties->Perspective());
+  EXPECT_EQ(svg2_properties->PaintOffsetTranslation(),
+            svg2_properties->Perspective()->Parent());
+
+  const auto* rect_properties = PaintPropertiesForElement("rect");
+  ASSERT_NE(nullptr, rect_properties->Transform());
+  EXPECT_EQ(svg2_properties->Perspective(),
+            rect_properties->Transform()->Parent());
+  EXPECT_EQ(TransformationMatrix().Translate(1, 0),
+            GeometryMapper::SourceToDestinationProjection(
+                rect_properties->Transform(),
+                svg2_properties->PaintOffsetTranslation()));
+
+  // Change filter which forward references rect, and insert a transform
+  // node above rect's transform.
+  GetDocument().getElementById("filter")->setAttribute("width", "20");
+  GetDocument().getElementById("svg2")->setAttribute("transform",
+                                                     "translate(2)");
+  UpdateAllLifecyclePhases();
+
+  EXPECT_NE(nullptr, svg2_properties->Transform());
+  EXPECT_EQ(svg2_properties->PaintOffsetTranslation(),
+            svg2_properties->Transform()->Parent());
+  EXPECT_EQ(svg2_properties->Transform(),
+            svg2_properties->Perspective()->Parent());
+  EXPECT_EQ(svg2_properties->Perspective(),
+            rect_properties->Transform()->Parent());
+
+  // Ensure that GeometryMapper's cache is properly invalidated and updated.
+  EXPECT_EQ(TransformationMatrix().Translate(3, 0),
+            GeometryMapper::SourceToDestinationProjection(
+                rect_properties->Transform(),
+                svg2_properties->PaintOffsetTranslation()));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index 07b8080..35a7b782 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/frame/visual_viewport.h"
 #include "third_party/blink/renderer/core/layout/jank_tracker.h"
 #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
 #include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
@@ -271,6 +272,12 @@
 bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate(
     const LocalFrameView& frame_view,
     const PrePaintTreeWalkContext& context) {
+  if ((RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+       RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) &&
+      frame_view.GetFrame().IsLocalRoot() &&
+      frame_view.GetPage()->GetVisualViewport().NeedsPaintPropertyUpdate())
+    return true;
+
   return frame_view.GetLayoutView() &&
          NeedsTreeBuilderContextUpdate(*frame_view.GetLayoutView(), context);
 }
diff --git a/third_party/blink/renderer/core/probe/core_probes.json5 b/third_party/blink/renderer/core/probe/core_probes.json5
index 069fb23f..ef19904db 100644
--- a/third_party/blink/renderer/core/probe/core_probes.json5
+++ b/third_party/blink/renderer/core/probe/core_probes.json5
@@ -215,5 +215,11 @@
         "workerTerminated",
       ]
     },
+    InspectorSession: {
+      probes: [
+        "didStartProvisionalLoad",
+        "didFailProvisionalLoad",
+      ]
+    },
   }
 }
diff --git a/third_party/blink/renderer/core/probe/core_probes.pidl b/third_party/blink/renderer/core/probe/core_probes.pidl
index 09d3ee88..11322da0 100644
--- a/third_party/blink/renderer/core/probe/core_probes.pidl
+++ b/third_party/blink/renderer/core/probe/core_probes.pidl
@@ -111,6 +111,8 @@
   void loadEventFired([Keep] LocalFrame*);
   void frameAttachedToParent([Keep] LocalFrame*);
   void frameDetachedFromParent([Keep] LocalFrame*);
+  void didStartProvisionalLoad([Keep] LocalFrame*);
+  void didFailProvisionalLoad([Keep] LocalFrame*);
   void willCommitLoad([Keep] LocalFrame*, DocumentLoader*);
   void didCommitLoad([Keep] LocalFrame*, DocumentLoader*);
   void didNavigateWithinDocument([Keep] LocalFrame*);
diff --git a/third_party/blink/renderer/devtools/front_end/console/ConsolePinPane.js b/third_party/blink/renderer/devtools/front_end/console/ConsolePinPane.js
index d1cdc0a..0ca624e 100644
--- a/third_party/blink/renderer/devtools/front_end/console/ConsolePinPane.js
+++ b/third_party/blink/renderer/devtools/front_end/console/ConsolePinPane.js
@@ -17,6 +17,14 @@
       this.addPin(expression);
   }
 
+  /**
+   * @override
+   */
+  willHide() {
+    for (const pin of this._pins)
+      pin.setHovered(false);
+  }
+
   _savePins() {
     const toSave = Array.from(this._pins).map(pin => pin.expression());
     this._pinsSetting.set(toSave);
@@ -114,6 +122,18 @@
     /** @type {?UI.TextEditor} */
     this._editor = null;
     this._committedExpression = expression;
+    this._hovered = false;
+    /** @type {?SDK.RemoteObject} */
+    this._lastNode = null;
+
+    this._pinPreview.addEventListener('mouseenter', this.setHovered.bind(this, true), false);
+    this._pinPreview.addEventListener('mouseleave', this.setHovered.bind(this, false), false);
+    this._pinPreview.addEventListener('click', event => {
+      if (this._lastNode) {
+        Common.Revealer.reveal(this._lastNode);
+        event.consume();
+      }
+    }, false);
 
     this._editorPromise = self.runtime.extension(UI.TextEditorFactory).instance().then(factory => {
       this._editor = factory.createEditor({
@@ -148,6 +168,17 @@
   }
 
   /**
+   * @param {boolean} hovered
+   */
+  setHovered(hovered) {
+    if (this._hovered === hovered)
+      return;
+    this._hovered = hovered;
+    if (!hovered && this._lastNode)
+      SDK.OverlayModel.hideDOMNodeHighlight();
+  }
+
+  /**
    * @return {string}
    */
   expression() {
@@ -171,8 +202,11 @@
    * @param {!UI.ContextMenu} contextMenu
    */
   appendToContextMenu(contextMenu) {
-    if (this._lastResult && this._lastResult.object)
+    if (this._lastResult && this._lastResult.object) {
       contextMenu.appendApplicableItems(this._lastResult.object);
+      // Prevent result from being released manually. It will release along with 'console' group.
+      this._lastResult = null;
+    }
   }
 
   /**
@@ -187,10 +221,11 @@
     const timeout = throwOnSideEffect ? 250 : undefined;
     this._lastExecutionContext = UI.context.flavor(SDK.ExecutionContext);
     const {preview, result} = await ObjectUI.JavaScriptREPL.evaluateAndBuildPreview(
-        text, throwOnSideEffect, timeout, !isEditing /* allowErrors */);
+        text, throwOnSideEffect, timeout, !isEditing /* allowErrors */, 'console');
     if (this._lastResult)
       this._lastExecutionContext.runtimeModel.releaseEvaluationResult(this._lastResult);
     this._lastResult = result || null;
+
     const previewText = preview.deepTextContent();
     if (!previewText || previewText !== this._pinPreview.deepTextContent()) {
       this._pinPreview.removeChildren();
@@ -206,6 +241,17 @@
       this._pinPreview.title = previewText;
     }
 
+    let node = null;
+    if (result && result.object && result.object.type === 'object' && result.object.subtype === 'node')
+      node = result.object;
+    if (this._hovered) {
+      if (node)
+        SDK.OverlayModel.highlightObjectAsDOMNode(node);
+      else if (this._lastNode)
+        SDK.OverlayModel.hideDOMNodeHighlight();
+    }
+    this._lastNode = node || null;
+
     const isError = result && result.exceptionDetails && !SDK.RuntimeModel.isSideEffectFailure(result);
     this._pinElement.classList.toggle('error-level', isError);
   }
diff --git a/third_party/blink/renderer/devtools/front_end/console/ConsoleViewMessage.js b/third_party/blink/renderer/devtools/front_end/console/ConsoleViewMessage.js
index 22236b1..7ab961e 100644
--- a/third_party/blink/renderer/devtools/front_end/console/ConsoleViewMessage.js
+++ b/third_party/blink/renderer/devtools/front_end/console/ConsoleViewMessage.js
@@ -45,6 +45,8 @@
     this._repeatCount = 1;
     this._closeGroupDecorationCount = 0;
     this._nestingLevel = nestingLevel;
+    /** @type {!Array<!ObjectUI.ObjectPropertiesSection>} */
+    this._focusableChildren = [];
 
     /** @type {?DataGrid.DataGrid} */
     this._dataGrid = null;
@@ -608,6 +610,7 @@
     const section = new ObjectUI.ObjectPropertiesSection(obj, titleElement, this._linkifier);
     section.element.classList.add('console-view-object-properties-section');
     section.enableContextMenu();
+    this._focusableChildren.push(section);
     return section.element;
   }
 
@@ -1035,6 +1038,15 @@
   }
 
   /**
+   * @return {number}
+   */
+  _focusedChildIndex() {
+    if (!this._focusableChildren.length)
+      return -1;
+    return this._focusableChildren.findIndex(child => child.element.hasFocus());
+  }
+
+  /**
    * @param {!Event} event
    */
   _onKeyDown(event) {
@@ -1050,16 +1062,66 @@
    */
   maybeHandleOnKeyDown(event) {
     // Handle trace expansion.
-    if (this._expandTrace) {
+    const focusedChildIndex = this._focusedChildIndex();
+    const isWrapperFocused = focusedChildIndex === -1;
+    if (this._expandTrace && isWrapperFocused) {
       if ((event.key === 'ArrowLeft' && this._traceExpanded) || (event.key === 'ArrowRight' && !this._traceExpanded)) {
         this._expandTrace(!this._traceExpanded);
         return true;
       }
     }
+    if (!this._focusableChildren.length)
+      return false;
+
+    if (event.key === 'ArrowLeft') {
+      this._element.focus();
+      return true;
+    }
+    if (event.key === 'ArrowRight') {
+      if (isWrapperFocused) {
+        this._focusChild(0);
+        return true;
+      }
+    }
+    if (event.key === 'ArrowUp') {
+      if (focusedChildIndex === 0) {
+        this._element.focus();
+        return true;
+      } else if (focusedChildIndex > 0) {
+        this._focusChild(focusedChildIndex - 1);
+        return true;
+      }
+    }
+    if (event.key === 'ArrowDown') {
+      if (isWrapperFocused) {
+        this._focusChild(0);
+        return true;
+      } else if (focusedChildIndex < this._focusableChildren.length - 1) {
+        this._focusChild(focusedChildIndex + 1);
+        return true;
+      }
+    }
     return false;
   }
 
   /**
+   * @param {number} index
+   */
+  _focusChild(index) {
+    const section = this._focusableChildren[index];
+    if (!section.objectTreeElement().selected)
+      section.objectTreeElement().select();
+    section.focus();
+  }
+
+  focusLastChildOrSelf() {
+    if (this._focusableChildren.length)
+      this._focusChild(this._focusableChildren.length - 1);
+    else if (this._element)
+      this._element.focus();
+  }
+
+  /**
    * @return {!Element}
    */
   contentElement() {
@@ -1557,9 +1619,12 @@
    * @param {!Event} event
    */
   maybeHandleOnKeyDown(event) {
-    if ((event.key === 'ArrowLeft' && !this._collapsed) || (event.key === 'ArrowRight' && this._collapsed)) {
-      this._setCollapsed(!this._collapsed);
-      return true;
+    const focusedChildIndex = this._focusedChildIndex();
+    if (focusedChildIndex === -1) {
+      if ((event.key === 'ArrowLeft' && !this._collapsed) || (event.key === 'ArrowRight' && this._collapsed)) {
+        this._setCollapsed(!this._collapsed);
+        return true;
+      }
     }
     return super.maybeHandleOnKeyDown(event);
   }
diff --git a/third_party/blink/renderer/devtools/front_end/console/ConsoleViewport.js b/third_party/blink/renderer/devtools/front_end/console/ConsoleViewport.js
index d0378a0..f1ea217 100644
--- a/third_party/blink/renderer/devtools/front_end/console/ConsoleViewport.js
+++ b/third_party/blink/renderer/devtools/front_end/console/ConsoleViewport.js
@@ -171,16 +171,21 @@
   _onKeyDown(event) {
     if (UI.isEditing() || !this._itemCount || event.shiftKey)
       return;
+    let isArrowUp = false;
     switch (event.key) {
       case 'ArrowUp':
-        this._virtualSelectedIndex--;
-        if (this._virtualSelectedIndex < 0)
-          this._virtualSelectedIndex = this._itemCount - 1;
+        if (this._virtualSelectedIndex > 0) {
+          isArrowUp = true;
+          this._virtualSelectedIndex--;
+        } else {
+          return;
+        }
         break;
       case 'ArrowDown':
-        this._virtualSelectedIndex++;
-        if (this._virtualSelectedIndex >= this._itemCount)
-          this._virtualSelectedIndex = 0;
+        if (this._virtualSelectedIndex < this._itemCount - 1)
+          this._virtualSelectedIndex++;
+        else
+          return;
         break;
       case 'Home':
         this._virtualSelectedIndex = 0;
@@ -193,20 +198,27 @@
     }
     event.consume(true);
     this.scrollItemIntoView(this._virtualSelectedIndex);
-    this._updateFocusedItem();
+    this._updateFocusedItem(isArrowUp);
   }
 
-  _updateFocusedItem() {
+  /**
+   * @param {boolean=} focusLastChild
+   */
+  _updateFocusedItem(focusLastChild) {
     const selectedElement = this.renderedElementAt(this._virtualSelectedIndex);
     const changed = this._lastSelectedElement !== selectedElement;
     const containerHasFocus = this._contentElement === this.element.ownerDocument.deepActiveElement();
     if (this._lastSelectedElement && changed)
       this._lastSelectedElement.classList.remove('console-selected');
-    if (selectedElement && (changed || containerHasFocus)) {
+    if (selectedElement && (changed || containerHasFocus) && this.element.hasFocus()) {
       selectedElement.classList.add('console-selected');
       // Do not focus the message if something within holds focus (e.g. object).
-      if (!selectedElement.hasFocus())
-        focusWithoutScroll(selectedElement);
+      if (!selectedElement.hasFocus()) {
+        if (focusLastChild)
+          this._renderedItems[this._virtualSelectedIndex - this._firstActiveIndex].focusLastChildOrSelf();
+        else
+          focusWithoutScroll(selectedElement);
+      }
     }
     if (this._itemCount && !this._contentElement.hasFocus())
       this._contentElement.tabIndex = 0;
diff --git a/third_party/blink/renderer/devtools/front_end/console_test_runner/ConsoleTestRunner.js b/third_party/blink/renderer/devtools/front_end/console_test_runner/ConsoleTestRunner.js
index dc28c73c..aea6002 100644
--- a/third_party/blink/renderer/devtools/front_end/console_test_runner/ConsoleTestRunner.js
+++ b/third_party/blink/renderer/devtools/front_end/console_test_runner/ConsoleTestRunner.js
@@ -444,6 +444,13 @@
 /**
  * @return {!Promise}
  */
+ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise = function() {
+  return new Promise(resolve => ConsoleTestRunner.waitForRemoteObjectsConsoleMessages(resolve));
+};
+
+/**
+ * @return {!Promise}
+ */
 ConsoleTestRunner.waitUntilConsoleEditorLoaded = function() {
   let fulfill;
   const promise = new Promise(x => (fulfill = x));
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/JavaScriptREPL.js b/third_party/blink/renderer/devtools/front_end/object_ui/JavaScriptREPL.js
index bdec0d7..f0b73e6 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/JavaScriptREPL.js
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/JavaScriptREPL.js
@@ -47,9 +47,10 @@
    * @param {boolean} throwOnSideEffect
    * @param {number=} timeout
    * @param {boolean=} allowErrors
+   * @param {string=} objectGroup
    * @return {!Promise<!{preview: !DocumentFragment, result: ?SDK.RuntimeModel.EvaluationResult}>}
    */
-  static async evaluateAndBuildPreview(text, throwOnSideEffect, timeout, allowErrors) {
+  static async evaluateAndBuildPreview(text, throwOnSideEffect, timeout, allowErrors, objectGroup) {
     const executionContext = UI.context.flavor(SDK.ExecutionContext);
     const isTextLong = text.length > ObjectUI.JavaScriptREPL._MaxLengthForEvaluation;
     if (!text || !executionContext || (throwOnSideEffect && isTextLong))
@@ -61,7 +62,8 @@
       generatePreview: true,
       includeCommandLineAPI: true,
       throwOnSideEffect: throwOnSideEffect,
-      timeout: timeout
+      timeout: timeout,
+      objectGroup: objectGroup
     };
     const result = await executionContext.evaluate(
         options, false /* userGesture */, wrappedResult.preprocessed /* awaitPromise */);
diff --git a/third_party/blink/renderer/modules/filesystem/choose_file_system_entries_options.idl b/third_party/blink/renderer/modules/filesystem/choose_file_system_entries_options.idl
index 8e50e6f..d1df3325 100644
--- a/third_party/blink/renderer/modules/filesystem/choose_file_system_entries_options.idl
+++ b/third_party/blink/renderer/modules/filesystem/choose_file_system_entries_options.idl
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#enumdef-choosefilesystementriestype
 enum ChooseFileSystemEntriesType { "openFile", "saveFile", "openDirectory" };
 
+// https://wicg.github.io/writable-files/#dictdef-choosefilesystementriesoptions
 dictionary ChooseFileSystemEntriesOptions {
     ChooseFileSystemEntriesType type = "openFile";
     boolean multiple = false;
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_base_handle.idl b/third_party/blink/renderer/modules/filesystem/file_system_base_handle.idl
index 0899593..20008ad4 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_base_handle.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_base_handle.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#filesystemhandle
 [
     RuntimeEnabled=WritableFiles,
     NoInterfaceObject
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_directory_handle.idl b/third_party/blink/renderer/modules/filesystem/file_system_directory_handle.idl
index 4475c46..5e2309e 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_directory_handle.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_directory_handle.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#filesystemdirectoryhandle
 [
     RuntimeEnabled=WritableFiles
 ] interface FileSystemDirectoryHandle : FileSystemBaseHandle {
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_file_handle.idl b/third_party/blink/renderer/modules/filesystem/file_system_file_handle.idl
index 7b554909..74f5582 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_file_handle.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_file_handle.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#filesystemfilehandle
 [
     RuntimeEnabled=WritableFiles
 ] interface FileSystemFileHandle : FileSystemBaseHandle {
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_get_directory_options.idl b/third_party/blink/renderer/modules/filesystem/file_system_get_directory_options.idl
index b430b67..246e8108 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_get_directory_options.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_get_directory_options.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#dictdef-filesystemgetdirectoryoptions
 dictionary FileSystemGetDirectoryOptions {
   boolean create = false;
 };
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_get_file_options.idl b/third_party/blink/renderer/modules/filesystem/file_system_get_file_options.idl
index ec0f2d24..19c7978f 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_get_file_options.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_get_file_options.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#dictdef-filesystemgetfileoptions
 dictionary FileSystemGetFileOptions {
   boolean create = false;
 };
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_writer.idl b/third_party/blink/renderer/modules/filesystem/file_system_writer.idl
index b3e225cb..b1cf941 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_writer.idl
+++ b/third_party/blink/renderer/modules/filesystem/file_system_writer.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#filesystemwriter
 [
     NoInterfaceObject,
     RuntimeEnabled=WritableFiles
diff --git a/third_party/blink/renderer/modules/filesystem/get_system_directory_options.idl b/third_party/blink/renderer/modules/filesystem/get_system_directory_options.idl
index 319adbc..831ac356 100644
--- a/third_party/blink/renderer/modules/filesystem/get_system_directory_options.idl
+++ b/third_party/blink/renderer/modules/filesystem/get_system_directory_options.idl
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
+// https://wicg.github.io/writable-files/#enumdef-systemdirectorytype
 enum SystemDirectoryType {
   "sandbox"
 };
 
+// https://wicg.github.io/writable-files/#dictdef-getsystemdirectoryoptions
 dictionary GetSystemDirectoryOptions {
   required SystemDirectoryType type;
 };
diff --git a/third_party/blink/renderer/modules/filesystem/window_file_system.idl b/third_party/blink/renderer/modules/filesystem/window_file_system.idl
index ed10797d..935f48bc 100644
--- a/third_party/blink/renderer/modules/filesystem/window_file_system.idl
+++ b/third_party/blink/renderer/modules/filesystem/window_file_system.idl
@@ -38,8 +38,7 @@
     [RuntimeEnabled=FileSystem] void webkitResolveLocalFileSystemURL(DOMString url,
             EntryCallback successCallback, optional ErrorCallback? errorCallback);
 
-    // https://github.com/WICG/writable-files/blob/master/EXPLAINER.md
-    // TODO(crbug.com/878581): This needs some kind of options dictionary.
+    // https://wicg.github.io/writable-files/#api-choosefilesystementries
     [RuntimeEnabled=WritableFiles, CallWith=ScriptState, SecureContext]
     Promise<(FileSystemBaseHandle or sequence<FileSystemBaseHandle>)>
         chooseFileSystemEntries(optional ChooseFileSystemEntriesOptions options);
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
index cc6c9eac..9e05d3b 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
@@ -538,8 +538,7 @@
 
   overlay_enclosure_ = new MediaControlOverlayEnclosureElement(*this);
 
-  if (RuntimeEnabledFeatures::MediaControlsOverlayPlayButtonEnabled() ||
-      IsModern()) {
+  if (RuntimeEnabledFeatures::MediaControlsOverlayPlayButtonEnabled()) {
     overlay_play_button_ = new MediaControlOverlayPlayButtonElement(*this);
 
     if (!IsModern())
@@ -650,7 +649,7 @@
     if (display_cutout_fullscreen_button_)
       panel_->ParserAppendChild(display_cutout_fullscreen_button_);
 
-    panel_->ParserAppendChild(overlay_play_button_);
+    MaybeParserAppendChild(panel_, overlay_play_button_);
     panel_->ParserAppendChild(media_button_panel_);
     button_panel = media_button_panel_;
   }
@@ -770,18 +769,35 @@
   // If we are in the "no-source" state we should show the overflow menu on a
   // video element.
   if (IsModern()) {
+    bool updated = false;
+
     if (state == kNoSource) {
-      // Check if the overflow menu has the "disabled" attribute set so we avoid
-      // unnecessarily resetting it.
+      // Check if the play button or overflow menu has the "disabled" attribute
+      // set so we avoid unnecessarily resetting it.
+      if (!play_button_->hasAttribute(HTMLNames::disabledAttr)) {
+        play_button_->setAttribute(HTMLNames::disabledAttr, "");
+        updated = true;
+      }
+
       if (ShouldShowVideoControls() &&
           !overflow_menu_->hasAttribute(HTMLNames::disabledAttr)) {
         overflow_menu_->setAttribute(HTMLNames::disabledAttr, "");
-        UpdateOverflowMenuWanted();
+        updated = true;
       }
-    } else if (overflow_menu_->hasAttribute(HTMLNames::disabledAttr)) {
-      overflow_menu_->removeAttribute(HTMLNames::disabledAttr);
-      UpdateOverflowMenuWanted();
+    } else {
+      if (play_button_->hasAttribute(HTMLNames::disabledAttr)) {
+        play_button_->removeAttribute(HTMLNames::disabledAttr);
+        updated = true;
+      }
+
+      if (overflow_menu_->hasAttribute(HTMLNames::disabledAttr)) {
+        overflow_menu_->removeAttribute(HTMLNames::disabledAttr);
+        updated = true;
+      }
     }
+
+    if (updated)
+      UpdateOverflowMenuWanted();
   }
 }
 
@@ -1265,13 +1281,16 @@
   // room and hide the overlay play button if there is not enough room.
   if (ShouldShowVideoControls()) {
     // Allocate vertical room for overlay play button if necessary.
-    WebSize overlay_play_button_size = overlay_play_button_->GetSizeOrDefault();
-    if (controls_size.height >= overlay_play_button_size.height &&
-        controls_size.width >= kModernMinWidthForOverlayPlayButton) {
-      overlay_play_button_->SetDoesFit(true);
-      controls_size.height -= overlay_play_button_size.height;
-    } else {
-      overlay_play_button_->SetDoesFit(false);
+    if (overlay_play_button_) {
+      WebSize overlay_play_button_size =
+          overlay_play_button_->GetSizeOrDefault();
+      if (controls_size.height >= overlay_play_button_size.height &&
+          controls_size.width >= kModernMinWidthForOverlayPlayButton) {
+        overlay_play_button_->SetDoesFit(true);
+        controls_size.height -= overlay_play_button_size.height;
+      } else {
+        overlay_play_button_->SetDoesFit(false);
+      }
     }
 
     controls_size.width -= kModernControlsVideoButtonPadding;
@@ -1288,7 +1307,8 @@
     }
 
     // If we cannot show the overlay play button, show the normal one.
-    play_button_->SetIsWanted(!overlay_play_button_->DoesFit());
+    play_button_->SetIsWanted(!overlay_play_button_ ||
+                              !overlay_play_button_->DoesFit());
   } else {
     controls_size.width -= kModernControlsAudioButtonPadding;
 
@@ -1509,7 +1529,7 @@
       !IsSpatialNavigationEnabled(GetDocument().GetFrame())) {
     const String& key = ToKeyboardEvent(event).key();
     if (key == "Enter" || ToKeyboardEvent(event).keyCode() == ' ') {
-      if (IsModern()) {
+      if (IsModern() && overlay_play_button_) {
         overlay_play_button_->OnMediaKeyboardEvent(&event);
       } else {
         play_button_->OnMediaKeyboardEvent(&event);
diff --git a/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css b/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
index 0008b69..7f114af 100644
--- a/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
+++ b/third_party/blink/renderer/modules/media_controls/resources/modernMediaControls.css
@@ -243,20 +243,20 @@
 
 video::-webkit-media-controls.sizing-small div[pseudo="-internal-media-controls-button-panel" i] {
   height: 48px;
-  line-height: 44px;
+  line-height: 48px;
   padding: 0 0 0 16px;
 }
 
 video::-webkit-media-controls.sizing-medium div[pseudo="-internal-media-controls-button-panel" i] {
   height: 64px;
-  line-height: 60px;
+  line-height: 64px;
   padding: 0 16px 0 32px;
 }
 
 /* TODO(https://crbug.com/857120): remove these rules and the sizing-large CSS class. */
 video::-webkit-media-controls.sizing-large div[pseudo="-internal-media-controls-button-panel" i] {
   height: 64px;
-  line-height: 60px;
+  line-height: 64px;
   padding: 0 16px 0 32px;
 }
 
@@ -309,8 +309,10 @@
 }
 
 audio::-webkit-media-controls-mute-button:disabled,
+audio::-webkit-media-controls-play-button:disabled,
 video::-internal-media-controls-overflow-button:disabled,
 video::-webkit-media-controls-mute-button:disabled,
+video::-webkit-media-controls-play-button:disabled,
 video::-webkit-media-controls-fullscreen-button:disabled {
   background-color: initial;
   opacity: 0.3;
@@ -424,6 +426,13 @@
   background-image: -webkit-image-set(url(ic_pause_white.svg) 1x);
 }
 
+video::-webkit-media-controls:not(.audio-only) input[pseudo="-webkit-media-controls-play-button"] {
+  /* Undo the extra 16px of left padding on the button panel. We only want that
+   * extra padding when the current time is the leftmost item, and not when the
+   * play button is leftmost. */
+  margin-left: -16px;
+}
+
 /**
  * Timeline
  */
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 777db98..8c0b114 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -804,6 +804,7 @@
     "graphics/animation_worklet_mutator_dispatcher_impl.cc",
     "graphics/animation_worklet_mutator_dispatcher_impl.h",
     "graphics/animation_worklet_mutators_state.h",
+    "graphics/apply_viewport_changes.h",
     "graphics/begin_frame_provider.cc",
     "graphics/begin_frame_provider.h",
     "graphics/bitmap_image.cc",
diff --git a/third_party/blink/renderer/platform/graphics/apply_viewport_changes.h b/third_party/blink/renderer/platform/graphics/apply_viewport_changes.h
new file mode 100644
index 0000000..c51a13b
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/apply_viewport_changes.h
@@ -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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_APPLY_VIEWPORT_CHANGES_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_APPLY_VIEWPORT_CHANGES_H_
+
+#include "cc/trees/layer_tree_host_client.h"
+
+namespace blink {
+
+// Allow us to use the Args struct for ApplyViewportChanges method within Blink
+// core.
+using ApplyViewportChangesArgs = cc::ApplyViewportChangesArgs;
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_APPLY_VIEWPORT_CHANGES_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
index e7bd01d..9ee3a58e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
@@ -60,8 +60,8 @@
 
 size_t ClipPaintPropertyNode::CacheMemoryUsageInBytes() const {
   size_t total_bytes = sizeof(*this);
-  if (geometry_mapper_clip_cache_)
-    total_bytes += sizeof(*geometry_mapper_clip_cache_);
+  if (clip_cache_)
+    total_bytes += sizeof(*clip_cache_);
   if (Parent())
     total_bytes += Parent()->CacheMemoryUsageInBytes();
   return total_bytes;
diff --git a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
index 16fe0724..503c83d 100644
--- a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
@@ -76,8 +76,8 @@
       return parent_changed;
 
     DCHECK(!IsParentAlias()) << "Changed the state of an alias node.";
-    SetChanged();
     state_ = std::move(state);
+    SetChanged();
     return true;
   }
 
@@ -129,11 +129,24 @@
   size_t CacheMemoryUsageInBytes() const;
 
  private:
+  friend class PaintPropertyNode<ClipPaintPropertyNode>;
+
   ClipPaintPropertyNode(const ClipPaintPropertyNode* parent,
                         State&& state,
                         bool is_parent_alias)
       : PaintPropertyNode(parent, is_parent_alias), state_(std::move(state)) {}
 
+  void SetChanged() {
+    // TODO(crbug.com/814815): This is a workaround of the bug. When the bug is
+    // fixed, change the following condition to
+    //   DCHECK(!clip_cache_ || !clip_cache_->IsValid());
+    if (clip_cache_ && clip_cache_->IsValid()) {
+      DLOG(WARNING) << "Clip tree changed without invalidating the cache.";
+      GeometryMapperClipCache::ClearCache();
+    }
+    PaintPropertyNode::SetChanged();
+  }
+
   // For access to GetClipCache();
   friend class GeometryMapper;
   friend class GeometryMapperTest;
@@ -143,13 +156,13 @@
   }
 
   GeometryMapperClipCache& GetClipCache() {
-    if (!geometry_mapper_clip_cache_)
-      geometry_mapper_clip_cache_.reset(new GeometryMapperClipCache());
-    return *geometry_mapper_clip_cache_.get();
+    if (!clip_cache_)
+      clip_cache_.reset(new GeometryMapperClipCache());
+    return *clip_cache_.get();
   }
 
   State state_;
-  std::unique_ptr<GeometryMapperClipCache> geometry_mapper_clip_cache_;
+  std::unique_ptr<GeometryMapperClipCache> clip_cache_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index 73af730..ac03328e9 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -82,8 +82,8 @@
       return parent_changed;
 
     DCHECK(!IsParentAlias()) << "Changed the state of an alias node.";
-    SetChanged();
     state_ = std::move(state);
+    SetChanged();
     return true;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.cc
index ca1a308..374c7b4 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.cc
@@ -20,6 +20,10 @@
   g_clip_cache_generation++;
 }
 
+bool GeometryMapperClipCache::IsValid() const {
+  return cache_generation_ == g_clip_cache_generation;
+}
+
 void GeometryMapperClipCache::InvalidateCacheIfNeeded() {
   if (cache_generation_ != g_clip_cache_generation) {
     clip_cache_.clear();
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
index 35215be..b080c99 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
@@ -52,6 +52,7 @@
   void SetCachedClip(const ClipAndTransform&, const FloatClipRect&);
 
   static void ClearCache();
+  bool IsValid() const;
 
  private:
   struct ClipCacheEntry {
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.cc
index cad4fc4..b5dd478 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.cc
@@ -17,15 +17,8 @@
   s_global_generation++;
 }
 
-// Computes flatten(m) ^ -1, return true if the inversion succeeded.
-static bool InverseProjection(const TransformationMatrix& m,
-                              TransformationMatrix& out) {
-  out = m;
-  out.FlattenTo2d();
-  if (!out.IsInvertible())
-    return false;
-  out = out.Inverse();
-  return true;
+bool GeometryMapperTransformCache::IsValid() const {
+  return cache_generation_ == s_global_generation;
 }
 
 void GeometryMapperTransformCache::Update(
@@ -120,8 +113,13 @@
   if (node.FlattensInheritedTransform())
     screen_transform_->to_screen.FlattenTo2d();
   screen_transform_->to_screen.Multiply(local);
-  screen_transform_->projection_from_screen_is_valid = InverseProjection(
-      screen_transform_->to_screen, screen_transform_->projection_from_screen);
+
+  auto to_screen_flattened = screen_transform_->to_screen;
+  to_screen_flattened.FlattenTo2d();
+  screen_transform_->projection_from_screen_is_valid =
+      to_screen_flattened.IsInvertible();
+  if (screen_transform_->projection_from_screen_is_valid)
+    screen_transform_->projection_from_screen = to_screen_flattened.Inverse();
 }
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h
index 94fd1f0..2ceeeb8e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h
@@ -22,6 +22,7 @@
   GeometryMapperTransformCache() = default;
 
   static void ClearCache();
+  bool IsValid() const;
 
   void UpdateIfNeeded(const TransformPaintPropertyNode& node) {
     if (cache_generation_ != s_global_generation)
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
index 5760dca..97aeb40 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
@@ -122,8 +122,8 @@
     if (parent == parent_)
       return false;
 
-    SetChanged();
     parent_ = parent;
+    static_cast<NodeType*>(this)->SetChanged();
     return true;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
index c0cecac..35f8337 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
@@ -91,9 +91,9 @@
     if (state == state_)
       return parent_changed;
 
-    SetChanged();
     state_ = std::move(state);
     Validate();
+    SetChanged();
     return true;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index ee5ac86..cfaee4d 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/platform/geometry/float_point_3d.h"
 #include "third_party/blink/renderer/platform/graphics/compositing_reasons.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
+#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
@@ -97,8 +98,8 @@
       return parent_changed;
 
     DCHECK(!IsParentAlias()) << "Changed the state of an alias node.";
-    SetChanged();
     state_ = std::move(state);
+    SetChanged();
     CheckAndUpdateIsIdentityOr2DTranslation();
     Validate();
     return true;
@@ -194,6 +195,8 @@
   size_t CacheMemoryUsageInBytes() const;
 
  private:
+  friend class PaintPropertyNode<TransformPaintPropertyNode>;
+
   TransformPaintPropertyNode(const TransformPaintPropertyNode* parent,
                              State&& state,
                              bool is_parent_alias)
@@ -226,6 +229,18 @@
 #endif
   }
 
+  void SetChanged() {
+    // TODO(crbug.com/814815): This is a workaround of the bug. When the bug is
+    // fixed, change the following condition to
+    //   DCHECK(!transform_cache_ || !transform_cache_->IsValid());
+    if (transform_cache_ && transform_cache_->IsValid()) {
+      DLOG(WARNING) << "Transform tree changed without invalidating the cache.";
+      GeometryMapperTransformCache::ClearCache();
+      GeometryMapperClipCache::ClearCache();
+    }
+    PaintPropertyNode::SetChanged();
+  }
+
   // For access to GetTransformCache() and SetCachedTransform.
   friend class GeometryMapper;
   friend class GeometryMapperTest;
@@ -239,6 +254,7 @@
     return *transform_cache_;
   }
   void UpdateScreenTransform() const {
+    DCHECK(transform_cache_);
     transform_cache_->UpdateScreenTransform(*this);
   }
 
diff --git a/third_party/blink/tools/blinkpy/common/path_finder.py b/third_party/blink/tools/blinkpy/common/path_finder.py
index 5fc1f4a..4a25e41b 100644
--- a/third_party/blink/tools/blinkpy/common/path_finder.py
+++ b/third_party/blink/tools/blinkpy/common/path_finder.py
@@ -95,6 +95,18 @@
         sys.path.append(path)
 
 
+def _does_blink_web_tests_exist():
+    return os.path.exists(os.path.join(get_chromium_src_dir(), 'third_party',
+                                       'blink', 'web_tests'))
+
+
+TESTS_IN_BLINK = _does_blink_web_tests_exist()
+# LayoutTests / web_tests path relative to the repository root.
+# Path separators are always '/', and this contains the trailing '/'.
+RELATIVE_WEB_TESTS = ('third_party/blink/web_tests/' if TESTS_IN_BLINK
+                      else 'third_party/WebKit/LayoutTests/')
+WEB_TESTS_LAST_COMPONENT = 'web_tests' if TESTS_IN_BLINK else 'LayoutTests'
+
 class PathFinder(object):
 
     def __init__(self, filesystem, sys_path=None, env_path=None):
@@ -107,7 +119,10 @@
     def chromium_base(self):
         return self._filesystem.dirname(self._filesystem.dirname(self._blink_base()))
 
+    # TODO(tkent): Rename this to web_tests_dir().
     def layout_tests_dir(self):
+        if TESTS_IN_BLINK:
+            return self.path_from_chromium_base('third_party', 'blink', 'web_tests')
         return self.path_from_chromium_base('third_party', 'WebKit', 'LayoutTests')
 
     def perf_tests_dir(self):
diff --git a/third_party/blink/tools/blinkpy/common/path_finder_unittest.py b/third_party/blink/tools/blinkpy/common/path_finder_unittest.py
index a10545f..edc6ff0 100644
--- a/third_party/blink/tools/blinkpy/common/path_finder_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/path_finder_unittest.py
@@ -5,6 +5,8 @@
 import unittest
 
 from blinkpy.common.path_finder import PathFinder
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
+from blinkpy.common.path_finder import TESTS_IN_BLINK
 from blinkpy.common.system.filesystem_mock import MockFileSystem
 
 
@@ -24,7 +26,7 @@
         finder = PathFinder(MockFileSystem())
         self.assertEqual(
             finder.layout_tests_dir(),
-            '/mock-checkout/third_party/WebKit/LayoutTests')
+            '/mock-checkout/' + RELATIVE_WEB_TESTS[:-1])
 
     def test_layout_tests_dir_with_backslash_sep(self):
         filesystem = MockFileSystem()
@@ -32,9 +34,14 @@
         filesystem.path_to_module = lambda _: (
             'C:\\mock-checkout\\third_party\\blink\\tools\\blinkpy\\foo.py')
         finder = PathFinder(filesystem)
-        self.assertEqual(
-            finder.layout_tests_dir(),
-            'C:\\mock-checkout\\third_party\\WebKit\\LayoutTests')
+        if TESTS_IN_BLINK:
+            self.assertEqual(
+                finder.layout_tests_dir(),
+                'C:\\mock-checkout\\third_party\\blink\\web_tests')
+        else:
+            self.assertEqual(
+                finder.layout_tests_dir(),
+                'C:\\mock-checkout\\third_party\\WebKit\\LayoutTests')
 
     def test_perf_tests_dir(self):
         finder = PathFinder(MockFileSystem())
@@ -46,7 +53,7 @@
         finder = PathFinder(MockFileSystem())
         self.assertEqual(
             finder.path_from_layout_tests('external', 'wpt'),
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt')
+            '/mock-checkout/' + RELATIVE_WEB_TESTS + 'external/wpt')
 
     def test_depot_tools_base_not_found(self):
         filesystem = MockFileSystem()
diff --git a/third_party/blink/tools/blinkpy/style/checker.py b/third_party/blink/tools/blinkpy/style/checker.py
index 8ca7b68c..37dd7b9 100644
--- a/third_party/blink/tools/blinkpy/style/checker.py
+++ b/third_party/blink/tools/blinkpy/style/checker.py
@@ -190,7 +190,9 @@
 # This list should be in addition to files with FileType.NONE.  Files
 # with FileType.NONE are automatically skipped without warning.
 _SKIPPED_FILES_WITHOUT_WARNING = [
+    # TODO(tkent): Remove the item for LayoutTests.
     'LayoutTests' + os.path.sep,
+    'web_tests' + os.path.sep,
     'third_party' + os.path.sep + 'blink' + os.path.sep + 'renderer' + os.path.sep + 'devtools' + os.path.sep + 'protocol.json',
 ]
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/queries.py b/third_party/blink/tools/blinkpy/tool/commands/queries.py
index 1f1c888..dacafd20 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/queries.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/queries.py
@@ -33,6 +33,7 @@
 
 from optparse import make_option
 
+from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
 from blinkpy.common.system.crash_logs import CrashLogs
 from blinkpy.tool.commands.command import Command
 from blinkpy.web_tests.models.test_expectations import TestExpectations
@@ -107,7 +108,7 @@
             layout_tests_dir = default_port.layout_tests_dir()
             for file in files:
                 if file.startswith(layout_tests_dir):
-                    file = file.replace(layout_tests_dir, 'LayoutTests')
+                    file = file.replace(layout_tests_dir, WEB_TESTS_LAST_COMPONENT)
                 print file
             return
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/queries_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/queries_unittest.py
index 9d72145..8d19afd 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/queries_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/queries_unittest.py
@@ -30,6 +30,7 @@
 import optparse
 import unittest
 
+from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
 from blinkpy.common.system.output_capture import OutputCapture
 from blinkpy.tool.commands.queries import PrintBaselines, PrintExpectations
 from blinkpy.tool.mock_tool import MockBlinkTool
@@ -104,10 +105,10 @@
 
     def test_paths(self):
         self.run_test([],
-                      ('LayoutTests/TestExpectations\n'
-                       'LayoutTests/NeverFixTests\n'
-                       'LayoutTests/StaleTestExpectations\n'
-                       'LayoutTests/SlowTests\n'),
+                      (WEB_TESTS_LAST_COMPONENT + '/TestExpectations\n' +
+                       WEB_TESTS_LAST_COMPONENT + '/NeverFixTests\n' +
+                       WEB_TESTS_LAST_COMPONENT + '/StaleTestExpectations\n' +
+                       WEB_TESTS_LAST_COMPONENT + '/SlowTests\n'),
                       paths=True)
 
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
index 00547b94..e3723570 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
@@ -32,6 +32,7 @@
 import optparse
 import re
 
+from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
 from blinkpy.common.memoized import memoized
 from blinkpy.common.net.buildbot import Build
 from blinkpy.tool.commands.command import Command
@@ -435,7 +436,7 @@
 
     def unstaged_baselines(self):
         """Returns absolute paths for unstaged (including untracked) baselines."""
-        baseline_re = re.compile(r'.*[\\/]LayoutTests[\\/].*-expected\.(txt|png|wav)$')
+        baseline_re = re.compile(r'.*[\\/]' + WEB_TESTS_LAST_COMPONENT + r'[\\/].*-expected\.(txt|png|wav)$')
         unstaged_changes = self._tool.git().unstaged_changes()
         return sorted(self._tool.git().absolute_path(path) for path in unstaged_changes if re.match(baseline_re, path))
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
index e3d0e23..79fd765 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
@@ -11,6 +11,7 @@
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
 from blinkpy.common.net.layout_test_results import LayoutTestResults
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.log_testing import LoggingTestCase
 from blinkpy.tool.commands.rebaseline import TestBaselineSet
 from blinkpy.tool.commands.rebaseline_cl import RebaselineCL
@@ -36,8 +37,8 @@
 
         git = MockGit(filesystem=self.tool.filesystem, executive=self.tool.executive)
         git.changed_files = lambda **_: [
-            'third_party/WebKit/LayoutTests/one/text-fail.html',
-            'third_party/WebKit/LayoutTests/one/flaky-fail.html',
+            RELATIVE_WEB_TESTS + 'one/text-fail.html',
+            RELATIVE_WEB_TESTS + 'one/flaky-fail.html',
         ]
         self.tool.git = lambda: git
 
@@ -170,13 +171,13 @@
     def test_execute_with_unstaged_baselines_aborts(self):
         git = self.tool.git()
         git.unstaged_changes = lambda: {
-            'third_party/WebKit/LayoutTests/my-test-expected.txt': '?'
+            RELATIVE_WEB_TESTS + 'my-test-expected.txt': '?'
         }
         exit_code = self.command.execute(self.command_options(), [], self.tool)
         self.assertEqual(exit_code, 1)
         self.assertLog([
             'ERROR: Aborting: there are unstaged baselines:\n',
-            'ERROR:   /mock-checkout/third_party/WebKit/LayoutTests/'
+            'ERROR:   /mock-checkout/' + RELATIVE_WEB_TESTS +
             'my-test-expected.txt\n',
         ])
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
index be4b1fc..7022cc80 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
@@ -8,6 +8,7 @@
 
 from blinkpy.common.net.buildbot import Build
 from blinkpy.common.net.layout_test_results import LayoutTestResults
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockExecutive
 from blinkpy.tool.commands.rebaseline import (
     AbstractParallelRebaselineCommand, Rebaseline, TestBaselineSet
@@ -138,17 +139,17 @@
     def test_unstaged_baselines(self):
         git = self.tool.git()
         git.unstaged_changes = lambda: {
-            'third_party/WebKit/LayoutTests/x/foo-expected.txt': 'M',
-            'third_party/WebKit/LayoutTests/x/foo-expected.something': '?',
-            'third_party/WebKit/LayoutTests/x/foo-expected.png': '?',
-            'third_party/WebKit/LayoutTests/x/foo.html': 'M',
+            RELATIVE_WEB_TESTS + 'x/foo-expected.txt': 'M',
+            RELATIVE_WEB_TESTS + 'x/foo-expected.something': '?',
+            RELATIVE_WEB_TESTS + 'x/foo-expected.png': '?',
+            RELATIVE_WEB_TESTS + 'x/foo.html': 'M',
             'docs/something.md': '?',
         }
         self.assertEqual(
             self.command.unstaged_baselines(),
             [
-                '/mock-checkout/third_party/WebKit/LayoutTests/x/foo-expected.png',
-                '/mock-checkout/third_party/WebKit/LayoutTests/x/foo-expected.txt',
+                '/mock-checkout/' + RELATIVE_WEB_TESTS + 'x/foo-expected.png',
+                '/mock-checkout/' + RELATIVE_WEB_TESTS + 'x/foo-expected.txt',
             ])
 
 
diff --git a/third_party/blink/tools/blinkpy/w3c/chromium_commit_unittest.py b/third_party/blink/tools/blinkpy/w3c/chromium_commit_unittest.py
index 69bd79b8a..5a2a903 100644
--- a/third_party/blink/tools/blinkpy/w3c/chromium_commit_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/chromium_commit_unittest.py
@@ -5,11 +5,12 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.executive_mock import MockExecutive, mock_git_commands
 from blinkpy.w3c.chromium_commit import ChromiumCommit
 
-CHROMIUM_WPT_DIR = 'third_party/WebKit/LayoutTests/external/wpt/'
+CHROMIUM_WPT_DIR = RELATIVE_WEB_TESTS + 'external/wpt/'
 
 
 class ChromiumCommitTest(unittest.TestCase):
diff --git a/third_party/blink/tools/blinkpy/w3c/chromium_exportable_commits_unittest.py b/third_party/blink/tools/blinkpy/w3c/chromium_exportable_commits_unittest.py
index af84a4a0..1f85a807 100644
--- a/third_party/blink/tools/blinkpy/w3c/chromium_exportable_commits_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/chromium_exportable_commits_unittest.py
@@ -5,6 +5,7 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import mock_git_commands
 from blinkpy.w3c.chromium_commit import ChromiumCommit
 from blinkpy.w3c.chromium_commit_mock import MockChromiumCommit
@@ -30,8 +31,8 @@
             'rev-parse': 'add087a97844f4b9e307d9a216940582d96db306',
             'crrev-parse': 'add087a97844f4b9e307d9a216940582d96db306',
             'diff': 'fake diff',
-            'diff-tree': 'third_party/WebKit/LayoutTests/external/wpt/some\n'
-                         'third_party/WebKit/LayoutTests/external/wpt/files',
+            'diff-tree': (RELATIVE_WEB_TESTS + 'external/wpt/some\n' +
+                          RELATIVE_WEB_TESTS + 'external/wpt/files'),
             'format-patch': 'hey I\'m a patch',
             'footers': 'cr-rev-position',
         }, strict=True)
@@ -43,19 +44,19 @@
         self.assertEqual(host.executive.calls, [
             ['git', 'rev-parse', '--show-toplevel'],
             ['git', 'rev-list', 'beefcafe..HEAD', '--reverse', '--',
-             'add087a97844f4b9e307d9a216940582d96db306/third_party/WebKit/LayoutTests/external/wpt/'],
+             'add087a97844f4b9e307d9a216940582d96db306/' + RELATIVE_WEB_TESTS + 'external/wpt/'],
             ['git', 'footers', '--position', 'add087a97844f4b9e307d9a216940582d96db306'],
             ['git', 'show', '--format=%B', '--no-patch', 'add087a97844f4b9e307d9a216940582d96db306'],
             ['git', 'diff-tree', '--name-only', '--no-commit-id', '-r', 'add087a97844f4b9e307d9a216940582d96db306', '--',
-             '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt'],
+             '/mock-checkout/' + RELATIVE_WEB_TESTS + 'external/wpt'],
             ['git', 'format-patch', '-1', '--stdout', 'add087a97844f4b9e307d9a216940582d96db306', '--',
-             'third_party/WebKit/LayoutTests/external/wpt/some', 'third_party/WebKit/LayoutTests/external/wpt/files'],
+             RELATIVE_WEB_TESTS + 'external/wpt/some', RELATIVE_WEB_TESTS + 'external/wpt/files'],
         ])
 
     def test_exportable_commits_since_require_clean_by_default(self):
         host = MockHost()
         host.executive = mock_git_commands({
-            'diff-tree': 'third_party/WebKit/LayoutTests/external/wpt/some_files',
+            'diff-tree': RELATIVE_WEB_TESTS + 'external/wpt/some_files',
             'footers': 'cr-rev-position',
             'format-patch': 'hey I\'m a patch',
             'rev-list': 'add087a97844f4b9e307d9a216940582d96db306\n'
@@ -75,7 +76,7 @@
     def test_exportable_commits_since_not_require_clean(self):
         host = MockHost()
         host.executive = mock_git_commands({
-            'diff-tree': 'third_party/WebKit/LayoutTests/external/wpt/some_files',
+            'diff-tree': RELATIVE_WEB_TESTS + 'external/wpt/some_files',
             'footers': 'cr-rev-position',
             'format-patch': 'hey I\'m a patch',
             'rev-list': 'add087a97844f4b9e307d9a216940582d96db306\n'
diff --git a/third_party/blink/tools/blinkpy/w3c/common.py b/third_party/blink/tools/blinkpy/w3c/common.py
index a0f8932..518de39 100644
--- a/third_party/blink/tools/blinkpy/w3c/common.py
+++ b/third_party/blink/tools/blinkpy/w3c/common.py
@@ -7,6 +7,8 @@
 import json
 import logging
 
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
+
 
 WPT_GH_ORG = 'web-platform-tests'
 WPT_GH_REPO_NAME = 'wpt'
@@ -23,7 +25,7 @@
 DEFAULT_WPT_COMMITTER_EMAIL = 'blink-w3c-test-autoroller@chromium.org'
 
 # TODO(qyearsley): Avoid hard-coding third_party/WebKit/LayoutTests.
-CHROMIUM_WPT_DIR = 'third_party/WebKit/LayoutTests/external/wpt/'
+CHROMIUM_WPT_DIR = RELATIVE_WEB_TESTS + 'external/wpt/'
 
 _log = logging.getLogger(__name__)
 
diff --git a/third_party/blink/tools/blinkpy/w3c/directory_owners_extractor_unittest.py b/third_party/blink/tools/blinkpy/w3c/directory_owners_extractor_unittest.py
index 553f030a..e176eb5 100644
--- a/third_party/blink/tools/blinkpy/w3c/directory_owners_extractor_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/directory_owners_extractor_unittest.py
@@ -4,19 +4,21 @@
 
 import unittest
 
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.filesystem_mock import MockFileSystem
 from blinkpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor
 
 
-ABS_WPT_BASE = '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt'
-REL_WPT_BASE = 'third_party/WebKit/LayoutTests/external/wpt'
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+ABS_WPT_BASE = MOCK_WEB_TESTS + 'external/wpt'
+REL_WPT_BASE = RELATIVE_WEB_TESTS + 'external/wpt'
 
 class DirectoryOwnersExtractorTest(unittest.TestCase):
 
     def setUp(self):
         # We always have an OWNERS file at LayoutTests/external.
         self.filesystem = MockFileSystem(files={
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/OWNERS': 'ecosystem-infra@chromium.org'
+            MOCK_WEB_TESTS + 'external/OWNERS': 'ecosystem-infra@chromium.org'
         })
         self.extractor = DirectoryOwnersExtractor(self.filesystem)
 
@@ -103,7 +105,7 @@
             ABS_WPT_BASE + '/x/y/z.html': '',
         })
         self.assertEqual(self.extractor.find_owners_file(REL_WPT_BASE + '/x/y'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/external/OWNERS')
+                         MOCK_WEB_TESTS + 'external/OWNERS')
 
     def test_find_owners_file_takes_four_kinds_of_paths(self):
         owners_path = ABS_WPT_BASE + '/foo/OWNERS'
diff --git a/third_party/blink/tools/blinkpy/w3c/gerrit_unittest.py b/third_party/blink/tools/blinkpy/w3c/gerrit_unittest.py
index a771d09..9aba2b72 100644
--- a/third_party/blink/tools/blinkpy/w3c/gerrit_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/gerrit_unittest.py
@@ -5,6 +5,7 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import mock_git_commands
 from blinkpy.w3c.gerrit import GerritCL
 from blinkpy.w3c.gerrit_mock import MockGerritAPI
@@ -70,7 +71,7 @@
             'revisions': {'1': {
                 'commit_with_footers': 'fake subject',
                 'files': {
-                    'third_party/WebKit/LayoutTests/external/wpt/foo/bar.html': '',
+                    RELATIVE_WEB_TESTS + 'external/wpt/foo/bar.html': '',
                 }
             }},
             'owner': {'email': 'test@chromium.org'},
@@ -87,7 +88,7 @@
             'revisions': {'1': {
                 'commit_with_footers': 'fake subject',
                 'files': {
-                    'third_party/WebKit/LayoutTests/foo/bar.html': '',
+                    RELATIVE_WEB_TESTS + 'foo/bar.html': '',
                 }
             }},
             'owner': {'email': 'test@chromium.org'},
@@ -104,7 +105,7 @@
             'revisions': {'1': {
                 'commit_with_footers': 'fake subject\nNo-Export: true',
                 'files': {
-                    'third_party/WebKit/LayoutTests/external/wpt/foo/bar.html': '',
+                    RELATIVE_WEB_TESTS + 'external/wpt/foo/bar.html': '',
                 }
             }},
             'owner': {'email': 'test@chromium.org'},
@@ -121,7 +122,7 @@
             'revisions': {'1': {
                 'commit_with_footers': 'fake subject\nNOEXPORT=true',
                 'files': {
-                    'third_party/WebKit/LayoutTests/external/wpt/foo/bar.html': '',
+                    RELATIVE_WEB_TESTS + 'external/wpt/foo/bar.html': '',
                 }
             }},
             'owner': {'email': 'test@chromium.org'},
@@ -138,7 +139,7 @@
             'revisions': {'1': {
                 'commit_with_footers': 'fake subject',
                 'files': {
-                    'third_party/WebKit/LayoutTests/external/wpt/foo/bar.html': '',
+                    RELATIVE_WEB_TESTS + 'external/wpt/foo/bar.html': '',
                 }
             }},
             'owner': {'email': 'test@chromium.org'},
diff --git a/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py b/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
index dd5e3f3..99eb8a1c 100644
--- a/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/import_notifier_unittest.py
@@ -7,6 +7,7 @@
 
 from blinkpy.common.checkout.git_mock import MockGit
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import mock_git_commands
 from blinkpy.common.system.filesystem_mock import MockFileSystem
 from blinkpy.w3c.local_wpt_mock import MockLocalWPT
@@ -14,13 +15,15 @@
 from blinkpy.w3c.wpt_expectations_updater import UMBRELLA_BUG
 
 
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+
 class ImportNotifierTest(unittest.TestCase):
 
     def setUp(self):
         self.host = MockHost()
         # Mock a virtual test suite at virtual/gpu/external/wpt/foo.
         self.host.filesystem = MockFileSystem({
-            '/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites':
+            MOCK_WEB_TESTS + 'VirtualTestSuites':
             '[{"prefix": "gpu", "base": "external/wpt/foo", "args": ["--foo"]}]'
         })
         self.git = self.host.git()
@@ -29,16 +32,16 @@
 
     def test_find_changed_baselines_of_tests(self):
         changed_files = [
-            'third_party/WebKit/LayoutTests/external/wpt/foo/bar.html',
-            'third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt',
-            'third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt',
-            'third_party/WebKit/LayoutTests/external/wpt/random_stuff.html',
+            RELATIVE_WEB_TESTS + 'external/wpt/foo/bar.html',
+            RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt',
+            RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt',
+            RELATIVE_WEB_TESTS + 'external/wpt/random_stuff.html',
         ]
         self.git.changed_files = lambda: changed_files
         self.assertEqual(self.notifier.find_changed_baselines_of_tests(['external/wpt/foo/bar.html']),
                          {'external/wpt/foo/bar.html': [
-                             'third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt',
-                             'third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt',
+                             RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt',
+                             RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt',
                          ]})
 
         self.assertEqual(self.notifier.find_changed_baselines_of_tests(set()), {})
@@ -83,12 +86,12 @@
 
     def test_examine_baseline_changes(self):
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
             'test@chromium.org'
         )
         changed_test_baselines = {'external/wpt/foo/bar.html': [
-            'third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt',
-            'third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt',
+            RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt',
+            RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt',
         ]}
         gerrit_url_with_ps = 'https://crrev.com/c/12345/3/'
         self.notifier.more_failures_in_baseline = lambda _: True
@@ -98,17 +101,17 @@
             self.notifier.new_failures_by_directory,
             {'external/wpt/foo': [
                 TestFailure(TestFailure.BASELINE_CHANGE, 'external/wpt/foo/bar.html',
-                            baseline_path='third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt',
+                            baseline_path=RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt',
                             gerrit_url_with_ps=gerrit_url_with_ps),
                 TestFailure(TestFailure.BASELINE_CHANGE, 'external/wpt/foo/bar.html',
-                            baseline_path='third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt',
+                            baseline_path=RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt',
                             gerrit_url_with_ps=gerrit_url_with_ps),
             ]}
         )
 
     def test_examine_new_test_expectations(self):
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
             'test@chromium.org'
         )
         test_expectations = {'external/wpt/foo/bar.html': [
@@ -142,14 +145,14 @@
 
         self.local_wpt.is_commit_affecting_directory = _is_commit_affecting_directory
         self.assertEqual(
-            self.notifier.format_commit_list(imported_commits, '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo'),
+            self.notifier.format_commit_list(imported_commits, MOCK_WEB_TESTS + 'external/wpt/foo'),
             u'Subject 1: https://github.com/web-platform-tests/wpt/commit/SHA1 [affecting this directory]\n'
             u'ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ: https://github.com/web-platform-tests/wpt/commit/SHA2\n'
         )
 
     def test_find_owned_directory_non_virtual(self):
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
             'test@chromium.org'
         )
         self.assertEqual(self.notifier.find_owned_directory('external/wpt/foo/bar.html'), 'external/wpt/foo')
@@ -157,20 +160,20 @@
 
     def test_find_owned_directory_virtual(self):
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
             'test@chromium.org'
         )
         self.assertEqual(self.notifier.find_owned_directory('virtual/gpu/external/wpt/foo/bar.html'), 'external/wpt/foo')
 
     def test_create_bugs_from_new_failures(self):
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
             '# COMPONENT: Blink>Infra>Ecosystem\n'
             '# WPT-NOTIFY: true\n'
             'foolip@chromium.org\n'
         )
         self.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/bar/OWNERS',
+            MOCK_WEB_TESTS + 'external/wpt/bar/OWNERS',
             'test@chromium.org'
         )
         self.notifier.new_failures_by_directory = {
@@ -202,19 +205,19 @@
     def test_test_failure_to_str_baseline_change(self):
         failure = TestFailure(
             TestFailure.BASELINE_CHANGE, 'external/wpt/foo/bar.html',
-            baseline_path='third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt',
+            baseline_path=RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt',
             gerrit_url_with_ps='https://crrev.com/c/12345/3/')
         self.assertEqual(str(failure),
-                         'external/wpt/foo/bar.html new failing tests: https://crrev.com/c/12345/3/'
-                         'third_party/WebKit/LayoutTests/external/wpt/foo/bar-expected.txt')
+                         'external/wpt/foo/bar.html new failing tests: https://crrev.com/c/12345/3/' +
+                         RELATIVE_WEB_TESTS + 'external/wpt/foo/bar-expected.txt')
 
         platform_failure = TestFailure(
             TestFailure.BASELINE_CHANGE, 'external/wpt/foo/bar.html',
-            baseline_path='third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt',
+            baseline_path=RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt',
             gerrit_url_with_ps='https://crrev.com/c/12345/3/')
         self.assertEqual(str(platform_failure),
-                         '[ Linux ] external/wpt/foo/bar.html new failing tests: https://crrev.com/c/12345/3/'
-                         'third_party/WebKit/LayoutTests/platform/linux/external/wpt/foo/bar-expected.txt')
+                         '[ Linux ] external/wpt/foo/bar.html new failing tests: https://crrev.com/c/12345/3/' +
+                         RELATIVE_WEB_TESTS + 'platform/linux/external/wpt/foo/bar-expected.txt')
 
     def test_test_failure_to_str_new_expectation(self):
         failure = TestFailure(
diff --git a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
index 2ebbb547..744b0007 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
@@ -26,22 +26,25 @@
 # SUCH DAMAGE.
 
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockExecutive, ScriptError
 from blinkpy.common.system.filesystem_mock import MockFileSystem
 from blinkpy.common.system.log_testing import LoggingTestCase
 from blinkpy.w3c.test_copier import TestCopier
 
 
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+
 FAKE_SOURCE_REPO_DIR = '/blink'
 
 FAKE_FILES = {
-    '/mock-checkout/third_party/Webkit/LayoutTests/external/OWNERS': '',
+    MOCK_WEB_TESTS + 'external/OWNERS': '',
     '/blink/w3c/dir/has_shebang.txt': '#!',
     '/blink/w3c/dir/README.txt': '',
     '/blink/w3c/dir/OWNERS': '',
     '/blink/w3c/dir/reftest.list': '',
-    '/mock-checkout/third_party/WebKit/LayoutTests/external/README.txt': '',
-    '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations': '',
+    MOCK_WEB_TESTS + 'external/README.txt': '',
+    MOCK_WEB_TESTS + 'W3CImportExpectations': '',
 }
 
 
@@ -95,14 +98,14 @@
         copier.do_import()
         self.assertEqual(
             host.filesystem.executable_files,
-            set(['/mock-checkout/third_party/WebKit/LayoutTests/external/blink/w3c/dir/has_shebang.txt']))
+            set([MOCK_WEB_TESTS + 'external/blink/w3c/dir/has_shebang.txt']))
 
     def test_ref_test_with_ref_is_copied(self):
         host = MockHost()
         host.filesystem = MockFileSystem(files={
             '/blink/w3c/dir1/my-ref-test.html': '<html><head><link rel="match" href="ref-file.html" />test</head></html>',
             '/blink/w3c/dir1/ref-file.html': '<html><head>test</head></html>',
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations': '',
+            MOCK_WEB_TESTS + 'W3CImportExpectations': '',
         })
         copier = TestCopier(host, FAKE_SOURCE_REPO_DIR)
         copier.find_importable_tests()
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
index aa0c9ce..0347a88 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
@@ -12,6 +12,7 @@
 from blinkpy.common.net.git_cl import TryJobStatus
 from blinkpy.common.net.git_cl_mock import MockGitCL
 from blinkpy.common.net.network_transaction import NetworkTimeout
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockCall
 from blinkpy.common.system.executive_mock import MockExecutive
 from blinkpy.common.system.log_testing import LoggingTestCase
@@ -24,12 +25,13 @@
 from blinkpy.web_tests.builder_list import BuilderList
 
 
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+
 class TestImporterTest(LoggingTestCase):
 
     def test_update_expectations_for_cl_no_results(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, time_out=True)
         success = importer.update_expectations_for_cl()
@@ -42,8 +44,7 @@
 
     def test_update_expectations_for_cl_closed_cl(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, status='closed', try_job_results={
             Build('builder-a', 123): TryJobStatus('COMPLETED', 'SUCCESS'),
@@ -57,8 +58,7 @@
 
     def test_update_expectations_for_cl_all_jobs_pass(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, status='lgtm', try_job_results={
             Build('builder-a', 123): TryJobStatus('COMPLETED', 'SUCCESS'),
@@ -72,8 +72,7 @@
 
     def test_update_expectations_for_cl_fail_but_no_changes(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, status='lgtm', try_job_results={
             Build('builder-a', 123): TryJobStatus('COMPLETED', 'FAILURE'),
@@ -88,8 +87,7 @@
 
     def test_run_commit_queue_for_cl_pass(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         # Only the latest job for each builder is counted.
         importer.git_cl = MockGitCL(host, status='lgtm', try_job_results={
@@ -112,8 +110,7 @@
 
     def test_run_commit_queue_for_cl_fail_cq(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, status='lgtm', try_job_results={
             Build('cq-builder-a', 120): TryJobStatus('COMPLETED', 'SUCCESS'),
@@ -135,8 +132,7 @@
 
     def test_run_commit_queue_for_cl_fail_to_land(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         # Only the latest job for each builder is counted.
         importer.git_cl = MockGitCL(host, status='lgtm', try_job_results={
@@ -161,8 +157,7 @@
 
     def test_run_commit_queue_for_cl_closed_cl(self):
         host = MockHost()
-        host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
         importer = TestImporter(host)
         importer.git_cl = MockGitCL(host, status='closed', try_job_results={
             Build('cq-builder-a', 120): TryJobStatus('COMPLETED', 'SUCCESS'),
@@ -202,8 +197,8 @@
             host, subject='My fake commit',
             patch=(
                 'Fake patch contents...\n'
-                '--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/outline-004.html\n'
-                '+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/outline-004.html\n'
+                '--- a/' + RELATIVE_WEB_TESTS + 'external/wpt/css/css-ui-3/outline-004.html\n'
+                '+++ b/' + RELATIVE_WEB_TESTS + 'external/wpt/css/css-ui-3/outline-004.html\n'
                 '@@ -20,7 +20,7 @@\n'
                 '...'))
         importer.exportable_but_not_exported_commits = lambda _: [fake_commit]
@@ -243,13 +238,13 @@
 
     def test_update_all_test_expectations_files(self):
         host = MockHost()
-        host.filesystem.files['/mock-checkout/third_party/WebKit/LayoutTests/TestExpectations'] = (
+        host.filesystem.files[MOCK_WEB_TESTS + 'TestExpectations'] = (
             'Bug(test) some/test/a.html [ Failure ]\n'
             'Bug(test) some/test/b.html [ Failure ]\n'
             'Bug(test) some/test/c.html [ Failure ]\n')
-        host.filesystem.files['/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites'] = '[]'
-        host.filesystem.files['/mock-checkout/third_party/WebKit/LayoutTests/new/a.html'] = ''
-        host.filesystem.files['/mock-checkout/third_party/WebKit/LayoutTests/new/b.html'] = ''
+        host.filesystem.files[MOCK_WEB_TESTS + 'VirtualTestSuites'] = '[]'
+        host.filesystem.files[MOCK_WEB_TESTS + 'new/a.html'] = ''
+        host.filesystem.files[MOCK_WEB_TESTS + 'new/b.html'] = ''
         importer = TestImporter(host)
         deleted_tests = ['some/test/b.html']
         renamed_test_pairs = {
@@ -258,23 +253,23 @@
         }
         importer.update_all_test_expectations_files(deleted_tests, renamed_test_pairs)
         self.assertMultiLineEqual(
-            host.filesystem.read_text_file('/mock-checkout/third_party/WebKit/LayoutTests/TestExpectations'),
+            host.filesystem.read_text_file(MOCK_WEB_TESTS + 'TestExpectations'),
             ('Bug(test) new/a.html [ Failure ]\n'
              'Bug(test) new/c.html [ Failure ]\n'))
 
     def test_get_directory_owners(self):
         host = MockHost()
-        host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
-        host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
                                         'someone@chromium.org\n')
         importer = TestImporter(host)
-        importer.chromium_git.changed_files = lambda: ['third_party/WebKit/LayoutTests/external/wpt/foo/x.html']
+        importer.chromium_git.changed_files = lambda: [RELATIVE_WEB_TESTS + 'external/wpt/foo/x.html']
         self.assertEqual(importer.get_directory_owners(), {('someone@chromium.org',): ['external/wpt/foo']})
 
     def test_get_directory_owners_no_changed_files(self):
         host = MockHost()
-        host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/W3CImportExpectations', '')
-        host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/OWNERS',
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'W3CImportExpectations', '')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'external/wpt/foo/OWNERS',
                                         'someone@chromium.org\n')
         importer = TestImporter(host)
         self.assertEqual(importer.get_directory_owners(), {})
@@ -455,8 +450,7 @@
         # asserts that TestImporter._generate_manifest would invoke the script.
         host = MockHost()
         importer = TestImporter(host)
-        blink_path = '/mock-checkout/third_party/WebKit'
-        host.filesystem.write_text_file(blink_path + '/LayoutTests/external/wpt/MANIFEST.json', '{}')
+        host.filesystem.write_text_file(MOCK_WEB_TESTS + 'external/wpt/MANIFEST.json', '{}')
         importer._generate_manifest()
         self.assertEqual(
             host.executive.calls,
@@ -467,22 +461,22 @@
                     'manifest',
                     '--work',
                     '--tests-root',
-                    blink_path + '/LayoutTests/external/wpt',
+                    MOCK_WEB_TESTS + 'external/wpt',
                 ]
             ])
         self.assertEqual(importer.chromium_git.added_paths,
-                         {blink_path + '/LayoutTests/external/' + BASE_MANIFEST_NAME})
+                         {MOCK_WEB_TESTS + 'external/' + BASE_MANIFEST_NAME})
 
     def test_only_wpt_manifest_changed(self):
         host = MockHost()
         importer = TestImporter(host)
         importer.chromium_git.changed_files = lambda: [
-            'third_party/WebKit/LayoutTests/external/' + BASE_MANIFEST_NAME,
-            'third_party/WebKit/LayoutTests/external/wpt/foo/x.html']
+            RELATIVE_WEB_TESTS + 'external/' + BASE_MANIFEST_NAME,
+            RELATIVE_WEB_TESTS + 'external/wpt/foo/x.html']
         self.assertFalse(importer._only_wpt_manifest_changed())
 
         importer.chromium_git.changed_files = lambda: [
-            'third_party/WebKit/LayoutTests/external/' + BASE_MANIFEST_NAME]
+            RELATIVE_WEB_TESTS + 'external/' + BASE_MANIFEST_NAME]
         self.assertTrue(importer._only_wpt_manifest_changed())
 
     def test_delete_orphaned_baselines_basic(self):
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_manifest_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_manifest_unittest.py
index 927f449e..ae3155b 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_manifest_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_manifest_unittest.py
@@ -5,23 +5,25 @@
 import unittest
 
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.executive_mock import MockExecutive
 from blinkpy.w3c.wpt_manifest import WPTManifest
 
 
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+
 class WPTManifestUnitTest(unittest.TestCase):
 
     def test_ensure_manifest_copies_new_manifest(self):
         host = MockHost()
-        manifest_path = '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/MANIFEST.json'
+        manifest_path = MOCK_WEB_TESTS + 'external/wpt/MANIFEST.json'
 
         self.assertFalse(host.filesystem.exists(manifest_path))
         WPTManifest.ensure_manifest(host)
         self.assertTrue(host.filesystem.exists(manifest_path))
         self.assertEqual(host.filesystem.written_files, {manifest_path: '{"manifest": "base"}'})
 
-        webkit_base = '/mock-checkout/third_party/WebKit'
         self.assertEqual(
             host.executive.calls,
             [
@@ -31,14 +33,14 @@
                     'manifest',
                     '--work',
                     '--tests-root',
-                    webkit_base + '/LayoutTests/external/wpt',
+                    MOCK_WEB_TESTS + 'external/wpt',
                 ]
             ]
         )
 
     def test_ensure_manifest_updates_manifest_if_it_exists(self):
         host = MockHost()
-        manifest_path = '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/MANIFEST.json'
+        manifest_path = MOCK_WEB_TESTS + 'external/wpt/MANIFEST.json'
 
         host.filesystem.write_text_file(manifest_path, '{"manifest": "NOT base"}')
 
@@ -47,7 +49,6 @@
         self.assertTrue(host.filesystem.exists(manifest_path))
         self.assertEqual(host.filesystem.written_files, {manifest_path: '{"manifest": "base"}'})
 
-        webkit_base = '/mock-checkout/third_party/WebKit'
         self.assertEqual(
             host.executive.calls,
             [
@@ -57,7 +58,7 @@
                     'manifest',
                     '--work',
                     '--tests-root',
-                    webkit_base + '/LayoutTests/external/wpt',
+                    MOCK_WEB_TESTS + 'external/wpt',
                 ]
             ]
         )
diff --git a/third_party/blink/tools/blinkpy/web_tests/controllers/layout_test_finder.py b/third_party/blink/tools/blinkpy/web_tests/controllers/layout_test_finder.py
index df0a9bba..8f59516 100644
--- a/third_party/blink/tools/blinkpy/web_tests/controllers/layout_test_finder.py
+++ b/third_party/blink/tools/blinkpy/web_tests/controllers/layout_test_finder.py
@@ -32,6 +32,7 @@
 import math
 import re
 
+from blinkpy.common.path_finder import TESTS_IN_BLINK
 from blinkpy.web_tests.layout_package.json_results_generator import convert_times_trie_to_flat_paths
 from blinkpy.web_tests.models import test_expectations
 
@@ -45,7 +46,10 @@
         self._port = port
         self._options = options
         self._filesystem = self._port.host.filesystem
-        self.LAYOUT_TESTS_DIRECTORIES = ('src', 'third_party', 'WebKit', 'LayoutTests')
+        if TESTS_IN_BLINK:
+            self.LAYOUT_TESTS_DIRECTORIES = ('src', 'third_party', 'blink', 'web_tests')
+        else:
+            self.LAYOUT_TESTS_DIRECTORIES = ('src', 'third_party', 'WebKit', 'LayoutTests')
 
     def find_tests(self, args, test_list=None, fastest_percentile=None):
         paths = self._strip_test_dir_prefixes(args)
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/android.py b/third_party/blink/tools/blinkpy/web_tests/port/android.py
index a96179ff..574033e 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/android.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/android.py
@@ -36,6 +36,7 @@
 import time
 
 from blinkpy.common import exit_codes
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.path_finder import get_chromium_src_dir
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.profiler import SingleFileOutputProfiler
@@ -103,8 +104,7 @@
 # 1. as a virtual path in file urls that will be bridged to HTTP.
 # 2. pointing to some files that are pushed to the device for tests that
 # don't work on file-over-http (e.g. blob protocol tests).
-DEVICE_WEBKIT_BASE_DIR = DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/'
-DEVICE_LAYOUT_TESTS_DIR = DEVICE_WEBKIT_BASE_DIR + 'LayoutTests/'
+DEVICE_LAYOUT_TESTS_DIR = DEVICE_SOURCE_ROOT_DIR + RELATIVE_WEB_TESTS
 
 KPTR_RESTRICT_PATH = '/proc/sys/kernel/kptr_restrict'
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index 3b4847c..94ae83b1 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -47,6 +47,7 @@
 from blinkpy.common import read_checksum_from_png
 from blinkpy.common.memoized import memoized
 from blinkpy.common.path_finder import PathFinder
+from blinkpy.common.path_finder import TESTS_IN_BLINK
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.path import abspath_to_uri
 from blinkpy.w3c.wpt_manifest import WPTManifest
@@ -254,6 +255,8 @@
                 '--ignore-certificate-errors-spki-list=' + WPT_FINGERPRINT +
                 ',' + SXG_FINGERPRINT,
                 '--user-data-dir']
+        if TESTS_IN_BLINK:
+            flags += ['--tests-in-blink']
         return flags
 
     def supports_per_test_timeout(self):
@@ -1591,7 +1594,7 @@
     def virtual_test_suites(self):
         if self._virtual_test_suites is None:
             path_to_virtual_test_suites = self._filesystem.join(self.layout_tests_dir(), 'VirtualTestSuites')
-            assert self._filesystem.exists(path_to_virtual_test_suites), 'LayoutTests/VirtualTestSuites not found'
+            assert self._filesystem.exists(path_to_virtual_test_suites), path_to_virtual_test_suites + ' not found'
             try:
                 test_suite_json = json.loads(self._filesystem.read_text_file(path_to_virtual_test_suites))
                 self._virtual_test_suites = []
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
index 7aabee8..a77d830 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base_unittest.py
@@ -32,6 +32,7 @@
 import unittest
 
 from blinkpy.common.path_finder import PathFinder
+from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.executive_mock import MockExecutive
 from blinkpy.common.system.log_testing import LoggingTestCase
@@ -44,6 +45,8 @@
 from blinkpy.web_tests.port.test import add_unit_tests_to_mock_filesystem, LAYOUT_TEST_DIR, TestPort
 
 
+MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
+
 class PortTest(LoggingTestCase):
 
     def make_port(self, executive=None, with_tests=False, port_name=None, **kwargs):
@@ -110,95 +113,95 @@
         port = self.make_port(port_name='foo')
         port.FALLBACK_PATHS = {'': ['foo']}
         test_file = 'fast/test.html'
-        port.host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites', '[]')
+        port.host.filesystem.write_text_file(MOCK_WEB_TESTS + 'VirtualTestSuites', '[]')
 
         # The default baseline
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
                          [(None, 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False), None)
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'fast/test-expected.txt')
 
         # Mismatch baseline
         self.assertEqual(port.expected_baselines(test_file, '.txt', match=False),
                          [(None, 'fast/test-expected-mismatch.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt', match=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/fast/test-expected-mismatch.txt')
+                         MOCK_WEB_TESTS + 'fast/test-expected-mismatch.txt')
 
         # Platform-specific baseline
         self.assertEqual(port.baseline_version_dir(),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo')
+                         MOCK_WEB_TESTS + 'platform/foo')
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
-                         [('/mock-checkout/third_party/WebKit/LayoutTests/platform/foo', 'fast/test-expected.txt')])
+                         [(MOCK_WEB_TESTS + 'platform/foo', 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
 
     def test_expected_baselines_flag_specific(self):
         port = self.make_port(port_name='foo')
         port.FALLBACK_PATHS = {'': ['foo']}
         test_file = 'fast/test.html'
-        port.host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites', '[]')
+        port.host.filesystem.write_text_file(MOCK_WEB_TESTS + 'VirtualTestSuites', '[]')
 
         # pylint: disable=protected-access
         port._options.additional_platform_directory = []
         port._options.additional_driver_flag = ['--special-flag']
         self.assertEqual(port.baseline_search_path(), [
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo',
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag',
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo'])
+            MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo',
+            MOCK_WEB_TESTS + 'flag-specific/special-flag',
+            MOCK_WEB_TESTS + 'platform/foo'])
         self.assertEqual(port.baseline_version_dir(),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo')
+                         MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo')
 
         # The default baseline
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
                          [(None, 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False), None)
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'fast/test-expected.txt')
 
         # Platform-specific baseline
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
-                         [('/mock-checkout/third_party/WebKit/LayoutTests/platform/foo', 'fast/test-expected.txt')])
+                         [(MOCK_WEB_TESTS + 'platform/foo', 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
 
         # Flag-specific baseline
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'flag-specific/special-flag/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
-                         [('/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag', 'fast/test-expected.txt')])
+                         [(MOCK_WEB_TESTS + 'flag-specific/special-flag', 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'flag-specific/special-flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'flag-specific/special-flag/fast/test-expected.txt')
 
         # Flag-specific platform-specific baseline
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo/fast/test-expected.txt', 'foo')
         self.assertEqual(
             port.expected_baselines(test_file, '.txt'),
-            [('/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo', 'fast/test-expected.txt')])
+            [(MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo', 'fast/test-expected.txt')])
         self.assertEqual(
             port.expected_filename(test_file, '.txt'),
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo/fast/test-expected.txt')
+            MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo/fast/test-expected.txt')
         self.assertEqual(
             port.expected_filename(test_file, '.txt', return_default=False),
-            '/mock-checkout/third_party/WebKit/LayoutTests/flag-specific/special-flag/platform/foo/fast/test-expected.txt')
+            MOCK_WEB_TESTS + 'flag-specific/special-flag/platform/foo/fast/test-expected.txt')
 
     def test_expected_baselines_virtual(self):
         port = self.make_port(port_name='foo')
         port.FALLBACK_PATHS = {'': ['foo']}
         virtual_test = 'virtual/flag/fast/test.html'
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites',
+            MOCK_WEB_TESTS + 'VirtualTestSuites',
             '[{ "prefix": "flag", "base": "fast", "args": ["--flag"]}]')
 
         # The default baseline for base test
@@ -206,56 +209,56 @@
                          [(None, 'virtual/flag/fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False), None)
         self.assertEqual(port.expected_filename(virtual_test, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False, fallback_base_for_virtual=False), None)
         self.assertEqual(port.expected_filename(virtual_test, '.txt', fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
 
         # Platform-specific baseline for base test
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(virtual_test, '.txt'),
                          [(None, 'virtual/flag/fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False, fallback_base_for_virtual=False), None)
         self.assertEqual(port.expected_filename(virtual_test, '.txt', fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
 
         # The default baseline for virtual test
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(virtual_test, '.txt'),
-                         [('/mock-checkout/third_party/WebKit/LayoutTests', 'virtual/flag/fast/test-expected.txt')])
+                         [(MOCK_WEB_TESTS[:-1], 'virtual/flag/fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False, fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'virtual/flag/fast/test-expected.txt')
 
         # Platform-specific baseline for virtual test
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/virtual/flag/fast/test-expected.txt', 'foo')
+            MOCK_WEB_TESTS + 'platform/foo/virtual/flag/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(virtual_test, '.txt'),
-                         [('/mock-checkout/third_party/WebKit/LayoutTests/platform/foo', 'virtual/flag/fast/test-expected.txt')])
+                         [(MOCK_WEB_TESTS + 'platform/foo', 'virtual/flag/fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', return_default=False, fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/virtual/flag/fast/test-expected.txt')
         self.assertEqual(port.expected_filename(virtual_test, '.txt', fallback_base_for_virtual=False),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/virtual/flag/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'platform/foo/virtual/flag/fast/test-expected.txt')
 
     def test_additional_platform_directory(self):
         port = self.make_port(port_name='foo')
         port.FALLBACK_PATHS = {'': ['foo']}
-        port.host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites', '[]')
+        port.host.filesystem.write_text_file(MOCK_WEB_TESTS + 'VirtualTestSuites', '[]')
         test_file = 'fast/test.html'
 
         # Simple additional platform directory
@@ -266,7 +269,7 @@
                          [(None, 'fast/test-expected.txt')])
         self.assertEqual(port.expected_filename(test_file, '.txt', return_default=False), None)
         self.assertEqual(port.expected_filename(test_file, '.txt'),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/fast/test-expected.txt')
+                         MOCK_WEB_TESTS + 'fast/test-expected.txt')
 
         port.host.filesystem.write_text_file('/tmp/local-baselines/fast/test-expected.txt', 'foo')
         self.assertEqual(port.expected_baselines(test_file, '.txt'),
@@ -291,23 +294,23 @@
 
     def test_nonexistant_expectations(self):
         port = self.make_port(port_name='foo')
-        port.expectations_files = lambda: ['/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations',
-                                           '/mock-checkout/third_party/WebKit/LayoutTests/platform/nonexistant/TestExpectations']
-        port.host.filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations', '')
+        port.expectations_files = lambda: [MOCK_WEB_TESTS + 'platform/exists/TestExpectations',
+                                           MOCK_WEB_TESTS + 'platform/nonexistant/TestExpectations']
+        port.host.filesystem.write_text_file(MOCK_WEB_TESTS + 'platform/exists/TestExpectations', '')
         self.assertEqual('\n'.join(port.expectations_dict().keys()),
-                         '/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations')
+                         MOCK_WEB_TESTS + 'platform/exists/TestExpectations')
 
     def test_additional_expectations(self):
         port = self.make_port(port_name='foo')
         port.port_name = 'foo'
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/TestExpectations', '')
+            MOCK_WEB_TESTS + 'platform/foo/TestExpectations', '')
         port.host.filesystem.write_text_file(
             '/tmp/additional-expectations-1.txt', 'content1\n')
         port.host.filesystem.write_text_file(
             '/tmp/additional-expectations-2.txt', 'content2\n')
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/FlagExpectations/special-flag', 'content3')
+            MOCK_WEB_TESTS + 'FlagExpectations/special-flag', 'content3')
 
         self.assertEqual('\n'.join(port.expectations_dict().values()), '')
 
@@ -330,21 +333,22 @@
         port = self.make_port(port_name='foo')
         port.port_name = 'foo'
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/FlagExpectations/special-flag-a', 'aa')
+            MOCK_WEB_TESTS + 'FlagExpectations/special-flag-a', 'aa')
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/FlagExpectations/special-flag-b', 'bb')
+            MOCK_WEB_TESTS + 'FlagExpectations/special-flag-b', 'bb')
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/FlagExpectations/README.txt', 'cc')
+            MOCK_WEB_TESTS + 'FlagExpectations/README.txt', 'cc')
 
         self.assertEqual('\n'.join(port.expectations_dict().values()), '')
-        self.assertEqual('\n'.join(port.all_expectations_dict().values()), 'bb\naa')
+        # all_expectations_dict() is an OrderedDict, but its order depends on
+        # file system walking order.
+        self.assertEqual('\n'.join(sorted(port.all_expectations_dict().values())), 'aa\nbb')
 
     def test_flag_specific_expectations_identify_unreadable_file(self):
         port = self.make_port(port_name='foo')
         port.port_name = 'foo'
 
-        non_utf8_file = ('/mock-checkout/third_party/WebKit/LayoutTests/'
-                         'FlagExpectations/non-utf8-file')
+        non_utf8_file = MOCK_WEB_TESTS + 'FlagExpectations/non-utf8-file'
         invalid_utf8 = '\xC0'
         port.host.filesystem.write_binary_file(non_utf8_file, invalid_utf8)
 
@@ -376,7 +380,7 @@
         self.assertEqual(port_c.additional_driver_flags(),
                          ['--cc'] + default_flags)
 
-        flag_file = '/mock-checkout/third_party/WebKit/LayoutTests/additional-driver-flag.setting'
+        flag_file = MOCK_WEB_TESTS + 'additional-driver-flag.setting'
         port_a.host.filesystem.write_text_file(flag_file, '--aa')
         port_b.host.filesystem.write_text_file(flag_file, '--aa')
         port_c.host.filesystem.write_text_file(flag_file, '--bb')
@@ -907,7 +911,7 @@
         # pylint: disable=protected-access
         port._options.image_first_tests = ['image-first', 'virtual/flag/additional-virtual-image-first']
         port.host.filesystem.write_text_file(
-            '/mock-checkout/third_party/WebKit/LayoutTests/VirtualTestSuites',
+            MOCK_WEB_TESTS + 'VirtualTestSuites',
             json.dumps([
                 { "prefix": "flag", "base": "image-first", "args": ["--flag"]},
                 { "prefix": "flag", "base": "non-image-first", "args": ["--flag"]}
diff --git a/third_party/blink/tools/blinkpy/web_tests/run_webkit_tests_unittest.py b/third_party/blink/tools/blinkpy/web_tests/run_webkit_tests_unittest.py
index 33b9ca2..97d5e74 100644
--- a/third_party/blink/tools/blinkpy/web_tests/run_webkit_tests_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/run_webkit_tests_unittest.py
@@ -39,6 +39,7 @@
 from blinkpy.common import path_finder
 from blinkpy.common.host import Host
 from blinkpy.common.host_mock import MockHost
+from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
 from blinkpy.common.system.path import abspath_to_uri
 from blinkpy.common.system.system_host import SystemHost
 
@@ -487,7 +488,7 @@
         self.assertEqual(tests_run, ['passes/text.html'])
 
     def test_single_file_with_prefix(self):
-        tests_run = get_tests_run(['LayoutTests/passes/text.html'])
+        tests_run = get_tests_run([WEB_TESTS_LAST_COMPONENT + '/passes/text.html'])
         self.assertEqual(['passes/text.html'], tests_run)
 
     def test_stderr_is_saved(self):
@@ -517,7 +518,7 @@
     def test_test_list_with_prefix(self):
         host = MockHost()
         filename = '/tmp/foo.txt'
-        host.filesystem.write_text_file(filename, 'LayoutTests/passes/text.html')
+        host.filesystem.write_text_file(filename, WEB_TESTS_LAST_COMPONENT + '/passes/text.html')
         tests_run = get_tests_run(['--test-list=%s' % filename], host=host)
         self.assertEqual(['passes/text.html'], tests_run)
 
diff --git a/third_party/closure_compiler/externs/bookmark_manager_private.js b/third_party/closure_compiler/externs/bookmark_manager_private.js
index ec1faf2..dfabdaf 100644
--- a/third_party/closure_compiler/externs/bookmark_manager_private.js
+++ b/third_party/closure_compiler/externs/bookmark_manager_private.js
@@ -81,9 +81,10 @@
 /**
  * Begins dragging a set of bookmarks
  * @param {Array} idList An array of string-valued ids
+ * @param {number} dragNodeIndex The index of the dragged node in |idList|
  * @param {boolean} isFromTouch True if the drag was initiated from touch
  */
-chrome.bookmarkManagerPrivate.startDrag = function(idList, isFromTouch) {};
+chrome.bookmarkManagerPrivate.startDrag = function(idList, dragNodeIndex, isFromTouch) {};
 
 /**
  * Performs the drop action of the drag and drop session
diff --git a/third_party/closure_compiler/externs/developer_private.js b/third_party/closure_compiler/externs/developer_private.js
index 54311a8..88f91de 100644
--- a/third_party/closure_compiler/externs/developer_private.js
+++ b/third_party/closure_compiler/externs/developer_private.js
@@ -270,9 +270,25 @@
 
 /**
  * @typedef {{
+ *   host: string,
+ *   granted: boolean
+ * }}
+ */
+chrome.developerPrivate.SiteControl;
+
+/**
+ * @typedef {{
+ *   hasAllHosts: boolean,
+ *   hosts: !Array<!chrome.developerPrivate.SiteControl>
+ * }}
+ */
+chrome.developerPrivate.SpecificSiteControls;
+
+/**
+ * @typedef {{
  *   simplePermissions: !Array<!chrome.developerPrivate.Permission>,
  *   hostAccess: (!chrome.developerPrivate.HostAccess|undefined),
- *   runtimeHostPermissions: (!Array<string>|undefined)
+ *   specificSiteControls: (!chrome.developerPrivate.SpecificSiteControls|undefined)
  * }}
  */
 chrome.developerPrivate.Permissions;
diff --git a/third_party/feed/BUILD.gn b/third_party/feed/BUILD.gn
index 798d3eb..589fab49 100644
--- a/third_party/feed/BUILD.gn
+++ b/third_party/feed/BUILD.gn
@@ -32,10 +32,10 @@
       "com.google.android.libraries.feed.sharedstream.publicapi.menumeasurer"
 }
 
-android_resources("basicstream_internal_actions_resources") {
-  resource_dirs = [ "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/res/" ]
+android_resources("sharedstream_contextmenumanager_resources") {
+  resource_dirs = [ "src/src/main/java/com/google/android/libraries/feed/sharedstream/contextmenumanager/res" ]
   custom_package =
-      "com.google.android.libraries.feed.basicstream.internal.actions"
+      "com.google.android.libraries.feed.sharedstream.contextmenumanager"
 }
 
 android_library("feed_lib_java") {
@@ -43,12 +43,12 @@
   java_files = feed_lib_java_sources
 
   deps = [
-    ":basicstream_internal_actions_resources",
     ":basicstream_internal_viewholders_resources",
     ":basicstream_resources",
     ":feed_lib_proto_java",
     ":piet_resources",
     ":shared_stream_publicapi_menumeasurer_resources",
+    ":sharedstream_contextmenumanager_resources",
     "//third_party/android_deps:android_support_annotations_java",
     "//third_party/android_deps:android_support_cardview_java",
     "//third_party/android_deps:android_support_v7_appcompat_java",
diff --git a/third_party/feed/README.chromium b/third_party/feed/README.chromium
index 0aa68d1..1fcc48d 100644
--- a/third_party/feed/README.chromium
+++ b/third_party/feed/README.chromium
@@ -2,7 +2,7 @@
 Short name: feed
 URL: https://chromium.googlesource.com/feed
 Version: 0
-Revision: e291161061d18d222bc1b546225fe0567386cbf1
+Revision: 2cefe05b77f7039db95a63804edf284e68055dda
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/feed/java_sources.gni b/third_party/feed/java_sources.gni
index 58fdb01..a7c31653 100644
--- a/third_party/feed/java_sources.gni
+++ b/third_party/feed/java_sources.gni
@@ -72,7 +72,6 @@
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/logging/VisibilityMonitor.java",
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/scroll/ScrollRestorer.java",
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/scroll/StreamScrollMonitor.java",
-  "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/util/ContextMenuHelper.java",
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/viewholders/ContinuationViewHolder.java",
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/viewholders/FeedViewHolder.java",
   "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/viewholders/HeaderViewHolder.java",
@@ -95,6 +94,7 @@
   "src/src/main/java/com/google/android/libraries/feed/common/functional/Predicate.java",
   "src/src/main/java/com/google/android/libraries/feed/common/functional/SettableSupplier.java",
   "src/src/main/java/com/google/android/libraries/feed/common/functional/Supplier.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/locale/LocaleUtils.java",
   "src/src/main/java/com/google/android/libraries/feed/common/logging/Dumpable.java",
   "src/src/main/java/com/google/android/libraries/feed/common/logging/Dumper.java",
   "src/src/main/java/com/google/android/libraries/feed/common/logging/Logger.java",
@@ -128,6 +128,7 @@
   "src/src/main/java/com/google/android/libraries/feed/feedprotocoladapter/internal/transformers/DataOperationTransformer.java",
   "src/src/main/java/com/google/android/libraries/feed/feedprotocoladapter/internal/transformers/FeatureDataOperationTransformer.java",
   "src/src/main/java/com/google/android/libraries/feed/feedrequestmanager/FeedRequestManager.java",
+  "src/src/main/java/com/google/android/libraries/feed/feedrequestmanager/internal/Utils.java",
   "src/src/main/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManager.java",
   "src/src/main/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManagerFactory.java",
   "src/src/main/java/com/google/android/libraries/feed/feedsessionmanager/internal/ContentCache.java",
@@ -156,6 +157,7 @@
   "src/src/main/java/com/google/android/libraries/feed/feedstore/internal/PersistentFeedStore.java",
   "src/src/main/java/com/google/android/libraries/feed/host/action/ActionApi.java",
   "src/src/main/java/com/google/android/libraries/feed/host/action/StreamActionApi.java",
+  "src/src/main/java/com/google/android/libraries/feed/host/config/ApplicationInfo.java",
   "src/src/main/java/com/google/android/libraries/feed/host/config/Configuration.java",
   "src/src/main/java/com/google/android/libraries/feed/host/config/DebugBehavior.java",
   "src/src/main/java/com/google/android/libraries/feed/host/imageloader/BundledAssets.java",
@@ -230,9 +232,8 @@
   "src/src/main/java/com/google/android/libraries/feed/piet/host/HostBindingProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/DrawableScalingHelper.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/GridRowView.java",
-  "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerColorDrawable.java",
-  "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerImageView.java",
-  "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java",
+  "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperView.java",
+  "src/src/main/java/com/google/android/libraries/feed/sharedstream/contextmenumanager/ContextMenuManager.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/offlinemonitor/StreamOfflineMonitor.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/piet/PietAssetProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/piet/PietCustomElementProvider.java",
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index 5682574..f48e3b3 100644
--- a/third_party/libaom/README.chromium
+++ b/third_party/libaom/README.chromium
@@ -2,9 +2,9 @@
 Short Name: libaom
 URL: https://aomedia.googlesource.com/aom/
 Version: 0
-Date: Tuesday September 18 2018
+Date: Monday October 08 2018
 Branch: master
-Commit: f866f5ebb34b22afc2244789dc8551b0c8d99a13
+Commit: a5078bf8d0e7c01eab670cfc1cfe7b9fb065e931
 License: BSD
 License File: source/libaom/LICENSE
 Security Critical: yes
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h
index ed07851..acf6f27 100644
--- a/third_party/libaom/source/config/config/aom_version.h
+++ b/third_party/libaom/source/config/config/aom_version.h
@@ -12,8 +12,8 @@
 #define VERSION_MAJOR 1
 #define VERSION_MINOR 0
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "612-gf866f5ebb"
+#define VERSION_EXTRA "614-ga5078bf8d"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "1.0.0-612-gf866f5ebb"
-#define VERSION_STRING " 1.0.0-612-gf866f5ebb"
+#define VERSION_STRING_NOSP "1.0.0-614-ga5078bf8d"
+#define VERSION_STRING " 1.0.0-614-ga5078bf8d"
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index a17246a..baabfbd 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -5,9 +5,9 @@
 License File: source/libvpx/LICENSE
 Security Critical: yes
 
-Date: Saturday September 29 2018
+Date: Monday October 08 2018
 Branch: master
-Commit: 2beb5c9f91e7166c2c9d01c94bf84767815121e4
+Commit: ecc31d28781c490f5fb18a3e6873692a1b8e6cea
 
 Description:
 Contains the sources used to compile libvpx binaries used by Google Chrome and
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 81961788..6ae53f6 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,7 +2,7 @@
 #define VERSION_MAJOR  1
 #define VERSION_MINOR  7
 #define VERSION_PATCH  0
-#define VERSION_EXTRA  "1115-g2beb5c9f9"
+#define VERSION_EXTRA  "1139-gecc31d287"
 #define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.7.0-1115-g2beb5c9f9"
-#define VERSION_STRING      " v1.7.0-1115-g2beb5c9f9"
+#define VERSION_STRING_NOSP "v1.7.0-1139-gecc31d287"
+#define VERSION_STRING      " v1.7.0-1139-gecc31d287"
diff --git a/tools/ipc_fuzzer/fuzzer/fuzzer.cc b/tools/ipc_fuzzer/fuzzer/fuzzer.cc
index ec291e5..af3ba548 100644
--- a/tools/ipc_fuzzer/fuzzer/fuzzer.cc
+++ b/tools/ipc_fuzzer/fuzzer/fuzzer.cc
@@ -1172,19 +1172,6 @@
 };
 
 template <>
-struct FuzzTraits<media::VideoCaptureFormat> {
-  static bool Fuzz(media::VideoCaptureFormat* p, Fuzzer* fuzzer) {
-    if (!FuzzParam(&p->frame_size, fuzzer))
-      return false;
-    if (!FuzzParam(&p->frame_rate, fuzzer))
-      return false;
-    if (!FuzzParam(reinterpret_cast<int*>(&p->pixel_format), fuzzer))
-      return false;
-    return true;
-  }
-};
-
-template <>
 struct FuzzTraits<net::LoadTimingInfo> {
   static bool Fuzz(net::LoadTimingInfo* p, Fuzzer* fuzzer) {
     return FuzzParam(&p->socket_log_id, fuzzer) &&
diff --git a/tools/linux/dump-static-initializers.py b/tools/linux/dump-static-initializers.py
index b71d062..e937d68 100755
--- a/tools/linux/dump-static-initializers.py
+++ b/tools/linux/dump-static-initializers.py
@@ -38,8 +38,10 @@
 IS_GIT_WORKSPACE = (subprocess.Popen(
     ['git', 'rev-parse'], stderr=subprocess.PIPE).wait() == 0)
 
+
 class Demangler(object):
   """A wrapper around c++filt to provide a function to demangle symbols."""
+
   def __init__(self, toolchain):
     self.cppfilt = subprocess.Popen([toolchain + 'c++filt'],
                                     stdin=subprocess.PIPE,
@@ -50,6 +52,7 @@
     self.cppfilt.stdin.write(sym + '\n')
     return self.cppfilt.stdout.readline().strip()
 
+
 # Matches for example: "cert_logger.pb.cc", capturing "cert_logger".
 protobuf_filename_re = re.compile(r'(.*)\.pb\.cc$')
 def QualifyFilenameAsProto(filename):
@@ -72,6 +75,7 @@
     candidate = line.strip()
   return candidate
 
+
 # Regex matching the substring of a symbol's demangled text representation most
 # likely to appear in a source file.
 # Example: "v8::internal::Builtins::InitBuiltinFunctionTable()" becomes
@@ -99,6 +103,7 @@
     candidate = line.strip()
   return candidate
 
+
 # Regex matching nm output for the symbols we're interested in.
 # See test_ParseNmLine for examples.
 nm_re = re.compile(r'(\S+) (\S+) t (?:_ZN12)?_GLOBAL__(?:sub_)?I_(.*)')
@@ -123,6 +128,7 @@
     '_GLOBAL__sub_I_extension_specifics.pb.cc')
   assert parse == ('extension_specifics.pb.cc', 40607408, 36), parse
 
+
 # Just always run the test; it is fast enough.
 test_ParseNmLine()
 
@@ -136,6 +142,7 @@
     if parse:
       yield parse
 
+
 # Regex matching objdump output for the symbols we're interested in.
 # Example line:
 #     12354ab:  (disassembly, including <FunctionReference>)
@@ -158,13 +165,14 @@
       if ref.startswith('.LC') or ref.startswith('_DYNAMIC'):
         # Ignore these, they are uninformative.
         continue
-      if ref.startswith('_GLOBAL__I_'):
+      if re.match('_GLOBAL__(?:sub_)?I_', ref):
         # Probably a relative jump within this function.
         continue
       refs.add(ref)
 
   return sorted(refs)
 
+
 def main():
   parser = optparse.OptionParser(usage='%prog [option] filename')
   parser.add_option('-d', '--diffable', dest='diffable',
@@ -236,5 +244,6 @@
 
   return 0
 
+
 if '__main__' == __name__:
   sys.exit(main())
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index faa2bb4c..9a62862a4 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -395,18 +395,8 @@
     },
 
     'chromium.perf.fyi': {
-      # TODO(crbug.com/828445): Remove 'Linux Compile Perf FYI'
-      'Linux Compile Perf FYI': 'official_goma_perf',
-      # TODO(crbug.com/828444): Remove 'Android Builder Perf FYI'
-      'Android Builder Perf FYI': 'official_goma_minimal_symbols_android',
-      # TODO(crbug.com/828442): Remove 'Android arm64 Builder Perf FYI'
-      'Android arm64 Builder Perf FYI': 'official_goma_minimal_symbols_android_arm64',
-      'Android CFI Builder Perf FYI': 'official_goma_minimal_symbols_android_thin_lto_opt',
-      'Android CFI arm64 Builder Perf FYI': 'official_goma_minimal_symbols_android_thin_lto_opt_arm64',
-      # TODO(crbug.com/828443): Remove 'Mac Builder Perf FYI'
-      'Mac Builder Perf FYI': 'official_goma',
-      # TODO(crbug.com/835288): Remove 'Win Builder Perf FYI'
-      'Win Builder Perf FYI': 'official_goma_x86',
+     'android-cfi-builder-perf-fyi': 'official_goma_minimal_symbols_android_thin_lto_opt',
+      'android_arm64-cfi-builder-perf-fyi': 'official_goma_minimal_symbols_android_thin_lto_opt_arm64',
     },
 
     'chromium.swarm': {
@@ -709,6 +699,10 @@
       'win_chrome_official': 'official_goma_x86',
     },
 
+    'tryserver.chrome.win': {
+      'win_chrome_official': 'official_goma_x86',
+    },
+
     'tryserver.v8': {
       'v8_linux_blink_rel': 'release_trybot',
       'v8_linux_chromium_gn_rel': 'release_trybot',
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 93404b06..9c28d79 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -1647,6 +1647,18 @@
   </description>
 </action>
 
+<action name="Android.ExploreSitesNTP.Opened">
+  <owner>dimich@chromium.org</owner>
+  <description>User opened NTP with enabled ExploreSites section.</description>
+</action>
+
+<action name="Android.ExploreSitesPage.Open">
+  <owner>dimich@chromium.org</owner>
+  <description>
+    User opened ExploreSites page, typically by clicking on a NTP category icon.
+  </description>
+</action>
+
 <action name="Android.HistoryPage.ClearBrowsingData">
   <owner>twellington@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a9119d595..976710e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -29309,6 +29309,7 @@
   <int value="-726892130" label="AndroidMessagesIntegration:disabled"/>
   <int value="-723224470" label="enable-password-force-saving:enabled"/>
   <int value="-722474177" label="browser-side-navigation:disabled"/>
+  <int value="-721245076" label="DesktopPWAsStayInWindow:disabled"/>
   <int value="-719819631" label="TranslateUI:disabled"/>
   <int value="-719147284" label="ExperimentalProductivityFeatures:disabled"/>
   <int value="-718884399" label="NewTabPageUIMd:enabled"/>
@@ -29607,6 +29608,7 @@
   <int value="-102537270" label="extension-content-verification"/>
   <int value="-102227288" label="PasswordExport:disabled"/>
   <int value="-99781021" label="disable-roboto-font-ui"/>
+  <int value="-97145978" label="DesktopPWAsStayInWindow:enabled"/>
   <int value="-89690053" label="MaterialDesignUserManager:enabled"/>
   <int value="-88822940" label="ssl-version-min"/>
   <int value="-88273414" label="ContentSuggestionsShowSummary:enabled"/>
@@ -29857,6 +29859,7 @@
   <int value="412957264" label="tab-close-buttons-hidden-with-touch"/>
   <int value="413081240" label="enable-new-md-input-view"/>
   <int value="413695227" label="NTPSuggestionsStandaloneUI:enabled"/>
+  <int value="414114078" label="LitePageServerPreviews:disabled"/>
   <int value="415154056" label="enable-physical-keyboard-autocorrect"/>
   <int value="416887895" label="enable-password-change-support"/>
   <int value="417709910"
@@ -29988,6 +29991,7 @@
   <int value="664591021" label="EnableContinueReading:enabled"/>
   <int value="665409384"
       label="AutofillToolkitViewsCreditCardDialogsMac:enabled"/>
+  <int value="667643314" label="LitePageServerPreviews:enabled"/>
   <int value="673588373" label="OmniboxPedalSuggestions:disabled"/>
   <int value="679931272" label="DcheckIsFatal:enabled"/>
   <int value="680070635"
@@ -33882,6 +33886,7 @@
   <int value="177" label="SSL_CLIENT_AUTH_NO_COMMON_ALGORITHMS"/>
   <int value="178" label="EARLY_DATA_REJECTED"/>
   <int value="179" label="WRONG_VERSION_ON_EARLY_DATA"/>
+  <int value="180" label="TLS13_DOWNGRADE_DETECTED"/>
   <int value="200" label="CERT_COMMON_NAME_INVALID"/>
   <int value="201" label="CERT_DATE_INVALID"/>
   <int value="202" label="CERT_AUTHORITY_INVALID"/>
@@ -40685,6 +40690,7 @@
   <int value="2" label="Subframe navigation"/>
   <int value="3" label="Server Unavailable"/>
   <int value="4" label="Infobar hasn't been seen by the user and needs to be"/>
+  <int value="5" label="Network was not slow"/>
 </enum>
 
 <enum name="PreviewsServerLitePageServerResponse">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 54daefa..0ed79ba 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -11176,6 +11176,18 @@
   </summary>
 </histogram>
 
+<histogram name="BrowserRenderProcessHost.InvisibleMediaStreamFrameDepth"
+    units="frame_depth" expires_after="M72">
+  <owner>boliu@chromium.org</owner>
+  <owner>alexmos@chromium.org</owner>
+  <summary>
+    Recorded at most once per renderer process when it first becomes invisible
+    and has media stream. Record the frame depth of the process. Note this
+    metric is only meaningful when some form of OOPIF is enabled; otherwise
+    frame depth is always 0.
+  </summary>
+</histogram>
+
 <histogram name="BrowserRenderProcessHost.KeepAliveDuration" units="ms">
   <owner>horo@chromium.org</owner>
   <summary>
@@ -27875,6 +27887,15 @@
   </summary>
 </histogram>
 
+<histogram name="ExploreSites.ClickedNTPCategoryIndex">
+  <owner>dimich@chromium.org</owner>
+  <summary>
+    0-based index of a category tile on NTP which was clicked by the user.
+    Indices are assigned by counting category tiles left-to-right, top-to-bottom
+    as they appear on NTP. Recorded on click.
+  </summary>
+</histogram>
+
 <histogram name="ExploreSites.ExploreSitesStore.StoreEvent"
     enum="ExploreSitesStoreEvent">
   <owner>dewittj@chromium.org</owner>
@@ -27892,6 +27913,15 @@
   </summary>
 </histogram>
 
+<histogram name="ExploreSites.NTPLoadingCatalogFromNetwork" units="Boolean">
+  <owner>dimich@chromium.org</owner>
+  <summary>
+    Recorded every time NTP is opened while ExploreSites feature is enabled.
+    Indicates whether or not the local cached version of ExploreSites catalog
+    was available or a network request was initiated to load one.
+  </summary>
+</histogram>
+
 <histogram name="ExtensionActivity.AdInjected" units="Extension Count">
   <obsolete>
     Deprecated with M46.
@@ -81551,6 +81581,22 @@
   </summary>
 </histogram>
 
+<histogram name="Printing.CUPS.JobDuration.JobCancelled" units="ms">
+  <owner>jschettler@chromium.org</owner>
+  <summary>
+    Records the print job duration of a cancelled print job. Includes time spent
+    in a suspended or error state. Only recorded on Chrome OS.
+  </summary>
+</histogram>
+
+<histogram name="Printing.CUPS.JobDuration.JobDone" units="ms">
+  <owner>jschettler@chromium.org</owner>
+  <summary>
+    Records the print job duration of a done/completed print job. Includes time
+    spent in a suspended or error state. Only recorded on Chrome OS.
+  </summary>
+</histogram>
+
 <histogram name="Printing.CUPS.JobResult" enum="PrintJobResult">
   <owner>skau@chromium.org</owner>
   <summary>
@@ -81568,6 +81614,14 @@
   </summary>
 </histogram>
 
+<histogram name="Printing.CUPS.PrintDocumentSize" units="KB">
+  <owner>jschettler@chromium.org</owner>
+  <summary>
+    Records the total size of the printed document (PDF) sent to CUPS. Only
+    recorded on Chrome OS.
+  </summary>
+</histogram>
+
 <histogram name="Printing.CUPS.PrinterAdded" enum="PrinterProtocol">
   <owner>skau@chromium.org</owner>
   <summary>
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 6df3e546..5450883 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -366,3 +366,9 @@
 crbug.com/853835 [ Linux ] loading.desktop.network_service/Kenh14_warm [ Skip ]
 crbug.com/853835 [ Linux ] loading.desktop.network_service/Taobao_cold [ Skip ]
 crbug.com/853835 [ Linux ] loading.desktop.network_service/Taobao_warm [ Skip ]
+crbug.com/879833 [ Linux ] loading.desktop.network_service/Walgreens_cold [ Skip ]
+crbug.com/879833 [ Linux ] loading.desktop.network_service/Walgreens_warm [ Skip ]
+
+# Benchmark: loading.desktop_layout_ng
+crbug.com/879833 [ Linux ] loading.desktop_layout_ng/Walgreens_cold [ Skip ]
+crbug.com/879833 [ Linux ] loading.desktop_layout_ng/Walgreens_warm [ Skip ]
diff --git a/tools/perf_expectations/OWNERS b/tools/perf_expectations/OWNERS
deleted file mode 100644
index 514cf031..0000000
--- a/tools/perf_expectations/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-jochen@chromium.org
-thakis@chromium.org
-thestig@chromium.org
diff --git a/tools/perf_expectations/PRESUBMIT.py b/tools/perf_expectations/PRESUBMIT.py
deleted file mode 100644
index 6c7768f..0000000
--- a/tools/perf_expectations/PRESUBMIT.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2011 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.
-
-"""Presubmit script for perf_expectations.
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
-details on the presubmit API built into depot_tools.
-"""
-
-PERF_EXPECTATIONS = 'tools/perf_expectations/perf_expectations.json'
-CONFIG_FILE = 'tools/perf_expectations/chromium_perf_expectations.cfg'
-
-def CheckChangeOnUpload(input_api, output_api):
-  run_tests = False
-  for path in input_api.LocalPaths():
-    path = path.replace('\\', '/')
-    if (PERF_EXPECTATIONS == path or CONFIG_FILE == path):
-      run_tests = True
-
-  output = []
-  if run_tests:
-    whitelist = [r'.+_unittest\.py$']
-    output.extend(input_api.canned_checks.RunUnitTestsInDirectory(
-        input_api, output_api, 'tests', whitelist))
-  return output
-
-
-def CheckChangeOnCommit(input_api, output_api):
-  run_tests = False
-  for path in input_api.LocalPaths():
-    path = path.replace('\\', '/')
-    if (PERF_EXPECTATIONS == path or CONFIG_FILE == path):
-      run_tests = True
-
-  output = []
-  if run_tests:
-    whitelist = [r'.+_unittest\.py$']
-    output.extend(input_api.canned_checks.RunUnitTestsInDirectory(
-        input_api, output_api, 'tests', whitelist))
-
-  output.extend(input_api.canned_checks.CheckDoNotSubmit(input_api,
-                                                         output_api))
-  return output
diff --git a/tools/perf_expectations/README.txt b/tools/perf_expectations/README.txt
deleted file mode 100644
index cd8f3d5..0000000
--- a/tools/perf_expectations/README.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-This is where old perf machinery used to live, keeping track of binary sizes,
-etc. Now that lives elsewhere and has a team to support it (see
-https://www.chromium.org/developers/tree-sheriffs/perf-sheriffs). This code
-remains to ensure that no static initializers get into Chromium.
-
-Because this code has this history, it's far more complicated than it needs to
-be. TODO(dpranke): Simplify it. https://crbug.com/572393
-
-In the meanwhile, if you're trying to update perf_expectations.json, there are
-no instructions for doing so, and the tools that you used to use don't work
-because they rely on data files that were last updated at the end of 2015. So
-here's what to do to reset the expected static initializer count value.
-
-The expected static initializer count value is in the "regress" field for the
-platform. In addition, each platform has a checksum in the "sha1" field to
-ensure that you properly used the magic tools. Since the magic tools don't work
-anymore, dpranke added a bypass to the verification. If you run:
-
-> tools/perf_expectations/make_expectations.py --checksum --verbose
-
-the script will tell you what the checksum *should* be. Alter the "sha1" field
-to be that value, and you can commit changes to that file.
-
-Please see https://crbug.com/572393 for more information.
diff --git a/tools/perf_expectations/chromium_perf_expectations.cfg b/tools/perf_expectations/chromium_perf_expectations.cfg
deleted file mode 100644
index 4d3abe7..0000000
--- a/tools/perf_expectations/chromium_perf_expectations.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "base_url": "http://build.chromium.org/f/chromium/perf",
-  "perf_file": "perf_expectations.json"
-}
diff --git a/tools/perf_expectations/make_expectations.py b/tools/perf_expectations/make_expectations.py
deleted file mode 100755
index 996d78a..0000000
--- a/tools/perf_expectations/make_expectations.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 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.
-
-# For instructions see:
-# http://www.chromium.org/developers/tree-sheriffs/perf-sheriffs
-
-import hashlib
-import math
-import optparse
-import os
-import re
-import subprocess
-import sys
-import time
-import urllib2
-
-
-try:
-  import json
-except ImportError:
-  import simplejson as json
-
-
-__version__ = '1.0'
-EXPECTATIONS_DIR = os.path.dirname(os.path.abspath(__file__))
-DEFAULT_CONFIG_FILE = os.path.join(EXPECTATIONS_DIR,
-                                   'chromium_perf_expectations.cfg')
-DEFAULT_TOLERANCE = 0.05
-USAGE = ''
-
-
-def ReadFile(filename):
-  try:
-    file = open(filename, 'rb')
-  except IOError, e:
-    print >> sys.stderr, ('I/O Error reading file %s(%s): %s' %
-                          (filename, e.errno, e.strerror))
-    raise e
-  contents = file.read()
-  file.close()
-  return contents
-
-
-def ConvertJsonIntoDict(string):
-  """Read a JSON string and convert its contents into a Python datatype."""
-  if len(string) == 0:
-    print >> sys.stderr, ('Error could not parse empty string')
-    raise Exception('JSON data missing')
-
-  try:
-    jsondata = json.loads(string)
-  except ValueError, e:
-    print >> sys.stderr, ('Error parsing string: "%s"' % string)
-    raise e
-  return jsondata
-
-
-# Floating point representation of last time we fetched a URL.
-last_fetched_at = None
-def FetchUrlContents(url):
-  global last_fetched_at
-  if last_fetched_at and ((time.time() - last_fetched_at) <= 0.5):
-    # Sleep for half a second to avoid overloading the server.
-    time.sleep(0.5)
-  try:
-    last_fetched_at = time.time()
-    connection = urllib2.urlopen(url)
-  except urllib2.HTTPError, e:
-    if e.code == 404:
-      return None
-    raise e
-  text = connection.read().strip()
-  connection.close()
-  return text
-
-
-def GetRowData(data, key):
-  rowdata = []
-  # reva and revb always come first.
-  for subkey in ['reva', 'revb']:
-    if subkey in data[key]:
-      rowdata.append('"%s": %s' % (subkey, data[key][subkey]))
-  # Strings, like type, come next.
-  for subkey in ['type', 'better']:
-    if subkey in data[key]:
-      rowdata.append('"%s": "%s"' % (subkey, data[key][subkey]))
-  # Finally the main numbers come last.
-  for subkey in ['improve', 'regress', 'tolerance']:
-    if subkey in data[key]:
-      rowdata.append('"%s": %s' % (subkey, data[key][subkey]))
-  return rowdata
-
-
-def GetRowDigest(rowdata, key):
-  sha1 = hashlib.sha1()
-  rowdata = [str(possibly_unicode_string).encode('ascii')
-             for possibly_unicode_string in rowdata]
-  sha1.update(str(rowdata) + key)
-  return sha1.hexdigest()[0:8]
-
-
-def WriteJson(filename, data, keys, calculate_sha1=True):
-  """Write a list of |keys| in |data| to the file specified in |filename|."""
-  try:
-    file = open(filename, 'wb')
-  except IOError, e:
-    print >> sys.stderr, ('I/O Error writing file %s(%s): %s' %
-                          (filename, e.errno, e.strerror))
-    return False
-  jsondata = []
-  for key in keys:
-    rowdata = GetRowData(data, key)
-    if calculate_sha1:
-      # Include an updated checksum.
-      rowdata.append('"sha1": "%s"' % GetRowDigest(rowdata, key))
-    else:
-      if 'sha1' in data[key]:
-        rowdata.append('"sha1": "%s"' % (data[key]['sha1']))
-    jsondata.append('"%s": {%s}' % (key, ', '.join(rowdata)))
-  jsondata.append('"load": true')
-  jsontext = '{%s\n}' % ',\n '.join(jsondata)
-  file.write(jsontext + '\n')
-  file.close()
-  return True
-
-
-def FloatIsInt(f):
-  epsilon = 1.0e-10
-  return abs(f - int(f)) <= epsilon
-
-
-last_key_printed = None
-def Main(args):
-  def OutputMessage(message, verbose_message=True):
-    global last_key_printed
-    if not options.verbose and verbose_message:
-      return
-
-    if key != last_key_printed:
-      last_key_printed = key
-      print '\n' + key + ':'
-    print '  %s' % message
-
-  parser = optparse.OptionParser(usage=USAGE, version=__version__)
-  parser.add_option('-v', '--verbose', action='store_true', default=False,
-                    help='enable verbose output')
-  parser.add_option('-s', '--checksum', action='store_true',
-                    help='test if any changes are pending')
-  parser.add_option('-c', '--config', dest='config_file',
-                    default=DEFAULT_CONFIG_FILE,
-                    help='set the config file to FILE', metavar='FILE')
-  options, args = parser.parse_args(args)
-
-  if options.verbose:
-    print 'Verbose output enabled.'
-
-  config = ConvertJsonIntoDict(ReadFile(options.config_file))
-
-  # Get the list of summaries for a test.
-  base_url = config['base_url']
-  # Make the perf expectations file relative to the path of the config file.
-  perf_file = os.path.join(
-    os.path.dirname(options.config_file), config['perf_file'])
-  perf = ConvertJsonIntoDict(ReadFile(perf_file))
-
-  # Fetch graphs.dat for this combination.
-  perfkeys = perf.keys()
-  # In perf_expectations.json, ignore the 'load' key.
-  perfkeys.remove('load')
-  perfkeys.sort()
-
-  write_new_expectations = False
-  found_checksum_mismatch = False
-  for key in perfkeys:
-    value = perf[key]
-    tolerance = value.get('tolerance', DEFAULT_TOLERANCE)
-    better = value.get('better', None)
-
-    # Verify the checksum.
-    original_checksum = value.get('sha1', '')
-    if 'sha1' in value:
-      del value['sha1']
-    rowdata = GetRowData(perf, key)
-    computed_checksum = GetRowDigest(rowdata, key)
-    if original_checksum == computed_checksum:
-      OutputMessage('checksum matches, skipping')
-      continue
-    elif options.checksum:
-      OutputMessage('checksum mismatch, original = %s, computed = %s' %
-                    (original_checksum, computed_checksum))
-      found_checksum_mismatch = True
-      continue
-
-    # Skip expectations that are missing a reva or revb.  We can't generate
-    # expectations for those.
-    if not(value.has_key('reva') and value.has_key('revb')):
-      OutputMessage('missing revision range, skipping')
-      continue
-    revb = int(value['revb'])
-    reva = int(value['reva'])
-
-    # Ensure that reva is less than revb.
-    if reva > revb:
-      temp = reva
-      reva = revb
-      revb = temp
-
-    # Get the system/test/graph/tracename and reftracename for the current key.
-    matchData = re.match(r'^([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)$', key)
-    if not matchData:
-      OutputMessage('cannot parse key, skipping')
-      continue
-    system = matchData.group(1)
-    test = matchData.group(2)
-    graph = matchData.group(3)
-    tracename = matchData.group(4)
-    reftracename = tracename + '_ref'
-
-    # Create the summary_url and get the json data for that URL.
-    # FetchUrlContents() may sleep to avoid overloading the server with
-    # requests.
-    summary_url = '%s/%s/%s/%s-summary.dat' % (base_url, system, test, graph)
-    summaryjson = FetchUrlContents(summary_url)
-    if not summaryjson:
-      OutputMessage('ERROR: cannot find json data, please verify',
-                    verbose_message=False)
-      return 0
-
-    # Set value's type to 'relative' by default.
-    value_type = value.get('type', 'relative')
-
-    summarylist = summaryjson.split('\n')
-    trace_values = {}
-    traces = [tracename]
-    if value_type == 'relative':
-      traces += [reftracename]
-    for trace in traces:
-      trace_values.setdefault(trace, {})
-
-    # Find the high and low values for each of the traces.
-    scanning = False
-    for line in summarylist:
-      jsondata = ConvertJsonIntoDict(line)
-      try:
-        rev = int(jsondata['rev'])
-      except ValueError:
-        print ('Warning: skipping rev %r because could not be parsed '
-               'as an integer.' % jsondata['rev'])
-        continue
-      if rev <= revb:
-        scanning = True
-      if rev < reva:
-        break
-
-      # We found the upper revision in the range.  Scan for trace data until we
-      # find the lower revision in the range.
-      if scanning:
-        for trace in traces:
-          if trace not in jsondata['traces']:
-            OutputMessage('trace %s missing' % trace)
-            continue
-          if type(jsondata['traces'][trace]) != type([]):
-            OutputMessage('trace %s format not recognized' % trace)
-            continue
-          try:
-            tracevalue = float(jsondata['traces'][trace][0])
-          except ValueError:
-            OutputMessage('trace %s value error: %s' % (
-                trace, str(jsondata['traces'][trace][0])))
-            continue
-
-          for bound in ['high', 'low']:
-            trace_values[trace].setdefault(bound, tracevalue)
-
-          trace_values[trace]['high'] = max(trace_values[trace]['high'],
-                                            tracevalue)
-          trace_values[trace]['low'] = min(trace_values[trace]['low'],
-                                           tracevalue)
-
-    if 'high' not in trace_values[tracename]:
-      OutputMessage('no suitable traces matched, skipping')
-      continue
-
-    if value_type == 'relative':
-      # Calculate assuming high deltas are regressions and low deltas are
-      # improvements.
-      regress = (float(trace_values[tracename]['high']) -
-                 float(trace_values[reftracename]['low']))
-      improve = (float(trace_values[tracename]['low']) -
-                 float(trace_values[reftracename]['high']))
-    elif value_type == 'absolute':
-      # Calculate assuming high absolutes are regressions and low absolutes are
-      # improvements.
-      regress = float(trace_values[tracename]['high'])
-      improve = float(trace_values[tracename]['low'])
-
-    # So far we've assumed better is lower (regress > improve).  If the actual
-    # values for regress and improve are equal, though, and better was not
-    # specified, alert the user so we don't let them create a new file with
-    # ambiguous rules.
-    if better == None and regress == improve:
-      OutputMessage('regress (%s) is equal to improve (%s), and "better" is '
-                    'unspecified, please fix by setting "better": "lower" or '
-                    '"better": "higher" in this perf trace\'s expectation' % (
-                    regress, improve), verbose_message=False)
-      return 1
-
-    # If the existing values assume regressions are low deltas relative to
-    # improvements, swap our regress and improve.  This value must be a
-    # scores-like result.
-    if 'regress' in perf[key] and 'improve' in perf[key]:
-      if perf[key]['regress'] < perf[key]['improve']:
-        assert(better != 'lower')
-        better = 'higher'
-        temp = regress
-        regress = improve
-        improve = temp
-      else:
-        # Sometimes values are equal, e.g., when they are both 0,
-        # 'better' may still be set to 'higher'.
-        assert(better != 'higher' or
-               perf[key]['regress'] == perf[key]['improve'])
-        better = 'lower'
-
-    # If both were ints keep as int, otherwise use the float version.
-    originally_ints = False
-    if FloatIsInt(regress) and FloatIsInt(improve):
-      originally_ints = True
-
-    if better == 'higher':
-      if originally_ints:
-        regress = int(math.floor(regress - abs(regress*tolerance)))
-        improve = int(math.ceil(improve + abs(improve*tolerance)))
-      else:
-        regress = regress - abs(regress*tolerance)
-        improve = improve + abs(improve*tolerance)
-    else:
-      if originally_ints:
-        improve = int(math.floor(improve - abs(improve*tolerance)))
-        regress = int(math.ceil(regress + abs(regress*tolerance)))
-      else:
-        improve = improve - abs(improve*tolerance)
-        regress = regress + abs(regress*tolerance)
-
-    # Calculate the new checksum to test if this is the only thing that may have
-    # changed.
-    checksum_rowdata = GetRowData(perf, key)
-    new_checksum = GetRowDigest(checksum_rowdata, key)
-
-    if ('regress' in perf[key] and 'improve' in perf[key] and
-        perf[key]['regress'] == regress and perf[key]['improve'] == improve and
-        original_checksum == new_checksum):
-      OutputMessage('no change')
-      continue
-
-    write_new_expectations = True
-    OutputMessage('traces: %s' % trace_values, verbose_message=False)
-    OutputMessage('before: %s' % perf[key], verbose_message=False)
-    perf[key]['regress'] = regress
-    perf[key]['improve'] = improve
-    OutputMessage('after: %s' % perf[key], verbose_message=False)
-
-  if options.checksum:
-    if found_checksum_mismatch:
-      return 1
-    else:
-      return 0
-
-  if write_new_expectations:
-    print '\nWriting expectations... ',
-    WriteJson(perf_file, perf, perfkeys)
-    print 'done'
-  else:
-    if options.verbose:
-      print ''
-    print 'No changes.'
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(Main(sys.argv))
diff --git a/tools/perf_expectations/perf_expectations.json b/tools/perf_expectations/perf_expectations.json
deleted file mode 100644
index bf071330..0000000
--- a/tools/perf_expectations/perf_expectations.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{"linux-release-64/sizes/chrome-si/initializers": {"reva": 480969, "revb": 480969, "type": "absolute", "better": "lower", "improve": 8, "regress": 8, "tolerance": 0, "sha1": "3c815259"},
- "linux-release-64/sizes/nacl_helper-si/initializers": {"reva": 480969, "revb": 480969, "type": "absolute", "better": "lower", "improve": 6, "regress": 8, "sha1": "8416f450"},
- "linux-release-64/sizes/nacl_helper_bootstrap-si/initializers": {"reva": 114822, "revb": 115019, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "228221af"},
- "linux-release/sizes/chrome-si/initializers": {"reva": 480969, "revb": 480969, "type": "absolute", "better": "lower", "improve": 9, "regress": 9, "tolerance": 0, "sha1": "03dc3cfd"},
- "linux-release/sizes/nacl_helper-si/initializers": {"reva": 480969, "revb": 480969, "type": "absolute", "better": "lower", "improve": 7, "regress": 9, "sha1": "1a3c5b2b"},
- "linux-release/sizes/nacl_helper_bootstrap-si/initializers": {"reva": 114822, "revb": 115019, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "dd908f29"},
- "mac-release/sizes/chrome-si/initializers": {"reva": 281731, "revb": 281731, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "tolerance": 0, "sha1": "01759b7f"},
- "load": true
-}
diff --git a/tools/perf_expectations/sample_test_cases.json b/tools/perf_expectations/sample_test_cases.json
deleted file mode 100644
index 0fb7462..0000000
--- a/tools/perf_expectations/sample_test_cases.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{"linux-release/media_tests_av_perf/audio_latency/latency": {"reva": 180005, "revb": 180520, "type": "absolute", "better": "lower", "improve": 190, "regress": 222, "sha1": "fc9815d5"},
-"linux-release/media_tests_av_perf/dropped_fps/tulip2.wav": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "fb8157f9"},
-"linux-release/media_tests_av_perf/dropped_fps/tulip2.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "c0fb3421"},
-"linux-release/media_tests_av_perf/dropped_frames/crowd1080.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "fa9582d3"},
-"linux-release/media_tests_av_perf/dropped_frames/crowd2160.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 166, "regress": 231, "sha1": "ca3a7a47"},
-"linux-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 0, "regress": 0},
-"linux-release/media_tests_av_perf/fps/tulip2.mp3": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 0, "regress": 0},
-"linux-release/media_tests_av_perf/fps/tulip2.mp4": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 32, "regress": 28},
-"linux-release/media_tests_av_perf/fps/tulip2.ogg": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 0, "regress": 0},
-"linux-release/media_tests_av_perf/fps/tulip2.ogv": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 32, "regress": 28},
-"linux-release/media_tests_av_perf/fps/tulip2.wav": {"reva": 34239, "revb": 1298213, "type": "absolute", "better": "higher", "improve": 0, "regress": 0},
-"win-release/media_tests_av_perf/dropped_fps/tulip2.wav": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "646c02f2"},
-"win-release/media_tests_av_perf/dropped_fps/tulip2.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "46c97b57"},
-"win-release/media_tests_av_perf/dropped_frames/crowd1080.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 0, "regress": 0, "sha1": "9b709aab"},
-"win-release/media_tests_av_perf/dropped_frames/crowd2160.webm": {"reva": 181131, "revb": 181572, "type": "absolute", "better": "lower", "improve": 174, "regress": 204, "sha1": "4c0270a6"},
-"win-release/media_tests_av_perf/fps/crowd1080.webm": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 53, "regress": 43, "sha1": "7ad49461"},
-"win-release/media_tests_av_perf/fps/crowd2160.webm": {"reva": 176330, "revb": 176978, "type": "absolute", "better": "higher", "improve": 26.0399945997, "regress": 25.9062437562, "sha1": "700526a9"},
-"win-release/media_tests_av_perf/fps/crowd360.webm": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 51, "regress": 47, "sha1": "7f8ef21c"},
-"win-release/media_tests_av_perf/fps/crowd480.webm": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 50, "regress": 47, "sha1": "5dc96881"},
-"win-release/media_tests_av_perf/fps/crowd720.webm": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 52, "regress": 47, "sha1": "4fcfb653"},
-"win-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 0, "regress": 0, "sha1": "54d94538"},
-"win-release/media_tests_av_perf/fps/tulip2.mp3": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 0, "regress": 0, "sha1": "113aef17"},
-"win-release/media_tests_av_perf/fps/tulip2.mp4": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 30, "regress": 28, "sha1": "a22847d0"},
-"win-release/media_tests_av_perf/fps/tulip2.ogg": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 0, "regress": 0, "sha1": "6ee2e716"},
-"win-release/media_tests_av_perf/fps/tulip2.ogv": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 32, "regress": 26, "sha1": "dfadb872"},
-"win-release/media_tests_av_perf/fps/tulip2.wav": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 0, "regress": 0, "sha1": "530c5bf5"},
-"win-release/media_tests_av_perf/fps/tulip2.webm": {"reva": 163299, "revb": 164141, "type": "absolute", "better": "higher", "improve": 30, "regress": 28, "sha1": "35b91c8e"}
-}
\ No newline at end of file
diff --git a/tools/perf_expectations/tests/perf_expectations_unittest.py b/tools/perf_expectations/tests/perf_expectations_unittest.py
deleted file mode 100755
index 9b9b126..0000000
--- a/tools/perf_expectations/tests/perf_expectations_unittest.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 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.
-
-"""Verify perf_expectations.json can be loaded using simplejson.
-
-perf_expectations.json is a JSON-formatted file.  This script verifies
-that simplejson can load it correctly.  It should catch most common
-formatting problems.
-"""
-
-import subprocess
-import sys
-import os
-import unittest
-import re
-
-simplejson = None
-
-def OnTestsLoad():
-  old_path = sys.path
-  script_path = os.path.dirname(sys.argv[0])
-  load_path = None
-  global simplejson
-
-  # This test script should be stored in src/tools/perf_expectations/.  That
-  # directory will most commonly live in 2 locations:
-  #
-  #   - a regular Chromium checkout, in which case src/third_party
-  #     is where to look for simplejson
-  #
-  #   - a buildbot checkout, in which case .../pylibs is where
-  #     to look for simplejson
-  #
-  # Locate and install the correct path based on what we can find.
-  #
-  for path in ('../../../third_party', '../../../../../pylibs'):
-    path = os.path.join(script_path, path)
-    if os.path.exists(path) and os.path.isdir(path):
-      load_path = os.path.abspath(path)
-      break
-
-  if load_path is None:
-    msg = "%s expects to live within a Chromium checkout" % sys.argv[0]
-    raise Exception, "Error locating simplejson load path (%s)" % msg
-
-  # Try importing simplejson once.  If this succeeds, we found it and will
-  # load it again later properly.  Fail if we cannot load it.
-  sys.path.append(load_path)
-  try:
-    import simplejson as Simplejson
-    simplejson = Simplejson
-  except ImportError, e:
-    msg = "%s expects to live within a Chromium checkout" % sys.argv[0]
-    raise Exception, "Error trying to import simplejson from %s (%s)" % \
-                     (load_path, msg)
-  finally:
-    sys.path = old_path
-  return True
-
-def LoadJsonFile(filename):
-  f = open(filename, 'r')
-  try:
-    data = simplejson.load(f)
-  except ValueError, e:
-    f.seek(0)
-    print "Error reading %s:\n%s" % (filename,
-                                     f.read()[:50]+'...')
-    raise e
-  f.close()
-  return data
-
-OnTestsLoad()
-
-CONFIG_JSON = os.path.join(os.path.dirname(sys.argv[0]),
-                           '../chromium_perf_expectations.cfg')
-MAKE_EXPECTATIONS = os.path.join(os.path.dirname(sys.argv[0]),
-                                 '../make_expectations.py')
-PERF_EXPECTATIONS = os.path.join(os.path.dirname(sys.argv[0]),
-                                 '../perf_expectations.json')
-
-
-class PerfExpectationsUnittest(unittest.TestCase):
-  def testPerfExpectations(self):
-    # Test data is dictionary.
-    perf_data = LoadJsonFile(PERF_EXPECTATIONS)
-    if not isinstance(perf_data, dict):
-      raise Exception('perf expectations is not a dict')
-
-    # Test the 'load' key.
-    if not 'load' in perf_data:
-      raise Exception("perf expectations is missing a load key")
-    if not isinstance(perf_data['load'], bool):
-      raise Exception("perf expectations load key has non-bool value")
-
-    # Test all key values are dictionaries.
-    bad_keys = []
-    for key in perf_data:
-      if key == 'load':
-        continue
-      if not isinstance(perf_data[key], dict):
-        bad_keys.append(key)
-    if len(bad_keys) > 0:
-      msg = "perf expectations keys have non-dict values"
-      raise Exception("%s: %s" % (msg, bad_keys))
-
-    # Test all key values have delta and var keys.
-    for key in perf_data:
-      if key == 'load':
-        continue
-
-      # First check if regress/improve is in the key's data.
-      if 'regress' in perf_data[key]:
-        if 'improve' not in perf_data[key]:
-          bad_keys.append(key)
-        if (not isinstance(perf_data[key]['regress'], int) and
-            not isinstance(perf_data[key]['regress'], float)):
-          bad_keys.append(key)
-        if (not isinstance(perf_data[key]['improve'], int) and
-            not isinstance(perf_data[key]['improve'], float)):
-          bad_keys.append(key)
-      else:
-        # Otherwise check if delta/var is in the key's data.
-        if 'delta' not in perf_data[key] or 'var' not in perf_data[key]:
-          bad_keys.append(key)
-        if (not isinstance(perf_data[key]['delta'], int) and
-            not isinstance(perf_data[key]['delta'], float)):
-          bad_keys.append(key)
-        if (not isinstance(perf_data[key]['var'], int) and
-            not isinstance(perf_data[key]['var'], float)):
-          bad_keys.append(key)
-
-    if len(bad_keys) > 0:
-      msg = "perf expectations key values missing or invalid delta/var"
-      raise Exception("%s: %s" % (msg, bad_keys))
-
-    # Test all keys have the correct format.
-    for key in perf_data:
-      if key == 'load':
-        continue
-      # tools/buildbot/scripts/master/log_parser.py should have a matching
-      # regular expression.
-      if not re.match(r"^([\w\.-]+)/([\w\.-]+)/([\w\.-]+)/([\w\.-]+)$", key):
-        bad_keys.append(key)
-    if len(bad_keys) > 0:
-      msg = "perf expectations keys in bad format, expected a/b/c/d"
-      raise Exception("%s: %s" % (msg, bad_keys))
-
-  def testNoUpdatesNeeded(self):
-    p = subprocess.Popen([MAKE_EXPECTATIONS, '-s'], stdout=subprocess.PIPE)
-    p.wait();
-    self.assertEqual(p.returncode, 0,
-        msg='Update expectations first by running ./make_expectations.py')
-
-  def testConfigFile(self):
-    # Test that the config file can be parsed as JSON.
-    config = LoadJsonFile(CONFIG_JSON)
-    # Require the following keys.
-    if 'base_url' not in config:
-      raise Exception('base_url not specified in config file')
-    if 'perf_file' not in config:
-      raise Exception('perf_file not specified in config file')
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/tools/perf_expectations/update_perf_expectations.py b/tools/perf_expectations/update_perf_expectations.py
deleted file mode 100755
index d1ce983..0000000
--- a/tools/perf_expectations/update_perf_expectations.py
+++ /dev/null
@@ -1,263 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Prepare tests that require re-baselining for input to make_expectations.py.
-
-The regularly running perf-AV tests require re-baselineing of expectations
-about once a week. The steps involved in rebaselining are:
-
-1.) Identify the tests to update, based off reported e-mail results.
-2.) Figure out reva and revb values, which is the starting and ending revision
- numbers for the range that we should use to obtain new thresholds.
-3.) Modify lines in perf_expectations.json referring to the tests to be updated,
- so that they may be used as input to make_expectations.py.
-
-This script automates the last step above.
-
-Here's a sample line from perf_expectations.json:
-
-"win-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": 163299, \
-"revb": 164141, "type": "absolute", "better": "higher", "improve": 0, \
-"regress": 0, "sha1": "54d94538"},
-
-To get the above test ready for input to make_expectations.py, it should become:
-
-"win-release/media_tests_av_perf/fps/tulip2.m4a": {"reva": <new reva>, \
-"revb": <new revb>, "type": "absolute", "better": "higher", "improve": 0, \
-"regress": 0},
-
-Examples:
-
-1.) To update the test specified above and get baseline
-values using the revision range 12345 and 23456, run this script with a command
-line like this:
-  python update_perf_expectations.py -f \
-  win-release/media_tests_av_perf/fps/tulip2.m4a --reva 12345 --revb 23456
-Or, using an input file,
-where the input file contains a single line with text
-  win-release/media_tests_av_perf/fps/tulip2.m4a
-run with this command line:
-  python update_perf_expectations.py -i input.txt --reva 12345 --revb 23456
-
-2.) Let's say you want to update all seek tests on windows, and get baseline
-values using the revision range 12345 and 23456.
-Run this script with this command line:
-  python update_perf_expectations.py -f win-release/media_tests_av_perf/seek/ \
-   --reva 12345 --revb 23456
-Or:
-  python update_perf_expectations.py -f win-release/.*/seek/ --reva 12345 \
-  --revb 23456
-
-Or, using an input file,
-where the input file contains a single line with text win-release/.*/seek/:
-  python update_perf_expectations.py -i input.txt --reva 12345 --revb 23456
-
-3.) Similarly, if you want to update seek tests on all platforms
-  python update_perf_expectations.py -f .*-release/.*/seek/ --reva 12345 \
-  --revb 23456
-
-"""
-
-import logging
-from optparse import OptionParser
-import os
-import re
-
-import make_expectations as perf_ex_lib
-
-# Default logging is INFO. Use --verbose to enable DEBUG logging.
-_DEFAULT_LOG_LEVEL = logging.INFO
-
-
-def GetTestsToUpdate(contents, all_test_keys):
-  """Parses input contents and obtains tests to be re-baselined.
-
-  Args:
-    contents: string containing contents of input file.
-    all_test_keys: list of keys of test dictionary.
-  Returns:
-    A list of keys for tests that should be updated.
-  """
-  # Each line of the input file specifies a test case to update.
-  tests_list = []
-  for test_case_filter in contents.splitlines():
-    # Skip any empty lines.
-    if test_case_filter:
-      # Sample expected line:
-      # win-release/media_tests_av_perf/seek/\
-      # CACHED_BUFFERED_SEEK_NoConstraints_crowd1080.ogv
-      # Or, if reg-ex, then sample line:
-      # win-release/media-tests_av_perf/seek*
-      # Skip any leading spaces if they exist in the input file.
-      logging.debug('Trying to match %s', test_case_filter)
-      tests_list.extend(GetMatchingTests(test_case_filter.strip(),
-                                         all_test_keys))
-  return tests_list
-
-
-def GetMatchingTests(tests_to_update, all_test_keys):
-  """Parses input reg-ex filter and obtains tests to be re-baselined.
-
-  Args:
-    tests_to_update: reg-ex string specifying tests to be updated.
-    all_test_keys: list of keys of tests dictionary.
-  Returns:
-    A list of keys for tests that should be updated.
-  """
-  tests_list = []
-  search_string = re.compile(tests_to_update)
-  # Get matching tests from the dictionary of tests
-  for test_key in all_test_keys:
-    if search_string.match(test_key):
-      tests_list.append(test_key)
-      logging.debug('%s will be updated', test_key)
-  logging.info('%s tests found matching reg-ex: %s', len(tests_list),
-               tests_to_update)
-  return tests_list
-
-
-def PrepareTestsForUpdate(tests_to_update, all_tests, reva, revb):
-  """Modifies value of tests that are to re-baselined:
-     Set reva and revb values to specified new values. Remove sha1.
-
-  Args:
-    tests_to_update: list of tests to be updated.
-    all_tests: dictionary of all tests.
-    reva: oldest revision in range to use for new values.
-    revb: newest revision in range to use for new values.
-  Raises:
-    ValueError: If reva or revb are not valid ints, or if either
-    of them are negative.
-  """
-  reva = int(reva)
-  revb = int(revb)
-
-  if reva < 0 or revb < 0:
-    raise ValueError('Revision values should be positive.')
-  # Ensure reva is less than revb.
-  # (this is similar to the check done in make_expectations.py)
-  if revb < reva:
-    temp = revb
-    revb = reva
-    reva = temp
-  for test_key in tests_to_update:
-    # Get original test from the dictionary of tests
-    test_value = all_tests[test_key]
-    if test_value:
-      # Sample line in perf_expectations.json:
-      #  "linux-release/media_tests _av_perf/dropped_frames/crowd360.webm":\
-      # {"reva": 155180, "revb": 155280, "type": "absolute", \
-      # "better": "lower", "improve": 0, "regress": 3, "sha1": "276ba29c"},
-      # Set new revision range
-      test_value['reva'] = reva
-      test_value['revb'] = revb
-      # Remove sha1 to indicate this test requires an update
-      # Check first to make sure it exist.
-      if 'sha1' in test_value:
-        del test_value['sha1']
-    else:
-      logging.warning('%s does not exist.', test_key)
-  logging.info('Done preparing tests for update.')
-
-
-def GetCommandLineOptions():
-  """Parse command line arguments.
-
-  Returns:
-    An options object containing command line arguments and their values.
-  """
-  parser = OptionParser()
-
-  parser.add_option('--reva', dest='reva', type='int',
-                    help='Starting revision of new range.',
-                    metavar='START_REVISION')
-  parser.add_option('--revb', dest='revb', type='int',
-                    help='Ending revision of new range.',
-                    metavar='END_REVISION')
-  parser.add_option('-f', dest='tests_filter',
-                    help='Regex to use for filtering tests to be updated. '
-                    'At least one of -filter or -input_file must be provided. '
-                    'If both are provided, then input-file is used.',
-                    metavar='FILTER', default='')
-  parser.add_option('-i', dest='input_file',
-                    help='Optional path to file with reg-exes for tests to'
-                    ' update. If provided, it overrides the filter argument.',
-                    metavar='INPUT_FILE', default='')
-  parser.add_option('--config', dest='config_file',
-                    default=perf_ex_lib.DEFAULT_CONFIG_FILE,
-                    help='Set the config file to FILE.', metavar='FILE')
-  parser.add_option('-v', dest='verbose', action='store_true', default=False,
-                    help='Enable verbose output.')
-  options = parser.parse_args()[0]
-  return options
-
-
-def Main():
-  """Main driver function."""
-  options = GetCommandLineOptions()
-
-  _SetLogger(options.verbose)
-  # Do some command-line validation
-  if not options.input_file and not options.tests_filter:
-    logging.error('At least one of input-file or test-filter must be provided.')
-    exit(1)
-  if options.input_file and options.tests_filter:
-    logging.error('Specify only one of input file or test-filter.')
-    exit(1)
-  if not options.reva or not options.revb:
-    logging.error('Start and end revision of range must be specified.')
-    exit(1)
-
-  # Load config.
-  config = perf_ex_lib.ConvertJsonIntoDict(
-      perf_ex_lib.ReadFile(options.config_file))
-
-  # Obtain the perf expectations file from the config file.
-  perf_file = os.path.join(
-      os.path.dirname(options.config_file), config['perf_file'])
-
-  # We should have all the information we require now.
-  # On to the real thang.
-  # First, get all the existing tests from the original perf_expectations file.
-  all_tests = perf_ex_lib.ConvertJsonIntoDict(
-      perf_ex_lib.ReadFile(perf_file))
-  all_test_keys = all_tests.keys()
-  # Remove the load key, because we don't want to modify it.
-  all_test_keys.remove('load')
-  # Keep tests sorted, like in the original file.
-  all_test_keys.sort()
-
-  # Next, get all tests that have been identified for an update.
-  tests_to_update = []
-  if options.input_file:
-    # Tests to update have been specified in an input_file.
-    # Get contents of file.
-    tests_filter = perf_ex_lib.ReadFile(options.input_file)
-  elif options.tests_filter:
-    # Tests to update have been specified as a reg-ex filter.
-    tests_filter = options.tests_filter
-
-  # Get tests to update based on filter specified.
-  tests_to_update = GetTestsToUpdate(tests_filter, all_test_keys)
-  logging.info('Done obtaining matching tests.')
-
-  # Now, prepare tests for update.
-  PrepareTestsForUpdate(tests_to_update, all_tests, options.reva, options.revb)
-
-  # Finally, write modified tests back to perf_expectations file.
-  perf_ex_lib.WriteJson(perf_file, all_tests, all_test_keys,
-                        calculate_sha1=False)
-  logging.info('Done writing tests for update to %s.', perf_file)
-
-
-def _SetLogger(verbose):
-  log_level = _DEFAULT_LOG_LEVEL
-  if verbose:
-    log_level = logging.DEBUG
-  logging.basicConfig(level=log_level, format='%(message)s')
-
-
-if __name__ == '__main__':
-  Main()
diff --git a/tools/perf_expectations/update_perf_expectations_unittest.py b/tools/perf_expectations/update_perf_expectations_unittest.py
deleted file mode 100755
index 9e54b89..0000000
--- a/tools/perf_expectations/update_perf_expectations_unittest.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""Unit tests for update_perf_expectations."""
-import copy
-from StringIO import StringIO
-import unittest
-import make_expectations as perf_ex_lib
-import update_perf_expectations as upe_mod
-
-
-# A separate .json file contains the list of test cases we'll use.
-# The tests used to be defined inline here, but are >80 characters in length.
-# Now they are expected to be defined in file ./sample_test_cases.json.
-# Create a dictionary of tests using .json file.
-all_tests = perf_ex_lib.ConvertJsonIntoDict(
-    perf_ex_lib.ReadFile('sample_test_cases.json'))
-# Get all keys.
-all_tests_keys = all_tests.keys()
-
-
-def VerifyPreparedTests(self, tests_to_update, reva, revb):
-  # Work with a copy of the set of tests.
-  all_tests_copy = copy.deepcopy(all_tests)
-  upe_mod.PrepareTestsForUpdate(tests_to_update, all_tests_copy, reva, revb)
-  # Make sure reva < revb
-  if reva > revb:
-    temp = reva
-    reva = revb
-    revb = temp
-  # Run through all tests and make sure only those that were
-  # specified to be modified had their 'sha1' value removed.
-  for test_key in all_tests_keys:
-    new_test_value = all_tests_copy[test_key]
-    original_test_value = all_tests[test_key]
-    if test_key in tests_to_update:
-      # Make sure there is no "sha1".
-      self.assertFalse('sha1' in new_test_value)
-      # Make sure reva and revb values are correctly set.
-      self.assertEqual(reva, new_test_value['reva'])
-      self.assertEqual(revb, new_test_value['revb'])
-    else:
-      # Make sure there is an "sha1" value
-      self.assertTrue('sha1' in new_test_value)
-      # Make sure the sha1, reva and revb values have not changed.
-      self.assertEqual(original_test_value['sha1'], new_test_value['sha1'])
-      self.assertEqual(original_test_value['reva'], new_test_value['reva'])
-      self.assertEqual(original_test_value['revb'], new_test_value['revb'])
-
-
-class UpdatePerfExpectationsTest(unittest.TestCase):
-  def testFilterMatch(self):
-    """Verifies different regular expressions test filter."""
-    self.maxDiff = None
-    # Tests to update specified by a single literal string.
-    tests_to_update = 'win-release/media_tests_av_perf/fps/tulip2.webm'
-    expected_tests_list = ['win-release/media_tests_av_perf/fps/tulip2.webm']
-    self.assertEqual(expected_tests_list,
-                     upe_mod.GetMatchingTests(tests_to_update,
-                                              all_tests_keys))
-
-    # Tests to update specified by a single reg-ex
-    tests_to_update = 'win-release/media_tests_av_perf/fps.*'
-    expected_tests_list = ['win-release/media_tests_av_perf/fps/crowd1080.webm',
-                           'win-release/media_tests_av_perf/fps/crowd2160.webm',
-                           'win-release/media_tests_av_perf/fps/crowd360.webm',
-                           'win-release/media_tests_av_perf/fps/crowd480.webm',
-                           'win-release/media_tests_av_perf/fps/crowd720.webm',
-                           'win-release/media_tests_av_perf/fps/tulip2.m4a',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp3',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp4',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogg',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogv',
-                           'win-release/media_tests_av_perf/fps/tulip2.wav',
-                           'win-release/media_tests_av_perf/fps/tulip2.webm']
-    actual_list = upe_mod.GetMatchingTests(tests_to_update,
-                                           all_tests_keys)
-    actual_list.sort()
-    self.assertEqual(expected_tests_list, actual_list)
-
-    # Tests to update are specified by a single reg-ex, spanning multiple OSes.
-    tests_to_update = '.*-release/media_tests_av_perf/fps.*'
-    expected_tests_list = ['linux-release/media_tests_av_perf/fps/tulip2.m4a',
-                           'linux-release/media_tests_av_perf/fps/tulip2.mp3',
-                           'linux-release/media_tests_av_perf/fps/tulip2.mp4',
-                           'linux-release/media_tests_av_perf/fps/tulip2.ogg',
-                           'linux-release/media_tests_av_perf/fps/tulip2.ogv',
-                           'linux-release/media_tests_av_perf/fps/tulip2.wav',
-                           'win-release/media_tests_av_perf/fps/crowd1080.webm',
-                           'win-release/media_tests_av_perf/fps/crowd2160.webm',
-                           'win-release/media_tests_av_perf/fps/crowd360.webm',
-                           'win-release/media_tests_av_perf/fps/crowd480.webm',
-                           'win-release/media_tests_av_perf/fps/crowd720.webm',
-                           'win-release/media_tests_av_perf/fps/tulip2.m4a',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp3',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp4',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogg',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogv',
-                           'win-release/media_tests_av_perf/fps/tulip2.wav',
-                           'win-release/media_tests_av_perf/fps/tulip2.webm']
-    actual_list = upe_mod.GetMatchingTests(tests_to_update,
-                                           all_tests_keys)
-    actual_list.sort()
-    self.assertEqual(expected_tests_list, actual_list)
-
-  def testLinesFromInputFile(self):
-    """Verifies different string formats specified in input file."""
-
-    # Tests to update have been specified by a single literal string in
-    # an input file.
-    # Use the StringIO class to mock a file object.
-    lines_from_file = StringIO(
-        'win-release/media_tests_av_perf/fps/tulip2.webm')
-    contents = lines_from_file.read()
-    expected_tests_list = ['win-release/media_tests_av_perf/fps/tulip2.webm']
-    actual_list = upe_mod.GetTestsToUpdate(contents, all_tests_keys)
-    actual_list.sort()
-    self.assertEqual(expected_tests_list, actual_list)
-    lines_from_file.close()
-
-    # Tests to update specified by a single reg-ex in an input file.
-    lines_from_file = StringIO('win-release/media_tests_av_perf/fps/tulip2.*\n')
-    contents = lines_from_file.read()
-    expected_tests_list = ['win-release/media_tests_av_perf/fps/tulip2.m4a',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp3',
-                           'win-release/media_tests_av_perf/fps/tulip2.mp4',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogg',
-                           'win-release/media_tests_av_perf/fps/tulip2.ogv',
-                           'win-release/media_tests_av_perf/fps/tulip2.wav',
-                           'win-release/media_tests_av_perf/fps/tulip2.webm']
-    actual_list = upe_mod.GetTestsToUpdate(contents, all_tests_keys)
-    actual_list.sort()
-    self.assertEqual(expected_tests_list, actual_list)
-    lines_from_file.close()
-
-    # Tests to update specified by multiple lines in an input file.
-    lines_from_file = StringIO(
-        '.*-release/media_tests_av_perf/fps/tulip2.*\n'
-        'win-release/media_tests_av_perf/dropped_fps/tulip2.*\n'
-        'linux-release/media_tests_av_perf/audio_latency/latency')
-    contents = lines_from_file.read()
-    expected_tests_list = [
-        'linux-release/media_tests_av_perf/audio_latency/latency',
-        'linux-release/media_tests_av_perf/fps/tulip2.m4a',
-        'linux-release/media_tests_av_perf/fps/tulip2.mp3',
-        'linux-release/media_tests_av_perf/fps/tulip2.mp4',
-        'linux-release/media_tests_av_perf/fps/tulip2.ogg',
-        'linux-release/media_tests_av_perf/fps/tulip2.ogv',
-        'linux-release/media_tests_av_perf/fps/tulip2.wav',
-        'win-release/media_tests_av_perf/dropped_fps/tulip2.wav',
-        'win-release/media_tests_av_perf/dropped_fps/tulip2.webm',
-        'win-release/media_tests_av_perf/fps/tulip2.m4a',
-        'win-release/media_tests_av_perf/fps/tulip2.mp3',
-        'win-release/media_tests_av_perf/fps/tulip2.mp4',
-        'win-release/media_tests_av_perf/fps/tulip2.ogg',
-        'win-release/media_tests_av_perf/fps/tulip2.ogv',
-        'win-release/media_tests_av_perf/fps/tulip2.wav',
-        'win-release/media_tests_av_perf/fps/tulip2.webm']
-    actual_list = upe_mod.GetTestsToUpdate(contents, all_tests_keys)
-    actual_list.sort()
-    self.assertEqual(expected_tests_list, actual_list)
-    lines_from_file.close()
-
-  def testPreparingForUpdate(self):
-    """Verifies that tests to be modified are changed as expected."""
-    tests_to_update = [
-        'linux-release/media_tests_av_perf/audio_latency/latency',
-        'linux-release/media_tests_av_perf/fps/tulip2.m4a',
-        'linux-release/media_tests_av_perf/fps/tulip2.mp3',
-        'linux-release/media_tests_av_perf/fps/tulip2.mp4',
-        'linux-release/media_tests_av_perf/fps/tulip2.ogg',
-        'linux-release/media_tests_av_perf/fps/tulip2.ogv',
-        'linux-release/media_tests_av_perf/fps/tulip2.wav',
-        'win-release/media_tests_av_perf/dropped_fps/tulip2.wav',
-        'win-release/media_tests_av_perf/dropped_fps/tulip2.webm',
-        'win-release/media_tests_av_perf/fps/tulip2.mp3',
-        'win-release/media_tests_av_perf/fps/tulip2.mp4',
-        'win-release/media_tests_av_perf/fps/tulip2.ogg',
-        'win-release/media_tests_av_perf/fps/tulip2.ogv',
-        'win-release/media_tests_av_perf/fps/tulip2.wav',
-        'win-release/media_tests_av_perf/fps/tulip2.webm']
-    # Test regular positive integers.
-    reva = 12345
-    revb = 54321
-    VerifyPreparedTests(self, tests_to_update, reva, revb)
-    # Test negative values.
-    reva = -54321
-    revb = 12345
-    with self.assertRaises(ValueError):
-      upe_mod.PrepareTestsForUpdate(tests_to_update, all_tests, reva, revb)
-    # Test reva greater than revb.
-    reva = 54321
-    revb = 12345
-    upe_mod.PrepareTestsForUpdate(tests_to_update, all_tests, reva, revb)
-    # Test non-integer values
-    reva = 'sds'
-    revb = 12345
-    with self.assertRaises(ValueError):
-      upe_mod.PrepareTestsForUpdate(tests_to_update, all_tests, reva, revb)
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index 0ea7ac9..487ba26d 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -8,7 +8,7 @@
 namespace ui {
 
 namespace {
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_CHROMEOS)
 static bool kExposeLayoutTableAsDataTable = true;
 #else
 static bool kExposeLayoutTableAsDataTable = false;
@@ -120,6 +120,27 @@
   }
 }
 
+bool IsTableHeaderRole(ax::mojom::Role role) {
+  switch (role) {
+    case ax::mojom::Role::kColumnHeader:
+    case ax::mojom::Role::kRowHeader:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool IsTableRowRole(ax::mojom::Role role) {
+  switch (role) {
+    case ax::mojom::Role::kRow:
+      return true;
+    case ax::mojom::Role::kLayoutTableRow:
+      return kExposeLayoutTableAsDataTable;
+    default:
+      return false;
+  }
+}
+
 bool IsContainerWithSelectableChildrenRole(ax::mojom::Role role) {
   switch (role) {
     case ax::mojom::Role::kComboBoxGrouping:
diff --git a/ui/accessibility/ax_role_properties.h b/ui/accessibility/ax_role_properties.h
index a99c785..bfb756f9 100644
--- a/ui/accessibility/ax_role_properties.h
+++ b/ui/accessibility/ax_role_properties.h
@@ -32,6 +32,12 @@
 // Returns true if this node is a table, a grid or a treegrid.
 AX_EXPORT bool IsTableLikeRole(ax::mojom::Role role);
 
+// Returns true if this node is a table header.
+AX_EXPORT bool IsTableHeaderRole(ax::mojom::Role role);
+
+// Returns true if this node is a row.
+AX_EXPORT bool IsTableRowRole(ax::mojom::Role role);
+
 // Returns true if the provided role is selectable from the standpoint of UI
 // automation.
 AX_EXPORT bool IsUIASelectable(ax::mojom::Role role);
diff --git a/ui/base/accelerators/accelerator.h b/ui/base/accelerators/accelerator.h
index 1ea2afec..f5304b8e 100644
--- a/ui/base/accelerators/accelerator.h
+++ b/ui/base/accelerators/accelerator.h
@@ -38,6 +38,11 @@
   };
 
   Accelerator();
+  // |modifiers| consists of ui::EventFlags bitwise-or-ed together,
+  // for example:
+  //     Accelerator(ui::VKEY_Z, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)
+  // would correspond to the shortcut "ctrl + shift + z".
+  //
   // NOTE: this constructor strips out non key related flags.
   Accelerator(KeyboardCode key_code,
               int modifiers,
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 92ed2ff0..b3905bd 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -389,11 +389,8 @@
   void BeginMainFrameNotExpectedSoon() override;
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
-  void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
-                           const gfx::Vector2dF& outer_delta,
-                           const gfx::Vector2dF& elastic_overscroll_delta,
-                           float page_scale,
-                           float top_controls_delta) override {}
+  void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
+  }
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override {}
   void RequestNewLayerTreeFrameSink() override;
diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn
index 1fba4a9..4d1e88b6 100644
--- a/ui/display/BUILD.gn
+++ b/ui/display/BUILD.gn
@@ -56,6 +56,7 @@
 
   public_deps = [
     "//ui/display/types",
+    "//ui/gfx:color_space",
     "//ui/gfx:gfx",
   ]
 
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index 5626512..75bd864 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -218,6 +218,7 @@
 
   public_deps = [
     ":events_base",
+    "//ui/display",
     "//ui/latency",
   ]
   deps = [
@@ -225,7 +226,6 @@
     ":gesture_detection",
     "//base/third_party/dynamic_annotations",
     "//skia",
-    "//ui/display",
     "//ui/gfx",
     "//ui/gfx/geometry",
   ]
@@ -532,8 +532,8 @@
       "//ui/events/devices",
       "//ui/events/devices/mojo:test_interfaces",
       "//ui/events/gestures/blink",
-      "//ui/events/mojo:test_interfaces",
       "//ui/events/platform",
+      "//ui/gfx/geometry/mojo:struct_traits",
       "//ui/gfx/ipc/geometry",
     ]
 
diff --git a/ui/events/event.h b/ui/events/event.h
index 5bbc071..b9b4c72 100644
--- a/ui/events/event.h
+++ b/ui/events/event.h
@@ -34,6 +34,7 @@
 
 namespace ui {
 class CancelModeEvent;
+class Event;
 class EventTarget;
 class KeyEvent;
 class LocatedEvent;
@@ -42,11 +43,9 @@
 class PointerEvent;
 class ScrollEvent;
 class TouchEvent;
-enum class DomCode;
-class Event;
-class MouseWheelEvent;
 
-using ScopedEvent = std::unique_ptr<Event>;
+enum class DomCode;
+
 using PointerId = int32_t;
 
 class EVENTS_EXPORT Event {
diff --git a/ui/events/mojo/BUILD.gn b/ui/events/mojo/BUILD.gn
index 9515ba4..a4523c5 100644
--- a/ui/events/mojo/BUILD.gn
+++ b/ui/events/mojo/BUILD.gn
@@ -13,18 +13,7 @@
 
   public_deps = [
     "//mojo/public/mojom/base",
-    "//ui/gfx/mojo",
+    "//ui/gfx/geometry/mojo",
     "//ui/latency/mojo:interfaces",
   ]
 }
-
-mojom("test_interfaces") {
-  testonly = true
-  sources = [
-    "traits_test_service.mojom",
-  ]
-
-  public_deps = [
-    ":interfaces",
-  ]
-}
diff --git a/ui/events/mojo/event.mojom b/ui/events/mojo/event.mojom
index 739f984..9dafcc2 100644
--- a/ui/events/mojo/event.mojom
+++ b/ui/events/mojo/event.mojom
@@ -7,6 +7,7 @@
 import "mojo/public/mojom/base/time.mojom";
 import "ui/events/mojo/event_constants.mojom";
 import "ui/events/mojo/keyboard_codes.mojom";
+import "ui/gfx/geometry/mojo/geometry.mojom";
 import "ui/latency/mojo/latency_info.mojom";
 
 struct KeyData {
@@ -49,17 +50,10 @@
 };
 
 struct LocationData {
-  // |x| and |y| are in the coordinate system of the View.
-  // Typically, this will be an integer-valued translation w.r.t.
-  // the screen and in this case, |x| and |y| are in units of physical
-  // pixels. However, some View embedders may apply arbitrary transformations
-  // of a view w.r.t. the screen.
-  float x;
-  float y;
-  // |screen_x| and |screen_y| are in screen coordinates in units of
-  // physical pixels.
-  float screen_x;
-  float screen_y;
+  // |relative_location| is in the coordinate system of the target and in DIPs.
+  gfx.mojom.PointF relative_location;
+  // |root_location| is relative to the client's root and in dips.
+  gfx.mojom.PointF root_location;
 };
 
 // TODO(rjkroege,sadrul): Add gesture representation.
@@ -155,6 +149,14 @@
   int32 offset_y;
 };
 
+struct MouseData {
+  int32 changed_button_flags;
+  LocationData location;
+  PointerDetails pointer_details;
+  // Only used for mouse wheel.
+  gfx.mojom.Vector2d wheel_offset;
+};
+
 // This is used for TouchEvents.
 struct TouchData {
   bool may_cause_scrolling;
@@ -181,5 +183,6 @@
   GestureData? gesture_data;
   ScrollData? scroll_data;
   TouchData? touch_data;
+  MouseData? mouse_data;
   map<string, array<uint8>>? properties;
 };
diff --git a/ui/events/mojo/event.typemap b/ui/events/mojo/event.typemap
index 2adfd2b2..a652049b 100644
--- a/ui/events/mojo/event.typemap
+++ b/ui/events/mojo/event.typemap
@@ -8,6 +8,7 @@
 public_deps = [
   "//ui/events",
   "//ui/events:dom_keycode_converter",
+  "//ui/gfx/geometry/mojo",
   "//ui/latency/mojo:interfaces",
 ]
 deps = [
diff --git a/ui/events/mojo/event_struct_traits.cc b/ui/events/mojo/event_struct_traits.cc
index b7ee9a2..c5324bb 100644
--- a/ui/events/mojo/event_struct_traits.cc
+++ b/ui/events/mojo/event_struct_traits.cc
@@ -17,28 +17,22 @@
 
 namespace {
 
-ui::mojom::LocationDataPtr GetLocationData(const ui::LocatedEvent* event) {
+ui::mojom::LocationDataPtr CreateLocationData(const ui::LocatedEvent* event) {
   ui::mojom::LocationDataPtr location_data(ui::mojom::LocationData::New());
-  location_data->x = event->location_f().x();
-  location_data->y = event->location_f().y();
-  location_data->screen_x = event->root_location_f().x();
-  location_data->screen_y = event->root_location_f().y();
+  location_data->relative_location = event->location_f();
+  location_data->root_location = event->root_location_f();
   return location_data;
 }
 
 void UpdateEventLocation(const ui::mojom::PointerData& pointer_data,
                          EventUniquePtr* out) {
-  // TODO(katie): Update LocationData to have two PointF instead of storing this
-  // as four floats.
-  const gfx::PointF location(pointer_data.location->x,
-                             pointer_data.location->y);
-  const gfx::PointF screen_location(pointer_data.location->screen_x,
-                                    pointer_data.location->screen_y);
   // Set the float location, as the constructor only takes a gfx::Point.
   // This uses the event root_location field to store screen pixel
   // coordinates. See http://crbug.com/608547
-  out->get()->AsLocatedEvent()->set_location_f(location);
-  out->get()->AsLocatedEvent()->set_root_location_f(screen_location);
+  out->get()->AsLocatedEvent()->set_location_f(
+      pointer_data.location->relative_location);
+  out->get()->AsLocatedEvent()->set_root_location_f(
+      pointer_data.location->root_location);
 }
 
 bool ReadPointerDetailsDeprecated(ui::mojom::EventType event_type,
@@ -92,7 +86,8 @@
 
   *out = std::make_unique<ui::ScrollEvent>(
       mojo::ConvertTo<ui::EventType>(event->action()),
-      gfx::Point(scroll_data->location->x, scroll_data->location->y),
+      gfx::Point(scroll_data->location->relative_location.x(),
+                 scroll_data->location->relative_location.y()),
       time_stamp, event->flags(), scroll_data->x_offset, scroll_data->y_offset,
       scroll_data->x_offset_ordinal, scroll_data->y_offset_ordinal,
       scroll_data->finger_count, scroll_data->momentum_phase);
@@ -107,8 +102,8 @@
     return false;
 
   *out = std::make_unique<ui::GestureEvent>(
-      gesture_data->location->x, gesture_data->location->y, event->flags(),
-      time_stamp,
+      gesture_data->location->relative_location.x(),
+      gesture_data->location->relative_location.y(), event->flags(), time_stamp,
       ui::GestureEventDetails(ConvertTo<ui::EventType>(event->action())));
   return true;
 }
@@ -347,24 +342,14 @@
 ui::mojom::PointerDataPtr
 StructTraits<ui::mojom::EventDataView, EventUniquePtr>::pointer_data(
     const EventUniquePtr& event) {
-  if (!event->IsPointerEvent() && !event->IsMouseEvent())
+  if (!event->IsPointerEvent())
     return nullptr;
 
   ui::mojom::PointerDataPtr pointer_data(ui::mojom::PointerData::New());
-  const ui::PointerDetails* pointer_details;
-  if (event->IsPointerEvent()) {
-    const ui::PointerEvent* pointer_event = event->AsPointerEvent();
-    pointer_data->changed_button_flags = pointer_event->changed_button_flags();
-    pointer_data->location = GetLocationData(event->AsLocatedEvent());
-    pointer_details = &pointer_event->pointer_details();
-  } else {
-    // Mouse event
-    const ui::MouseEvent* mouse_event = event->AsMouseEvent();
-    pointer_data->changed_button_flags = mouse_event->changed_button_flags();
-    pointer_data->location = GetLocationData(mouse_event);
-    pointer_details = &mouse_event->pointer_details();
-  }
-
+  const ui::PointerEvent* pointer_event = event->AsPointerEvent();
+  const ui::PointerDetails* pointer_details = &pointer_event->pointer_details();
+  pointer_data->changed_button_flags = pointer_event->changed_button_flags();
+  pointer_data->location = CreateLocationData(event->AsLocatedEvent());
   pointer_data->pointer_id = pointer_details->id;
   ui::EventPointerType pointer_type = pointer_details->pointer_type;
 
@@ -403,8 +388,7 @@
   // TODO(rjkroege): Handle force-touch on MacOS
   // TODO(rjkroege): Adjust brush data appropriately for Android.
 
-  if (event->type() == ui::ET_POINTER_WHEEL_CHANGED ||
-      event->type() == ui::ET_MOUSEWHEEL) {
+  if (event->type() == ui::ET_POINTER_WHEEL_CHANGED) {
     ui::mojom::WheelDataPtr wheel_data(ui::mojom::WheelData::New());
 
     // TODO(rjkroege): Support page scrolling on windows by directly
@@ -430,6 +414,23 @@
 }
 
 // static
+ui::mojom::MouseDataPtr
+StructTraits<ui::mojom::EventDataView, EventUniquePtr>::mouse_data(
+    const EventUniquePtr& event) {
+  if (!event->IsMouseEvent())
+    return nullptr;
+
+  const ui::MouseEvent* mouse_event = event->AsMouseEvent();
+  ui::mojom::MouseDataPtr mouse_data(ui::mojom::MouseData::New());
+  mouse_data->changed_button_flags = mouse_event->changed_button_flags();
+  mouse_data->pointer_details = mouse_event->pointer_details();
+  mouse_data->location = CreateLocationData(mouse_event);
+  if (mouse_event->IsMouseWheelEvent())
+    mouse_data->wheel_offset = mouse_event->AsMouseWheelEvent()->offset();
+  return mouse_data;
+}
+
+// static
 ui::mojom::GestureDataPtr
 StructTraits<ui::mojom::EventDataView, EventUniquePtr>::gesture_data(
     const EventUniquePtr& event) {
@@ -437,7 +438,7 @@
     return nullptr;
 
   ui::mojom::GestureDataPtr gesture_data(ui::mojom::GestureData::New());
-  gesture_data->location = GetLocationData(event->AsLocatedEvent());
+  gesture_data->location = CreateLocationData(event->AsLocatedEvent());
   return gesture_data;
 }
 
@@ -449,7 +450,7 @@
     return nullptr;
 
   ui::mojom::ScrollDataPtr scroll_data(ui::mojom::ScrollData::New());
-  scroll_data->location = GetLocationData(event->AsLocatedEvent());
+  scroll_data->location = CreateLocationData(event->AsLocatedEvent());
   const ui::ScrollEvent* scroll_event = event->AsScrollEvent();
   scroll_data->x_offset = scroll_event->x_offset();
   scroll_data->y_offset = scroll_event->y_offset();
@@ -471,11 +472,7 @@
   ui::mojom::TouchDataPtr touch_data(ui::mojom::TouchData::New());
   touch_data->may_cause_scrolling = touch_event->may_cause_scrolling();
   touch_data->hovering = touch_event->hovering();
-  touch_data->location = ui::mojom::LocationData::New();
-  touch_data->location->x = touch_event->location_f().x();
-  touch_data->location->y = touch_event->location_f().y();
-  touch_data->location->screen_x = touch_event->root_location_f().x();
-  touch_data->location->screen_y = touch_event->root_location_f().y();
+  touch_data->location = CreateLocationData(touch_event);
   touch_data->pointer_details = touch_event->pointer_details();
   return touch_data;
 }
@@ -574,33 +571,28 @@
     case ui::mojom::EventType::MOUSE_EXITED_EVENT:
     case ui::mojom::EventType::MOUSE_WHEEL_EVENT:
     case ui::mojom::EventType::MOUSE_CAPTURE_CHANGED_EVENT: {
-      ui::mojom::PointerDataPtr pointer_data;
-      if (!event.ReadPointerData<ui::mojom::PointerDataPtr>(&pointer_data))
+      ui::mojom::MouseDataPtr mouse_data;
+      if (!event.ReadMouseData(&mouse_data))
         return false;
 
-      ui::PointerDetails pointer_details;
-      if (!ReadPointerDetailsDeprecated(event.action(), *pointer_data,
-                                        &pointer_details))
-        return false;
-
+      std::unique_ptr<ui::MouseEvent> mouse_event;
       if (event.action() == ui::mojom::EventType::MOUSE_WHEEL_EVENT) {
-        // Use the mouse event to construct a MouseWheel event to avoid asserts
-        // in ui::MouseEvent's constructor that ensure it is not used to create
-        // a wheel event.
-        ui::MouseEvent mouse_event(
-            ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), time_stamp,
-            event.flags(), pointer_data->changed_button_flags, pointer_details);
-        *out = std::make_unique<ui::MouseWheelEvent>(
-            mouse_event, pointer_details.offset.x(),
-            pointer_details.offset.y());
+        mouse_event = std::make_unique<ui::MouseWheelEvent>(
+            mouse_data->wheel_offset,
+            gfx::Point(),  // Real location set below.
+            gfx::Point(),  // Real location set below.
+            time_stamp, event.flags(), mouse_data->changed_button_flags);
       } else {
-        *out = std::make_unique<ui::MouseEvent>(
-            mojo::ConvertTo<ui::EventType>(event.action()), gfx::Point(),
-            gfx::Point(), time_stamp, event.flags(),
-            pointer_data->changed_button_flags, pointer_details);
+        mouse_event = std::make_unique<ui::MouseEvent>(
+            mojo::ConvertTo<ui::EventType>(event.action()),
+            gfx::Point(),  // Real location set below.
+            gfx::Point(),  // Real location set below.
+            time_stamp, event.flags(), mouse_data->changed_button_flags,
+            mouse_data->pointer_details);
       }
-
-      UpdateEventLocation(*pointer_data, out);
+      mouse_event->set_location_f(mouse_data->location->relative_location);
+      mouse_event->set_root_location_f(mouse_data->location->root_location);
+      *out = std::move(mouse_event);
       break;
     }
     case ui::mojom::EventType::TOUCH_RELEASED:
@@ -615,14 +607,12 @@
               mojo::ConvertTo<ui::EventType>(event.action()),
               gfx::Point(),  // Real location set below.
               time_stamp, touch_data->pointer_details, event.flags());
-      touch_event->set_location_f(
-          gfx::PointF(touch_data->location->x, touch_data->location->y));
-      touch_event->set_root_location_f(gfx::PointF(
-          touch_data->location->screen_x, touch_data->location->screen_y));
+      touch_event->set_location_f(touch_data->location->relative_location);
+      touch_event->set_root_location_f(touch_data->location->root_location);
       touch_event->set_may_cause_scrolling(touch_data->may_cause_scrolling);
       touch_event->set_hovering(touch_data->hovering);
       *out = std::move(touch_event);
-      return true;
+      break;
     }
     case ui::mojom::EventType::UNKNOWN:
       NOTREACHED() << "Using unknown event types closes connections";
diff --git a/ui/events/mojo/event_struct_traits.h b/ui/events/mojo/event_struct_traits.h
index c6b09d2..1e5d3fb1 100644
--- a/ui/events/mojo/event_struct_traits.h
+++ b/ui/events/mojo/event_struct_traits.h
@@ -46,6 +46,7 @@
   static ui::mojom::GestureDataPtr gesture_data(const EventUniquePtr& event);
   static ui::mojom::ScrollDataPtr scroll_data(const EventUniquePtr& event);
   static ui::mojom::TouchDataPtr touch_data(const EventUniquePtr& event);
+  static ui::mojom::MouseDataPtr mouse_data(const EventUniquePtr& event);
   static base::flat_map<std::string, std::vector<uint8_t>> properties(
       const EventUniquePtr& event);
   static bool Read(ui::mojom::EventDataView r, EventUniquePtr* out);
diff --git a/ui/events/mojo/struct_traits_unittest.cc b/ui/events/mojo/struct_traits_unittest.cc
index 7a774c0..2c4d62b 100644
--- a/ui/events/mojo/struct_traits_unittest.cc
+++ b/ui/events/mojo/struct_traits_unittest.cc
@@ -4,47 +4,21 @@
 
 #include <utility>
 
-#include "base/message_loop/message_loop.h"
 #include "base/stl_util.h"
 #include "mojo/public/cpp/base/time_mojom_traits.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/event.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/mojo/event.mojom.h"
 #include "ui/events/mojo/event_struct_traits.h"
-#include "ui/events/mojo/traits_test_service.mojom.h"
+#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
 #include "ui/latency/mojo/latency_info_struct_traits.h"
 
 namespace ui {
 
 namespace {
 
-class StructTraitsTest : public testing::Test, public mojom::TraitsTestService {
- public:
-  StructTraitsTest() {}
-  ~StructTraitsTest() override = default;
-
- protected:
-  mojom::TraitsTestServicePtr GetTraitsTestProxy() {
-    mojom::TraitsTestServicePtr proxy;
-    traits_test_bindings_.AddBinding(this, mojo::MakeRequest(&proxy));
-    return proxy;
-  }
-
- private:
-  // TraitsTestService:
-  void EchoEvent(std::unique_ptr<ui::Event> e,
-                 EchoEventCallback callback) override {
-    std::move(callback).Run(std::move(e));
-  }
-
-  base::MessageLoop loop_;
-  mojo::BindingSet<TraitsTestService> traits_test_bindings_;
-  DISALLOW_COPY_AND_ASSIGN(StructTraitsTest);
-};
-
 void ExpectTouchEventsEqual(const TouchEvent& expected,
                             const TouchEvent& actual) {
   EXPECT_EQ(expected.may_cause_scrolling(), actual.may_cause_scrolling());
@@ -64,6 +38,11 @@
   EXPECT_EQ(expected.changed_button_flags(), actual.changed_button_flags());
 }
 
+void ExpectMouseWheelEventsEqual(const MouseWheelEvent& expected,
+                                 const MouseWheelEvent& actual) {
+  EXPECT_EQ(expected.offset(), actual.offset());
+}
+
 void ExpectEventsEqual(const Event& expected, const Event& actual) {
   EXPECT_EQ(expected.type(), actual.type());
   EXPECT_EQ(expected.time_stamp(), actual.time_stamp());
@@ -77,6 +56,11 @@
     ASSERT_TRUE(actual.IsMouseEvent());
     ExpectMouseEventsEqual(*expected.AsMouseEvent(), *actual.AsMouseEvent());
   }
+  if (expected.IsMouseWheelEvent()) {
+    ASSERT_TRUE(actual.IsMouseWheelEvent());
+    ExpectMouseWheelEventsEqual(*expected.AsMouseWheelEvent(),
+                                *actual.AsMouseWheelEvent());
+  }
   if (expected.IsTouchEvent()) {
     ASSERT_TRUE(actual.IsTouchEvent());
     ExpectTouchEventsEqual(*expected.AsTouchEvent(), *actual.AsTouchEvent());
@@ -85,28 +69,28 @@
 
 }  // namespace
 
-TEST_F(StructTraitsTest, KeyEvent) {
+TEST(StructTraitsTest, KeyEvent) {
   const KeyEvent kTestData[] = {
       {ET_KEY_PRESSED, VKEY_RETURN, EF_CONTROL_DOWN},
       {ET_KEY_PRESSED, VKEY_MENU, EF_ALT_DOWN},
       {ET_KEY_RELEASED, VKEY_SHIFT, EF_SHIFT_DOWN},
       {ET_KEY_RELEASED, VKEY_MENU, EF_ALT_DOWN},
-      {ET_KEY_PRESSED, VKEY_A, ui::DomCode::US_A, EF_NONE},
-      {ET_KEY_PRESSED, VKEY_B, ui::DomCode::US_B,
-       EF_CONTROL_DOWN | EF_ALT_DOWN},
-      {'\x12', VKEY_2, ui::DomCode::NONE, EF_CONTROL_DOWN},
-      {'Z', VKEY_Z, ui::DomCode::NONE, EF_CAPS_LOCK_ON},
-      {'z', VKEY_Z, ui::DomCode::NONE, EF_NONE},
+      {ET_KEY_PRESSED, VKEY_A, DomCode::US_A, EF_NONE},
+      {ET_KEY_PRESSED, VKEY_B, DomCode::US_B, EF_CONTROL_DOWN | EF_ALT_DOWN},
+      {'\x12', VKEY_2, DomCode::NONE, EF_CONTROL_DOWN},
+      {'Z', VKEY_Z, DomCode::NONE, EF_CAPS_LOCK_ON},
+      {'z', VKEY_Z, DomCode::NONE, EF_NONE},
       {ET_KEY_PRESSED, VKEY_Z, EF_NONE,
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(101)},
-      {'Z', VKEY_Z, ui::DomCode::NONE, EF_NONE,
+      {'Z', VKEY_Z, DomCode::NONE, EF_NONE,
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(102)},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(kTestData[i]), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     EXPECT_TRUE(output->IsKeyEvent());
 
     const KeyEvent* output_key_event = output->AsKeyEvent();
@@ -123,7 +107,7 @@
   }
 }
 
-TEST_F(StructTraitsTest, PointerEvent) {
+TEST(StructTraitsTest, PointerEvent) {
   const PointerEvent kTestData[] = {
       // Mouse pointer events:
       {ET_POINTER_DOWN, gfx::Point(10, 10), gfx::Point(20, 30), EF_NONE, 0,
@@ -206,10 +190,11 @@
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(211)},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(kTestData[i]), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     EXPECT_TRUE(output->IsPointerEvent());
 
     const PointerEvent* output_ptr_event = output->AsPointerEvent();
@@ -227,7 +212,7 @@
   }
 }
 
-TEST_F(StructTraitsTest, MouseEvent) {
+TEST(StructTraitsTest, MouseEvent) {
   const MouseEvent kTestData[] = {
       {ET_MOUSE_PRESSED, gfx::Point(10, 10), gfx::Point(20, 30),
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(201), EF_NONE, 0,
@@ -264,17 +249,18 @@
                       MouseEvent::kMousePointerId)},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(kTestData[i]), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     ASSERT_TRUE(output->IsMouseEvent());
 
     ExpectEventsEqual(kTestData[i], *output);
   }
 }
 
-TEST_F(StructTraitsTest, PointerWheelEvent) {
+TEST(StructTraitsTest, PointerWheelEvent) {
   const MouseWheelEvent kTestData[] = {
       {gfx::Vector2d(11, 15), gfx::Point(3, 4), gfx::Point(40, 30),
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(301),
@@ -288,10 +274,12 @@
        EF_NONE},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy =
+        std::make_unique<PointerEvent>(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(ui::PointerEvent(kTestData[i])), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     EXPECT_EQ(ET_POINTER_WHEEL_CHANGED, output->type());
 
     const PointerEvent* output_pointer_event = output->AsPointerEvent();
@@ -306,7 +294,7 @@
   }
 }
 
-TEST_F(StructTraitsTest, MouseWheelEvent) {
+TEST(StructTraitsTest, MouseWheelEvent) {
   const MouseWheelEvent kTestData[] = {
       {gfx::Vector2d(11, 15), gfx::Point(3, 4), gfx::Point(40, 30),
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(301),
@@ -320,44 +308,40 @@
        EF_NONE},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy =
+        std::make_unique<MouseWheelEvent>(PointerEvent(kTestData[i]));
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(
-        Event::Clone(ui::MouseWheelEvent(ui::PointerEvent(kTestData[i]))),
-        &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     ASSERT_EQ(ET_MOUSEWHEEL, output->type());
 
     const MouseWheelEvent* output_event = output->AsMouseWheelEvent();
     // TODO(sky): make this use ExpectEventsEqual().
-    EXPECT_EQ(ET_MOUSEWHEEL, output_event->type());
-    EXPECT_EQ(kTestData[i].flags(), output_event->flags());
-    EXPECT_EQ(kTestData[i].location(), output_event->location());
-    EXPECT_EQ(kTestData[i].root_location(), output_event->root_location());
-    EXPECT_EQ(kTestData[i].offset(), output_event->offset());
-    EXPECT_EQ(kTestData[i].time_stamp(), output_event->time_stamp());
+    ExpectMouseWheelEventsEqual(kTestData[i], *output_event);
   }
 }
 
-TEST_F(StructTraitsTest, FloatingPointLocations) {
-  ui::MouseEvent input_event = ui::MouseEvent(
+TEST(StructTraitsTest, FloatingPointLocations) {
+  MouseEvent input_event(
       ET_MOUSE_PRESSED, gfx::Point(10, 10), gfx::Point(20, 30),
       base::TimeTicks() + base::TimeDelta::FromMicroseconds(201), EF_NONE, 0,
       PointerDetails(EventPointerType::POINTER_TYPE_MOUSE,
                      MouseEvent::kMousePointerId));
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   input_event.set_location_f(gfx::PointF(10.1, 10.2));
   input_event.set_root_location_f(gfx::PointF(20.2, 30.3));
 
+  std::unique_ptr<Event> expected_copy = Event::Clone(input_event);
   std::unique_ptr<Event> output;
-  proxy->EchoEvent(Event::Clone(input_event), &output);
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(&expected_copy,
+                                                                &output));
   ASSERT_TRUE(output->IsMouseEvent());
 
   ExpectEventsEqual(input_event, *output->AsMouseEvent());
 }
 
-TEST_F(StructTraitsTest, KeyEventPropertiesSerialized) {
+TEST(StructTraitsTest, KeyEventPropertiesSerialized) {
   KeyEvent key_event(ET_KEY_PRESSED, VKEY_T, EF_NONE);
   const std::string key = "key";
   const std::vector<uint8_t> value(0xCD, 2);
@@ -374,7 +358,7 @@
   EXPECT_EQ(properties, *(deserialized->AsKeyEvent()->properties()));
 }
 
-TEST_F(StructTraitsTest, GestureEvent) {
+TEST(StructTraitsTest, GestureEvent) {
   const GestureEvent kTestData[] = {
       {10, 20, EF_NONE,
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(401),
@@ -384,10 +368,11 @@
        GestureEventDetails(ET_GESTURE_TAP)},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(kTestData[i]), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     EXPECT_TRUE(output->IsGestureEvent());
 
     const GestureEvent* output_ptr_event = output->AsGestureEvent();
@@ -398,7 +383,7 @@
   }
 }
 
-TEST_F(StructTraitsTest, ScrollEvent) {
+TEST(StructTraitsTest, ScrollEvent) {
   const ScrollEvent kTestData[] = {
       {ET_SCROLL, gfx::Point(10, 20),
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(501), EF_NONE, 1,
@@ -430,10 +415,11 @@
        2, 3, 4, 5, EventMomentumPhase::END, ScrollEventPhase::kNone},
   };
 
-  mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   for (size_t i = 0; i < base::size(kTestData); i++) {
+    std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    proxy->EchoEvent(Event::Clone(kTestData[i]), &output);
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
+        &expected_copy, &output));
     EXPECT_TRUE(output->IsScrollEvent());
 
     const ScrollEvent* output_ptr_event = output->AsScrollEvent();
@@ -450,7 +436,7 @@
   }
 }
 
-TEST_F(StructTraitsTest, PointerDetails) {
+TEST(StructTraitsTest, PointerDetails) {
   const PointerDetails kTestData[] = {
       {EventPointerType::POINTER_TYPE_UNKNOWN, 1, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f},
       {EventPointerType::POINTER_TYPE_MOUSE, 1, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f},
@@ -467,13 +453,13 @@
     input.offset.set_y(i + 1);
 
     PointerDetails output;
-    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<ui::mojom::PointerDetails>(
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::PointerDetails>(
         &input, &output));
     EXPECT_EQ(input, output);
   }
 }
 
-TEST_F(StructTraitsTest, TouchEvent) {
+TEST(StructTraitsTest, TouchEvent) {
   const TouchEvent kTestData[] = {
       {ET_TOUCH_RELEASED,
        {1, 2},
@@ -488,7 +474,7 @@
   for (size_t i = 0; i < base::size(kTestData); i++) {
     std::unique_ptr<Event> expected_copy = Event::Clone(kTestData[i]);
     std::unique_ptr<Event> output;
-    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<ui::mojom::Event>(
+    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Event>(
         &expected_copy, &output));
     ExpectEventsEqual(*expected_copy, *output);
   }
@@ -501,20 +487,20 @@
   touch_event->set_hovering(true);
   std::unique_ptr<Event> expected = std::move(touch_event);
   std::unique_ptr<Event> output;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<ui::mojom::Event>(&expected,
-                                                                    &output));
+  ASSERT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::Event>(&expected, &output));
   ExpectEventsEqual(*expected, *output);
 }
 
-TEST_F(StructTraitsTest, UnserializedTouchEventFields) {
+TEST(StructTraitsTest, UnserializedTouchEventFields) {
   std::unique_ptr<TouchEvent> touch_event =
       std::make_unique<TouchEvent>(ET_TOUCH_CANCELLED, gfx::Point(),
                                    base::TimeTicks::Now(), PointerDetails());
   touch_event->set_should_remove_native_touch_id_mapping(true);
   std::unique_ptr<Event> expected = std::move(touch_event);
   std::unique_ptr<Event> output;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<ui::mojom::Event>(&expected,
-                                                                    &output));
+  ASSERT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::Event>(&expected, &output));
   ExpectEventsEqual(*expected, *output);
   // Have to set this back to false, else the destructor tries to access
   // state not setup in tests.
diff --git a/ui/events/mojo/traits_test_service.mojom b/ui/events/mojo/traits_test_service.mojom
deleted file mode 100644
index 1df4cbe..0000000
--- a/ui/events/mojo/traits_test_service.mojom
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module ui.mojom;
-
-import "ui/events/mojo/event.mojom";
-
-interface TraitsTestService {
-  [Sync]
-  EchoEvent(Event e) => (Event pass);
-};
diff --git a/ui/file_manager/gallery/js/gallery.js b/ui/file_manager/gallery/js/gallery.js
index 5cee684..34ddcf0 100644
--- a/ui/file_manager/gallery/js/gallery.js
+++ b/ui/file_manager/gallery/js/gallery.js
@@ -814,6 +814,9 @@
     selectedItem.touch();
     this.dataModel_.evictCache();
 
+    // Filename Edit field shows for anything selected.
+    this.filenameEdit_.hidden = false;
+
     // Update the title and the display name.
     if (numSelectedItems === 1) {
       document.title = this.selectedEntry_.name;
@@ -841,6 +844,7 @@
     }
   } else {
     document.title = '';
+    this.filenameEdit_.hidden = true;
     this.filenameEdit_.disabled = true;
     this.filenameEdit_.value = '';
     this.resizeRenameField_();
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index 140855b..5391ab5f 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -256,6 +256,57 @@
 };
 
 /**
+ * Tests opening Quick View on a Crostini file.
+ */
+testcase.openQuickViewCrostini = function() {
+  let appId;
+
+  const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
+  const realLinuxFiles = '#directory-tree [volume-type-icon="crostini"]';
+
+  StepsRunner.run([
+    // Open Files app on Downloads containing ENTRIES.photos.
+    function() {
+      setupAndWaitUntilReady(
+          null, RootPath.DOWNLOADS, this.next, [ENTRIES.photos], []);
+    },
+    // Check: the fake Linux files icon should be shown.
+    function(results) {
+      appId = results.windowId;
+      remoteCall.waitForElement(appId, fakeLinuxFiles).then(this.next);
+    },
+    // Add files to the Crostini volume.
+    function() {
+      addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET, this.next);
+    },
+    // Click the fake Linux files icon to mount the Crostini volume.
+    function() {
+      remoteCall.callRemoteTestUtil(
+          'fakeMouseClick', appId, [fakeLinuxFiles], this.next);
+    },
+    // Check: the Crostini volume icon should appear.
+    function(result) {
+      chrome.test.assertTrue(!!result, 'fakeMouseClick failed');
+      remoteCall.waitForElement(appId, realLinuxFiles).then(this.next);
+    },
+    // Check: the Crostini files should appear in the file list.
+    function() {
+      const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
+      remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true})
+          .then(this.next);
+    },
+    // Open a Crostini file in Quick View.
+    function() {
+      const openSteps = openQuickViewSteps(appId, ENTRIES.hello.nameText);
+      StepsRunner.run(openSteps).then(this.next);
+    },
+    function() {
+      checkIfNoErrorsOccured(this.next);
+    },
+  ]);
+};
+
+/**
  * Tests opening Quick View and scrolling its <webview> which contains a tall
  * text document.
  */
@@ -420,7 +471,7 @@
   const webView = ['#quick-view', '#dialog[open] webview.content'];
 
   StepsRunner.run([
-    // Open Files app on Downloads containing ENTRIES.tallText.
+    // Open Files app on Downloads containing ENTRIES.tallPdf.
     function() {
       setupAndWaitUntilReady(
           null, RootPath.DOWNLOADS, this.next, [ENTRIES.tallPdf], []);
@@ -467,7 +518,7 @@
     // Check: the <webview> embed type should be PDF mime type.
     function(type) {
       chrome.test.assertEq('application/pdf', type);
-      checkIfNoErrorsOccured(this.next);
+      this.next();
     },
     function() {
       checkIfNoErrorsOccured(this.next);
diff --git a/ui/file_manager/integration_tests/gallery/open_image_files.js b/ui/file_manager/integration_tests/gallery/open_image_files.js
index b6e69b53..4345a49 100644
--- a/ui/file_manager/integration_tests/gallery/open_image_files.js
+++ b/ui/file_manager/integration_tests/gallery/open_image_files.js
@@ -161,3 +161,27 @@
 testcase.openMultipleImagesAndChangeToSlideModeOnDownloads = function() {
   return openMultipleImagesAndChangeToSlideMode('local', 'downloads');
 };
+
+/**
+ * Runs a test to check whether the rename-input field is hidden after
+ * deleting the only selected image in the gallery.
+ * @return {Promise} Promise to be fulfilled with on success.
+ */
+testcase.deleteSingleOpenPhotoOnDownloads = () => {
+  const launchedPromise = launch('local', 'downloads', [ENTRIES.desktop]);
+  let appId;
+  return launchedPromise.then(args => {
+    appId = args.appId;
+    return gallery.waitForSlideImage(appId, 800, 600, 'My Desktop Background');
+  }).then(() => {
+    // Click the delete button.
+    return gallery.waitAndClickElement(appId, 'button.delete');
+  }).then(result => {
+      chrome.test.assertTrue(!!result);
+    // Wait and click delete button of confirmation dialog.
+    return gallery.waitAndClickElement(appId, '.cr-dialog-ok');
+  }).then(() => {
+    // Check: The edit name field should hide.
+    return gallery.waitForElement(appId, '#rename-input[hidden]');
+  });
+};
\ No newline at end of file
diff --git a/ui/file_manager/integration_tests/gallery/thumbnail_mode.js b/ui/file_manager/integration_tests/gallery/thumbnail_mode.js
index 010b73d5..0353efd 100644
--- a/ui/file_manager/integration_tests/gallery/thumbnail_mode.js
+++ b/ui/file_manager/integration_tests/gallery/thumbnail_mode.js
@@ -118,6 +118,9 @@
     chrome.test.assertTrue(!!result);
     // Wait until error banner is shown.
     return gallery.waitForElement(appId, '.gallery[error] .error-banner');
+  }).then(function() {
+    // Check: The edit name field should hide.
+    return gallery.waitForElement(appId, '#rename-input[hidden]');
   });
 }
 
@@ -161,6 +164,9 @@
     // Confirm slideshow button is disabled.
     return gallery.waitForElement(appId, 'button.slideshow[disabled]');
   }).then(function() {
+    // Check: The edit name field should hide.
+    return gallery.waitForElement(appId, '#rename-input[hidden]');
+  }).then(function() {
     // Switch back to slide mode by clicking mode button.
     return gallery.waitAndClickElement(appId, 'button.mode:not([disabled])');
   }).then(function(result) {
diff --git a/ui/gfx/color_palette.h b/ui/gfx/color_palette.h
index 0d55ae0a..4a465f55 100644
--- a/ui/gfx/color_palette.h
+++ b/ui/gfx/color_palette.h
@@ -22,6 +22,7 @@
 constexpr SkColor kGoogleBlue900 = SkColorSetRGB(0x17, 0x4E, 0xA6);
 constexpr SkColor kGoogleBlueDark600 = SkColorSetRGB(0x25, 0x81, 0xDF);
 constexpr SkColor kGoogleRed300 = SkColorSetRGB(0xF2, 0x8B, 0xB2);
+constexpr SkColor kGoogleRed500 = SkColorSetRGB(0xEA, 0x43, 0x35);
 constexpr SkColor kGoogleRed600 = SkColorSetRGB(0xD9, 0x30, 0x25);
 constexpr SkColor kGoogleRed700 = SkColorSetRGB(0xC5, 0x22, 0x1F);
 constexpr SkColor kGoogleRed800 = SkColorSetRGB(0xB3, 0x14, 0x12);
diff --git a/ui/gfx/mojo/selection_bound.typemap b/ui/gfx/mojo/selection_bound.typemap
index e2b16f7..d6bb094 100644
--- a/ui/gfx/mojo/selection_bound.typemap
+++ b/ui/gfx/mojo/selection_bound.typemap
@@ -6,3 +6,6 @@
 public_headers = [ "//ui/gfx/selection_bound.h" ]
 traits_headers = [ "//ui/gfx/mojo/selection_bound_struct_traits.h" ]
 type_mappings = [ "gfx.mojom.SelectionBound=gfx::SelectionBound" ]
+deps = [
+  "//ui/gfx/geometry/mojo:struct_traits",
+]
diff --git a/ui/gfx/mojo/selection_bound_struct_traits.h b/ui/gfx/mojo/selection_bound_struct_traits.h
index f4ba831..681de71 100644
--- a/ui/gfx/mojo/selection_bound_struct_traits.h
+++ b/ui/gfx/mojo/selection_bound_struct_traits.h
@@ -5,6 +5,7 @@
 #ifndef UI_GFX_MOJO_SELECTION_BOUND_STRUCT_TRAITS_H_
 #define UI_GFX_MOJO_SELECTION_BOUND_STRUCT_TRAITS_H_
 
+#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
 #include "ui/gfx/mojo/selection_bound.mojom-shared.h"
 #include "ui/gfx/selection_bound.h"
 
diff --git a/ui/latency/mojo/BUILD.gn b/ui/latency/mojo/BUILD.gn
index e8e2ba30..17c27b1 100644
--- a/ui/latency/mojo/BUILD.gn
+++ b/ui/latency/mojo/BUILD.gn
@@ -11,7 +11,6 @@
 
   public_deps = [
     "//mojo/public/mojom/base",
-    "//ui/gfx/geometry/mojo",
   ]
 }
 
diff --git a/ui/latency/mojo/DEPS b/ui/latency/mojo/DEPS
index 77a2762..733587e 100644
--- a/ui/latency/mojo/DEPS
+++ b/ui/latency/mojo/DEPS
@@ -1,5 +1,4 @@
 include_rules = [
   "+mojo/public",
-  "+ui/gfx/geometry/mojo",
   "+ui/latency",
 ]
diff --git a/ui/latency/mojo/latency_info.mojom b/ui/latency/mojo/latency_info.mojom
index a39a8ed..ee082c71 100644
--- a/ui/latency/mojo/latency_info.mojom
+++ b/ui/latency/mojo/latency_info.mojom
@@ -5,7 +5,6 @@
 module ui.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
-import "ui/gfx/geometry/mojo/geometry.mojom";
 
 enum LatencyComponentType {
   // ---------------------------BEGIN COMPONENT-------------------------------
diff --git a/ui/latency/mojo/latency_info.typemap b/ui/latency/mojo/latency_info.typemap
index 53c9bf6..4450bfe 100644
--- a/ui/latency/mojo/latency_info.typemap
+++ b/ui/latency/mojo/latency_info.typemap
@@ -13,7 +13,6 @@
   "latency_info_struct_traits.h",
 ]
 public_deps = [
-  "//ui/gfx/geometry/mojo:struct_traits",
   "//ui/latency",
 ]
 deps = [
diff --git a/ui/latency/mojo/latency_info_struct_traits.h b/ui/latency/mojo/latency_info_struct_traits.h
index e668e4d..00ab6f2 100644
--- a/ui/latency/mojo/latency_info_struct_traits.h
+++ b/ui/latency/mojo/latency_info_struct_traits.h
@@ -5,7 +5,6 @@
 #ifndef UI_LATENCY_MOJO_LATENCY_INFO_STRUCT_TRAITS_H_
 #define UI_LATENCY_MOJO_LATENCY_INFO_STRUCT_TRAITS_H_
 
-#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
 #include "ui/latency/latency_info.h"
 #include "ui/latency/mojo/latency_info.mojom-shared.h"
 
diff --git a/ui/ozone/platform/scenic/ozone_platform_scenic.cc b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
index 2684a83..fde7eea 100644
--- a/ui/ozone/platform/scenic/ozone_platform_scenic.cc
+++ b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
@@ -4,9 +4,14 @@
 
 #include "ui/ozone/platform/scenic/ozone_platform_scenic.h"
 
+#include <memory>
+#include <utility>
+#include <vector>
+
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop_current.h"
 #include "ui/base/cursor/ozone/bitmap_cursor_factory_ozone.h"
 #include "ui/display/manager/fake_display_delegate.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
@@ -46,13 +51,15 @@
 };
 
 // OzonePlatform for Scenic.
-class OzonePlatformScenic : public OzonePlatform {
+class OzonePlatformScenic
+    : public OzonePlatform,
+      public base::MessageLoopCurrent::DestructionObserver {
  public:
-  OzonePlatformScenic() : surface_factory_(&window_manager_) {}
+  OzonePlatformScenic()
+      : window_manager_(std::make_unique<ScenicWindowManager>()),
+        surface_factory_(window_manager_.get()) {}
   ~OzonePlatformScenic() override = default;
 
-  ScenicWindowManager* window_manager() { return &window_manager_; }
-
   // OzonePlatform implementation.
   ui::SurfaceFactoryOzone* GetSurfaceFactoryOzone() override {
     return &surface_factory_;
@@ -87,7 +94,8 @@
       return nullptr;
     }
     return std::make_unique<ScenicWindow>(
-        &window_manager_, delegate, std::move(properties.view_owner_request));
+        window_manager_.get(), delegate,
+        std::move(properties.view_owner_request));
   }
 
   const PlatformProperties& GetPlatformProperties() override {
@@ -101,7 +109,7 @@
   }
 
   std::unique_ptr<PlatformScreen> CreateScreen() override {
-    return window_manager_.CreateScreen();
+    return window_manager_->CreateScreen();
   }
 
   void InitializeUI(const InitParams& params) override {
@@ -114,12 +122,20 @@
     input_controller_ = CreateStubInputController();
     cursor_factory_ozone_ = std::make_unique<BitmapCursorFactoryOzone>();
     gpu_platform_support_host_.reset(CreateStubGpuPlatformSupportHost());
+
+    base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
   }
 
   void InitializeGPU(const InitParams& params) override {}
 
  private:
-  ScenicWindowManager window_manager_;
+  // Performs graceful cleanup tasks on main message loop teardown.
+  void Shutdown() { window_manager_.reset(); }
+
+  // base::MessageLoopCurrent::DestructionObserver implementation.
+  void WillDestroyCurrentMessageLoop() override { Shutdown(); }
+
+  std::unique_ptr<ScenicWindowManager> window_manager_;
   ScenicSurfaceFactory surface_factory_;
 
   std::unique_ptr<PlatformEventSource> platform_event_source_;
diff --git a/ui/ozone/platform/scenic/scenic_window.cc b/ui/ozone/platform/scenic/scenic_window.cc
index 28a9b23..c8622cd 100644
--- a/ui/ozone/platform/scenic/scenic_window.cc
+++ b/ui/ozone/platform/scenic/scenic_window.cc
@@ -260,7 +260,6 @@
   bool result = false;
 
   if (event.is_focus()) {
-    LOG(ERROR) << "RECEIVED FOCUS EVENT!";
     delegate_->OnActivationChanged(event.focus().focused);
     result = true;
   } else {
diff --git a/ui/ozone/platform/scenic/scenic_window_manager.cc b/ui/ozone/platform/scenic/scenic_window_manager.cc
index 9db874b..cf900fe 100644
--- a/ui/ozone/platform/scenic/scenic_window_manager.cc
+++ b/ui/ozone/platform/scenic/scenic_window_manager.cc
@@ -5,6 +5,7 @@
 #include "ui/ozone/platform/scenic/scenic_window_manager.h"
 
 #include "base/fuchsia/component_context.h"
+#include "ui/ozone/platform/scenic/ozone_platform_scenic.h"
 
 namespace ui {
 
@@ -22,8 +23,9 @@
   if (!view_manager_) {
     view_manager_ = base::fuchsia::ComponentContext::GetDefault()
                         ->ConnectToService<fuchsia::ui::viewsv1::ViewManager>();
-    view_manager_.set_error_handler(
-        [this]() { LOG(FATAL) << "ViewManager connection failed."; });
+    view_manager_.set_error_handler([this]() {
+      LOG(ERROR) << "The ViewManager channel was unexpectedly terminated.";
+    });
   }
 
   return view_manager_.get();
@@ -32,8 +34,9 @@
 fuchsia::ui::scenic::Scenic* ScenicWindowManager::GetScenic() {
   if (!scenic_) {
     GetViewManager()->GetScenic(scenic_.NewRequest());
-    scenic_.set_error_handler(
-        [this]() { LOG(FATAL) << "Scenic connection failed."; });
+    scenic_.set_error_handler([this]() {
+      LOG(ERROR) << "The Scenic channel was unexpectedly terminated.";
+    });
   }
   return scenic_.get();
 }
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index a57f45a..b7fa6d3 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -95,6 +95,7 @@
     "color_chooser/color_chooser_view.h",
     "context_menu_controller.h",
     "controls/animated_icon_view.h",
+    "controls/animated_image_view.h",
     "controls/button/blue_button.h",
     "controls/button/button.h",
     "controls/button/checkbox.h",
@@ -112,6 +113,7 @@
     "controls/focus_ring.h",
     "controls/focusable_border.h",
     "controls/image_view.h",
+    "controls/image_view_base.h",
     "controls/label.h",
     "controls/link.h",
     "controls/link_listener.h",
@@ -292,6 +294,7 @@
     "button_drag_utils.cc",
     "color_chooser/color_chooser_view.cc",
     "controls/animated_icon_view.cc",
+    "controls/animated_image_view.cc",
     "controls/button/blue_button.cc",
     "controls/button/button.cc",
     "controls/button/checkbox.cc",
@@ -307,6 +310,7 @@
     "controls/focus_ring.cc",
     "controls/focusable_border.cc",
     "controls/image_view.cc",
+    "controls/image_view_base.cc",
     "controls/label.cc",
     "controls/link.cc",
     "controls/menu/display_change_listener_mac.cc",
diff --git a/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc b/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
index 2e78f27..aa9e497 100644
--- a/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
+++ b/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
@@ -310,7 +310,7 @@
   EXPECT_EQ(height, height3);
 }
 
-TEST_F(AXSystemCaretWinTest, TestCaretMSAAEvents) {
+TEST_F(AXSystemCaretWinTest, DISABLED_TestCaretMSAAEvents) {
   TextfieldTestApi textfield_test_api(textfield_);
   Microsoft::WRL::ComPtr<IAccessible> caret_accessible;
   gfx::NativeWindow native_window = widget_->GetNativeWindow();
diff --git a/ui/views/controls/animated_image_view.cc b/ui/views/controls/animated_image_view.cc
new file mode 100644
index 0000000..2d56a88
--- /dev/null
+++ b/ui/views/controls/animated_image_view.cc
@@ -0,0 +1,139 @@
+// 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/views/controls/animated_image_view.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/skottie_wrapper.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+namespace {
+
+bool AreAnimatedImagesEqual(const gfx::SkiaVectorAnimation& animation_1,
+                            const gfx::SkiaVectorAnimation& animation_2) {
+  // In rare cases this may return false, even if the animated images are backed
+  // by the same resource file.
+  return animation_1.skottie() == animation_2.skottie();
+}
+
+}  // namespace
+
+AnimatedImageView::AnimatedImageView() = default;
+
+AnimatedImageView::~AnimatedImageView() = default;
+
+void AnimatedImageView::SetAnimatedImage(
+    std::unique_ptr<gfx::SkiaVectorAnimation> animated_image) {
+  if (animated_image_ &&
+      AreAnimatedImagesEqual(*animated_image, *animated_image_)) {
+    Stop();
+    return;
+  }
+
+  gfx::Size preferred_size(GetPreferredSize());
+  animated_image_ = std::move(animated_image);
+
+  // Stop the animation to reset it.
+  Stop();
+
+  if (preferred_size != GetPreferredSize())
+    PreferredSizeChanged();
+  SchedulePaint();
+}
+
+void AnimatedImageView::Play() {
+  DCHECK(animated_image_);
+  DCHECK_EQ(state_, State::kStopped);
+
+  state_ = State::kPlaying;
+
+  // We cannot play the animation unless we have a valid compositor.
+  if (!compositor_)
+    return;
+
+  // Ensure the class is added as an observer to receive clock ticks.
+  if (!compositor_->HasAnimationObserver(this))
+    compositor_->AddAnimationObserver(this);
+
+  animated_image_->Start();
+}
+
+void AnimatedImageView::Stop() {
+  DCHECK(animated_image_);
+  if (compositor_)
+    compositor_->RemoveAnimationObserver(this);
+
+  animated_image_->Stop();
+  state_ = State::kStopped;
+}
+
+gfx::Size AnimatedImageView::GetImageSize() const {
+  return image_size_.value_or(
+      animated_image_ ? animated_image_->GetOriginalSize() : gfx::Size());
+}
+
+void AnimatedImageView::OnPaint(gfx::Canvas* canvas) {
+  View::OnPaint(canvas);
+  if (!animated_image_)
+    return;
+  canvas->Save();
+  canvas->Translate(GetImageBounds().origin().OffsetFromOrigin());
+
+  // OnPaint may be called before clock tick was received; in that case just
+  // paint the first frame.
+  if (!previous_timestamp_.is_null() && state_ != State::kStopped)
+    animated_image_->Paint(canvas, previous_timestamp_, GetImageSize());
+  else
+    animated_image_->PaintFrame(canvas, 0, GetImageSize());
+
+  canvas->Restore();
+}
+
+const char* AnimatedImageView::GetClassName() const {
+  return "AnimatedImageView";
+}
+
+void AnimatedImageView::NativeViewHierarchyChanged() {
+  // When switching a window from one display to another, the compositor
+  // associated with the widget changes.
+  AddedToWidget();
+}
+
+void AnimatedImageView::AddedToWidget() {
+  ui::Compositor* compositor = GetWidget()->GetCompositor();
+  DCHECK(compositor);
+  if (compositor_ != compositor) {
+    if (compositor_ && compositor_->HasAnimationObserver(this))
+      compositor_->RemoveAnimationObserver(this);
+    compositor_ = compositor;
+  }
+}
+
+void AnimatedImageView::RemovedFromWidget() {
+  if (compositor_) {
+    Stop();
+    if (compositor_->HasAnimationObserver(this))
+      compositor_->RemoveAnimationObserver(this);
+    compositor_ = nullptr;
+  }
+}
+
+void AnimatedImageView::OnAnimationStep(base::TimeTicks timestamp) {
+  previous_timestamp_ = timestamp;
+  SchedulePaint();
+}
+
+void AnimatedImageView::OnCompositingShuttingDown(ui::Compositor* compositor) {
+  if (compositor_ == compositor) {
+    Stop();
+    compositor_->RemoveAnimationObserver(this);
+    compositor_ = nullptr;
+  }
+}
+
+}  // namespace views
diff --git a/ui/views/controls/animated_image_view.h b/ui/views/controls/animated_image_view.h
new file mode 100644
index 0000000..c62fe5c
--- /dev/null
+++ b/ui/views/controls/animated_image_view.h
@@ -0,0 +1,91 @@
+// 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_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_
+#define UI_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/gfx/skia_vector_animation.h"
+#include "ui/views/controls/image_view_base.h"
+
+namespace gfx {
+class SkiaVectorAnimation;
+class Canvas;
+}  // namespace gfx
+
+namespace ui {
+class Compositor;
+}
+
+namespace views {
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AnimatedImageView class.
+//
+// An AnimatedImageView can display a skia vector animation. The animation paint
+// size can be set via SetImageSize. The animation is stopped by default.
+// Use this over AnimatedIconView if you want to play a skottie animation file.
+//
+/////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT AnimatedImageView : public ImageViewBase,
+                                       public ui::CompositorAnimationObserver {
+ public:
+  enum class State {
+    kPlaying,  // The animation is currently playing.
+    kStopped   // The animation is stopped and paint will raster the first
+               // frame.
+  };
+
+  AnimatedImageView();
+  ~AnimatedImageView() override;
+
+  // Set the animated image that should be displayed. Setting an animated image
+  // will result in stopping the current animation.
+  void SetAnimatedImage(
+      std::unique_ptr<gfx::SkiaVectorAnimation> animated_image);
+
+  // Plays the animation in loop.
+  void Play();
+
+  // Stops any animation and resets it to the start frame.
+  void Stop();
+
+ private:
+  friend class AnimatedImageViewTest;
+
+  // Overridden from View:
+  void OnPaint(gfx::Canvas* canvas) override;
+  const char* GetClassName() const override;
+  void NativeViewHierarchyChanged() override;
+  void AddedToWidget() override;
+  void RemovedFromWidget() override;
+
+  // Overridden from ui::CompositorAnimationObserver:
+  void OnAnimationStep(base::TimeTicks timestamp) override;
+  void OnCompositingShuttingDown(ui::Compositor* compositor) override;
+
+  // Overridden from ImageViewBase:
+  gfx::Size GetImageSize() const override;
+
+  // The current state of the animation.
+  State state_ = State::kStopped;
+
+  // The compositor associated with the widget of this view.
+  ui::Compositor* compositor_ = nullptr;
+
+  // The most recent timestamp at which a paint was scheduled for this view.
+  base::TimeTicks previous_timestamp_;
+
+  // The underlying skia vector animation.
+  std::unique_ptr<gfx::SkiaVectorAnimation> animated_image_;
+
+  DISALLOW_COPY_AND_ASSIGN(AnimatedImageView);
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_
diff --git a/ui/views/controls/image_view.cc b/ui/views/controls/image_view.cc
index 1eb38138..2e9c406 100644
--- a/ui/views/controls/image_view.cc
+++ b/ui/views/controls/image_view.cc
@@ -7,12 +7,9 @@
 #include <utility>
 
 #include "base/logging.h"
-#include "base/strings/utf_string_conversions.h"
 #include "cc/paint/paint_flags.h"
 #include "skia/ext/image_operations.h"
-#include "ui/accessibility/ax_node_data.h"
 #include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/insets.h"
 
 namespace views {
 
@@ -29,13 +26,9 @@
 // static
 const char ImageView::kViewClassName[] = "ImageView";
 
-ImageView::ImageView()
-    : horizontal_alignment_(CENTER),
-      vertical_alignment_(CENTER),
-      last_paint_scale_(0.f),
-      last_painted_bitmap_pixels_(nullptr) {}
+ImageView::ImageView() = default;
 
-ImageView::~ImageView() {}
+ImageView::~ImageView() = default;
 
 void ImageView::SetImage(const gfx::ImageSkia& img) {
   if (IsImageEqual(img))
@@ -63,20 +56,6 @@
   return image_;
 }
 
-void ImageView::SetImageSize(const gfx::Size& image_size) {
-  image_size_ = image_size;
-  PreferredSizeChanged();
-}
-
-gfx::Rect ImageView::GetImageBounds() const {
-  gfx::Size image_size = GetImageSize();
-  return gfx::Rect(ComputeImageOrigin(image_size), image_size);
-}
-
-void ImageView::ResetImageSize() {
-  image_size_.reset();
-}
-
 bool ImageView::IsImageEqual(const gfx::ImageSkia& img) const {
   // Even though we copy ImageSkia in SetImage() the backing store
   // (ImageSkiaStorage) is not copied and may have changed since the last call
@@ -92,130 +71,15 @@
   return image_size_.value_or(image_.size());
 }
 
-gfx::Point ImageView::ComputeImageOrigin(const gfx::Size& image_size) const {
-  gfx::Insets insets = GetInsets();
-
-  int x = 0;
-  // In order to properly handle alignment of images in RTL locales, we need
-  // to flip the meaning of trailing and leading. For example, if the
-  // horizontal alignment is set to trailing, then we'll use left alignment for
-  // the image instead of right alignment if the UI layout is RTL.
-  Alignment actual_horizontal_alignment = horizontal_alignment_;
-  if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) {
-    actual_horizontal_alignment =
-        (horizontal_alignment_ == LEADING) ? TRAILING : LEADING;
-  }
-  switch (actual_horizontal_alignment) {
-    case LEADING:
-      x = insets.left();
-      break;
-    case TRAILING:
-      x = width() - insets.right() - image_size.width();
-      break;
-    case CENTER:
-      x = (width() - insets.width() - image_size.width()) / 2 + insets.left();
-      break;
-  }
-
-  int y = 0;
-  switch (vertical_alignment_) {
-    case LEADING:
-      y = insets.top();
-      break;
-    case TRAILING:
-      y = height() - insets.bottom() - image_size.height();
-      break;
-    case CENTER:
-      y = (height() - insets.height() - image_size.height()) / 2 + insets.top();
-      break;
-  }
-
-  return gfx::Point(x, y);
-}
-
 void ImageView::OnPaint(gfx::Canvas* canvas) {
   View::OnPaint(canvas);
   OnPaintImage(canvas);
 }
 
-void ImageView::SetAccessibleName(const base::string16& accessible_name) {
-  accessible_name_ = accessible_name;
-}
-
-base::string16 ImageView::GetAccessibleName() const {
-  return accessible_name_;
-}
-
-void ImageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  node_data->role = ax::mojom::Role::kImage;
-  node_data->SetName(accessible_name_);
-}
-
 const char* ImageView::GetClassName() const {
   return kViewClassName;
 }
 
-void ImageView::SetHorizontalAlignment(Alignment alignment) {
-  if (alignment != horizontal_alignment_) {
-    horizontal_alignment_ = alignment;
-    SchedulePaint();
-  }
-}
-
-ImageView::Alignment ImageView::GetHorizontalAlignment() const {
-  return horizontal_alignment_;
-}
-
-void ImageView::SetVerticalAlignment(Alignment alignment) {
-  if (alignment != vertical_alignment_) {
-    vertical_alignment_ = alignment;
-    SchedulePaint();
-  }
-}
-
-ImageView::Alignment ImageView::GetVerticalAlignment() const {
-  return vertical_alignment_;
-}
-
-// TODO(crbug.com/890465): Update the duplicate code here and in views::Button.
-void ImageView::SetTooltipText(const base::string16& tooltip) {
-  tooltip_text_ = tooltip;
-  if (accessible_name_.empty())
-    accessible_name_ = tooltip_text_;
-}
-
-base::string16 ImageView::GetTooltipText() const {
-  return tooltip_text_;
-}
-
-bool ImageView::GetTooltipText(const gfx::Point& p,
-                               base::string16* tooltip) const {
-  if (tooltip_text_.empty())
-    return false;
-
-  *tooltip = GetTooltipText();
-  return true;
-}
-
-gfx::Size ImageView::CalculatePreferredSize() const {
-  gfx::Size size = GetImageSize();
-  size.Enlarge(GetInsets().width(), GetInsets().height());
-  return size;
-}
-
-views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const {
-  // ImageView contains an image which is rastered at the device scale factor.
-  // By default, the paint commands are recorded at a scale factor slightly
-  // different from the device scale factor. Re-rastering the image at this
-  // paint recording scale will result in a distorted image. Paint recording
-  // scale might also not be uniform along the x & y axis, thus resulting in
-  // further distortion in the aspect ratio of the final image.
-  // |kUniformScaling| ensures that the paint recording scale is uniform along
-  // the x & y axis and keeps the scale equal to the device scale factor.
-  // See http://crbug.com/754010 for more details.
-  return views::PaintInfo::ScaleType::kUniformScaling;
-}
-
 void ImageView::OnPaintImage(gfx::Canvas* canvas) {
   last_paint_scale_ = canvas->image_scale();
   last_painted_bitmap_pixels_ = nullptr;
diff --git a/ui/views/controls/image_view.h b/ui/views/controls/image_view.h
index b13e494..e80a97a 100644
--- a/ui/views/controls/image_view.h
+++ b/ui/views/controls/image_view.h
@@ -6,9 +6,8 @@
 #define UI_VIEWS_CONTROLS_IMAGE_VIEW_H_
 
 #include "base/macros.h"
-#include "base/optional.h"
 #include "ui/gfx/image/image_skia.h"
-#include "ui/views/view.h"
+#include "ui/views/controls/image_view_base.h"
 
 namespace gfx {
 class Canvas;
@@ -26,17 +25,11 @@
 // provided image size.
 //
 /////////////////////////////////////////////////////////////////////////////
-class VIEWS_EXPORT ImageView : public View {
+class VIEWS_EXPORT ImageView : public ImageViewBase {
  public:
   // Internal class name.
   static const char kViewClassName[];
 
-  enum Alignment {
-    LEADING = 0,
-    CENTER,
-    TRAILING
-  };
-
   ImageView();
   ~ImageView() override;
 
@@ -52,39 +45,13 @@
   // The returned image is still owned by the ImageView.
   const gfx::ImageSkia& GetImage() const;
 
-  // Set the desired image size for the receiving ImageView.
-  void SetImageSize(const gfx::Size& image_size);
-
-  // Returns the actual bounds of the visible image inside the view.
-  gfx::Rect GetImageBounds() const;
-
-  // Reset the image size to the current image dimensions.
-  void ResetImageSize();
-
-  // Set / Get the horizontal alignment.
-  void SetHorizontalAlignment(Alignment ha);
-  Alignment GetHorizontalAlignment() const;
-
-  // Set / Get the vertical alignment.
-  void SetVerticalAlignment(Alignment va);
-  Alignment GetVerticalAlignment() const;
-
-  // Set / Get the tooltip text.
-  void SetTooltipText(const base::string16& tooltip);
-  base::string16 GetTooltipText() const;
-
-  // Set / Get the accessible name text.
-  void SetAccessibleName(const base::string16& name);
-  base::string16 GetAccessibleName() const;
-
-  // Overriden from View:
+  // Overridden from View:
   void OnPaint(gfx::Canvas* canvas) override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   const char* GetClassName() const override;
-  bool GetTooltipText(const gfx::Point& p,
-                      base::string16* tooltip) const override;
-  gfx::Size CalculatePreferredSize() const override;
-  views::PaintInfo::ScaleType GetPaintScaleType() const override;
+
+ protected:
+  // Overridden from ImageViewBase:
+  gfx::Size GetImageSize() const override;
 
  private:
   friend class ImageViewTest;
@@ -99,40 +66,18 @@
   // for this to return false even though the images are in fact equal.
   bool IsImageEqual(const gfx::ImageSkia& img) const;
 
-  // Returns the size the image will be painted.
-  gfx::Size GetImageSize() const;
-
-  // Compute the image origin given the desired size and the receiver alignment
-  // properties.
-  gfx::Point ComputeImageOrigin(const gfx::Size& image_size) const;
-
-  // The actual image size.
-  base::Optional<gfx::Size> image_size_;
-
   // The underlying image.
   gfx::ImageSkia image_;
 
   // Caches the scaled image reps.
   gfx::ImageSkia scaled_image_;
 
-  // Horizontal alignment.
-  Alignment horizontal_alignment_;
-
-  // Vertical alignment.
-  Alignment vertical_alignment_;
-
-  // The current tooltip text.
-  base::string16 tooltip_text_;
-
-  // The current accessible name text.
-  base::string16 accessible_name_;
-
   // Scale last painted at.
-  float last_paint_scale_;
+  float last_paint_scale_ = 0.f;
 
   // Address of bytes we last painted. This is used only for comparison, so its
   // safe to cache.
-  void* last_painted_bitmap_pixels_;
+  void* last_painted_bitmap_pixels_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ImageView);
 };
diff --git a/ui/views/controls/image_view_base.cc b/ui/views/controls/image_view_base.cc
new file mode 100644
index 0000000..f942680
--- /dev/null
+++ b/ui/views/controls/image_view_base.cc
@@ -0,0 +1,154 @@
+// 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/views/controls/image_view_base.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/geometry/insets.h"
+
+namespace views {
+
+ImageViewBase::ImageViewBase() = default;
+
+ImageViewBase::~ImageViewBase() = default;
+
+void ImageViewBase::SetImageSize(const gfx::Size& image_size) {
+  image_size_ = image_size;
+  image_origin_ = ComputeImageOrigin(GetImageSize());
+  PreferredSizeChanged();
+}
+
+gfx::Rect ImageViewBase::GetImageBounds() const {
+  return gfx::Rect(image_origin_, GetImageSize());
+}
+
+void ImageViewBase::ResetImageSize() {
+  image_size_.reset();
+  PreferredSizeChanged();
+  image_origin_ = ComputeImageOrigin(GetImageSize());
+}
+
+gfx::Point ImageViewBase::ComputeImageOrigin(
+    const gfx::Size& image_size) const {
+  gfx::Insets insets = GetInsets();
+
+  int x = 0;
+  // In order to properly handle alignment of images in RTL locales, we need
+  // to flip the meaning of trailing and leading. For example, if the
+  // horizontal alignment is set to trailing, then we'll use left alignment for
+  // the image instead of right alignment if the UI layout is RTL.
+  Alignment actual_horizontal_alignment = horizontal_alignment_;
+  if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) {
+    actual_horizontal_alignment =
+        (horizontal_alignment_ == LEADING) ? TRAILING : LEADING;
+  }
+  switch (actual_horizontal_alignment) {
+    case LEADING:
+      x = insets.left();
+      break;
+    case TRAILING:
+      x = width() - insets.right() - image_size.width();
+      break;
+    case CENTER:
+      x = (width() - insets.width() - image_size.width()) / 2 + insets.left();
+      break;
+  }
+
+  int y = 0;
+  switch (vertical_alignment_) {
+    case LEADING:
+      y = insets.top();
+      break;
+    case TRAILING:
+      y = height() - insets.bottom() - image_size.height();
+      break;
+    case CENTER:
+      y = (height() - insets.height() - image_size.height()) / 2 + insets.top();
+      break;
+  }
+
+  return gfx::Point(x, y);
+}
+
+void ImageViewBase::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  node_data->role = ax::mojom::Role::kImage;
+  node_data->SetName(tooltip_text_);
+}
+
+void ImageViewBase::SetHorizontalAlignment(Alignment alignment) {
+  if (alignment != horizontal_alignment_) {
+    horizontal_alignment_ = alignment;
+    image_origin_ = ComputeImageOrigin(GetImageSize());
+    SchedulePaint();
+  }
+}
+
+ImageViewBase::Alignment ImageViewBase::GetHorizontalAlignment() const {
+  return horizontal_alignment_;
+}
+
+void ImageViewBase::SetVerticalAlignment(Alignment alignment) {
+  if (alignment != vertical_alignment_) {
+    vertical_alignment_ = alignment;
+    image_origin_ = ComputeImageOrigin(GetImageSize());
+    SchedulePaint();
+  }
+}
+
+ImageViewBase::Alignment ImageViewBase::GetVerticalAlignment() const {
+  return vertical_alignment_;
+}
+
+void ImageViewBase::SetAccessibleName(const base::string16& accessible_name) {
+  accessible_name_ = accessible_name;
+}
+
+base::string16 ImageViewBase::GetAccessibleName() const {
+  return accessible_name_;
+}
+
+void ImageViewBase::SetTooltipText(const base::string16& tooltip) {
+  tooltip_text_ = tooltip;
+}
+
+base::string16 ImageViewBase::GetTooltipText() const {
+  return tooltip_text_;
+}
+
+bool ImageViewBase::GetTooltipText(const gfx::Point& p,
+                                   base::string16* tooltip) const {
+  if (tooltip_text_.empty())
+    return false;
+
+  *tooltip = GetTooltipText();
+  return true;
+}
+
+gfx::Size ImageViewBase::CalculatePreferredSize() const {
+  gfx::Size size = GetImageSize();
+  size.Enlarge(GetInsets().width(), GetInsets().height());
+  return size;
+}
+
+views::PaintInfo::ScaleType ImageViewBase::GetPaintScaleType() const {
+  // ImageViewBase contains an image which is rastered at the device scale
+  // factor. By default, the paint commands are recorded at a scale factor
+  // slightly different from the device scale factor. Re-rastering the image at
+  // this paint recording scale will result in a distorted image. Paint
+  // recording scale might also not be uniform along the x & y axis, thus
+  // resulting in further distortion in the aspect ratio of the final image.
+  // |kUniformScaling| ensures that the paint recording scale is uniform along
+  // the x & y axis and keeps the scale equal to the device scale factor.
+  // See http://crbug.com/754010 for more details.
+  return views::PaintInfo::ScaleType::kUniformScaling;
+}
+
+void ImageViewBase::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+  image_origin_ = ComputeImageOrigin(GetImageSize());
+}
+
+}  // namespace views
diff --git a/ui/views/controls/image_view_base.h b/ui/views/controls/image_view_base.h
new file mode 100644
index 0000000..250543d
--- /dev/null
+++ b/ui/views/controls/image_view_base.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 UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_
+#define UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "ui/views/view.h"
+
+namespace gfx {
+class Canvas;
+}
+
+namespace views {
+
+class VIEWS_EXPORT ImageViewBase : public View {
+ public:
+  enum Alignment { LEADING, CENTER, TRAILING };
+
+  ImageViewBase();
+  ~ImageViewBase() override;
+
+  // Set the desired image size for the receiving ImageView.
+  void SetImageSize(const gfx::Size& image_size);
+
+  // Returns the actual bounds of the visible image inside the view.
+  gfx::Rect GetImageBounds() const;
+
+  // Reset the image size to the current image dimensions.
+  void ResetImageSize();
+
+  // Set / Get the horizontal alignment.
+  void SetHorizontalAlignment(Alignment ha);
+  Alignment GetHorizontalAlignment() const;
+
+  // Set / Get the vertical alignment.
+  void SetVerticalAlignment(Alignment va);
+  Alignment GetVerticalAlignment() const;
+
+  // Set / Get the tooltip text.
+  void SetTooltipText(const base::string16& tooltip);
+  base::string16 GetTooltipText() const;
+
+  // Set / Get the accessible name text.
+  void SetAccessibleName(const base::string16& name);
+  base::string16 GetAccessibleName() const;
+
+  // Overridden from View:
+  void OnPaint(gfx::Canvas* canvas) override = 0;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  const char* GetClassName() const override = 0;
+  bool GetTooltipText(const gfx::Point& p,
+                      base::string16* tooltip) const override;
+  gfx::Size CalculatePreferredSize() const override;
+  views::PaintInfo::ScaleType GetPaintScaleType() const override;
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+
+ protected:
+  // Returns the size the image will be painted.
+  virtual gfx::Size GetImageSize() const = 0;
+
+  // The requested image size.
+  base::Optional<gfx::Size> image_size_;
+
+  // The origin of the image.
+  gfx::Point image_origin_;
+
+ private:
+  friend class ImageViewTest;
+
+  // Compute the image origin given the desired size and the receiver alignment
+  // properties.
+  gfx::Point ComputeImageOrigin(const gfx::Size& image_size) const;
+
+  // Horizontal alignment.
+  Alignment horizontal_alignment_ = Alignment::CENTER;
+
+  // Vertical alignment.
+  Alignment vertical_alignment_ = Alignment::CENTER;
+
+  // The current tooltip text.
+  base::string16 tooltip_text_;
+
+  // The current accessible name text.
+  base::string16 accessible_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageViewBase);
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_
diff --git a/ui/views/examples/BUILD.gn b/ui/views/examples/BUILD.gn
index c757e20b..0069d8c 100644
--- a/ui/views/examples/BUILD.gn
+++ b/ui/views/examples/BUILD.gn
@@ -9,6 +9,8 @@
   testonly = true
 
   sources = [
+    "animated_image_view_example.cc",
+    "animated_image_view_example.h",
     "box_layout_example.cc",
     "box_layout_example.h",
     "bubble_example.cc",
diff --git a/ui/views/examples/animated_image_view_example.cc b/ui/views/examples/animated_image_view_example.cc
new file mode 100644
index 0000000..4e52a95
--- /dev/null
+++ b/ui/views/examples/animated_image_view_example.cc
@@ -0,0 +1,143 @@
+// 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/views/examples/animated_image_view_example.h"
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/skottie_wrapper.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/animated_image_view.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/md_text_button.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+
+namespace views {
+namespace examples {
+
+namespace {
+
+// This class can load a skottie(and lottie) animation file from disk and play
+// it in a view as AnimatedImageView.
+// See https://skia.org/user/modules/skottie for more info on skottie.
+class AnimationGallery : public View,
+                         public TextfieldController,
+                         public ButtonListener {
+ public:
+  AnimationGallery()
+      : animated_image_view_(new AnimatedImageView()),
+        image_view_container_(new views::View()),
+        size_input_(new Textfield()),
+        file_chooser_(new Textfield()),
+        file_go_button_(
+            MdTextButton::Create(this, base::ASCIIToUTF16("Render"))) {
+    AddChildView(size_input_);
+
+    image_view_container_->AddChildView(animated_image_view_);
+    image_view_container_->SetLayoutManager(std::make_unique<FillLayout>());
+    animated_image_view_->SetBorder(
+        CreateSolidSidedBorder(1, 1, 1, 1, SK_ColorBLACK));
+    AddChildView(image_view_container_);
+
+    BoxLayout* box = SetLayoutManager(
+        std::make_unique<BoxLayout>(BoxLayout::kVertical, gfx::Insets(10), 10));
+    box->SetFlexForView(image_view_container_, 1);
+
+    file_chooser_->set_placeholder_text(
+        base::ASCIIToUTF16("Enter path to lottie JSON file"));
+    View* file_container = new View();
+    BoxLayout* file_box =
+        file_container->SetLayoutManager(std::make_unique<BoxLayout>(
+            BoxLayout::kHorizontal, gfx::Insets(10), 10));
+    file_container->AddChildView(file_chooser_);
+    file_container->AddChildView(file_go_button_);
+    file_box->SetFlexForView(file_chooser_, 1);
+    AddChildView(file_container);
+
+    size_input_->set_placeholder_text(
+        base::ASCIIToUTF16("Size in dip (Empty for default)"));
+    size_input_->set_controller(this);
+  }
+
+  ~AnimationGallery() override = default;
+
+  // TextfieldController:
+  void ContentsChanged(Textfield* sender,
+                       const base::string16& new_contents) override {
+    if (sender == size_input_) {
+      if (!base::StringToInt(new_contents, &size_) && (size_ > 0)) {
+        size_ = 0;
+        size_input_->SetText(base::string16());
+      }
+      Update();
+    }
+  }
+
+  // ButtonListener:
+  void ButtonPressed(Button* sender, const ui::Event& event) override {
+    DCHECK_EQ(file_go_button_, sender);
+    std::string json;
+    base::ScopedAllowBlockingForTesting allow_blocking;
+#if defined(OS_POSIX)
+    base::FilePath path(base::UTF16ToUTF8(file_chooser_->text()));
+#else
+    base::FilePath path(file_chooser_->text());
+#endif  // defined(OS_POSIX)
+    base::ReadFileToString(path, &json);
+
+    auto skottie = base::MakeRefCounted<gfx::SkottieWrapper>(
+        base::RefCountedString::TakeString(&json));
+    animated_image_view_->SetAnimatedImage(
+        std::make_unique<gfx::SkiaVectorAnimation>(skottie));
+    animated_image_view_->Play();
+    Update();
+  }
+
+ private:
+  void Update() {
+    if (size_ > 24)
+      animated_image_view_->SetImageSize(gfx::Size(size_, size_));
+    else
+      animated_image_view_->ResetImageSize();
+    Layout();
+  }
+
+  AnimatedImageView* animated_image_view_;
+  View* image_view_container_;
+  Textfield* size_input_;
+  Textfield* file_chooser_;
+  Button* file_go_button_;
+
+  int size_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(AnimationGallery);
+};
+
+}  // namespace
+
+AnimatedImageViewExample::AnimatedImageViewExample()
+    : ExampleBase("Animated Image View") {}
+
+AnimatedImageViewExample::~AnimatedImageViewExample() {}
+
+void AnimatedImageViewExample::CreateExampleView(View* container) {
+  container->SetLayoutManager(std::make_unique<FillLayout>());
+  container->AddChildView(new AnimationGallery());
+}
+
+}  // namespace examples
+}  // namespace views
diff --git a/ui/views/examples/animated_image_view_example.h b/ui/views/examples/animated_image_view_example.h
new file mode 100644
index 0000000..8bf30b5
--- /dev/null
+++ b/ui/views/examples/animated_image_view_example.h
@@ -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.
+
+#ifndef UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_
+#define UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_
+
+#include "base/macros.h"
+#include "ui/views/examples/example_base.h"
+
+namespace views {
+namespace examples {
+
+class VIEWS_EXAMPLES_EXPORT AnimatedImageViewExample : public ExampleBase {
+ public:
+  AnimatedImageViewExample();
+  ~AnimatedImageViewExample() override;
+
+  // ExampleBase:
+  void CreateExampleView(View* container) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AnimatedImageViewExample);
+};
+
+}  // namespace examples
+}  // namespace views
+
+#endif  // UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_
diff --git a/ui/views/examples/examples_window.cc b/ui/views/examples/examples_window.cc
index 0ceb1abd..683d5d59 100644
--- a/ui/views/examples/examples_window.cc
+++ b/ui/views/examples/examples_window.cc
@@ -18,6 +18,7 @@
 #include "ui/views/background.h"
 #include "ui/views/controls/combobox/combobox.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/examples/animated_image_view_example.h"
 #include "ui/views/examples/box_layout_example.h"
 #include "ui/views/examples/bubble_example.h"
 #include "ui/views/examples/button_example.h"
@@ -58,6 +59,7 @@
 // Creates the default set of examples.
 ExampleVector CreateExamples() {
   ExampleVector examples;
+  examples.push_back(std::make_unique<AnimatedImageViewExample>());
   examples.push_back(std::make_unique<BoxLayoutExample>());
   examples.push_back(std::make_unique<BubbleExample>());
   examples.push_back(std::make_unique<ButtonExample>());
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.html b/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
index a0a5253..f6203a2 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
@@ -135,7 +135,7 @@
         opacity: 1;
       }
 
-      .label {
+      #label {
         background: var(--google-blue-600);
         border-radius: 14px;
         bottom: 28px;
@@ -180,7 +180,7 @@
         <div id="bar"></div>
         <div id="markers" hidden$="[[!markerCount]]">
           <template is="dom-repeat" items="[[getMarkers_(markerCount)]]">
-            <div class$="[[getMarkerClass_(index, value, min, max,
+            <div class$="[[getMarkerClass_(index, immediateValue_, min, max,
                                            markerCount)]]"></div>
           </template>
         </div>
@@ -189,9 +189,7 @@
         <div id="knob" tabindex="0"></div>
       </div>
       <div id="labelContainer" aria-label="[[label_]]">
-        <div id="label" class="label">
-          <div id="labelText">[[label_]]</div>
-        </div>
+        <div id="label">[[label_]]</div>
       </div>
     </div>
   </template>
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
index 609b8ed..97033f5a 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
@@ -89,6 +89,27 @@
         type: Number,
         value: 0,
         notify: true,
+        observer: 'onValueChanged_',
+      },
+
+      /**
+       * If true, |value| is updated while dragging happens. If false, |value|
+       * is updated only once, when drag gesture finishes.
+       */
+      updateValueInstantly: {
+        type: Boolean,
+        value: true,
+      },
+
+      /**
+       * |immediateValue_| has the most up-to-date value and is used to render
+       * the slider UI. When dragging, |immediateValue_| is always updated, and
+       * |value| is updated at least once when dragging is stopped.
+       * @private
+       */
+      immediateValue_: {
+        type: Number,
+        value: 0,
       },
 
       /** @private */
@@ -111,8 +132,8 @@
     },
 
     observers: [
-      'updateLabelAndAria_(value, min, max, ticks.*)',
-      'updateKnobAndBar_(value, min, max)',
+      'updateLabelAndAria_(immediateValue_, min, max)',
+      'updateKnobAndBar_(immediateValue_, min, max)',
     ],
 
     listeners: {
@@ -177,9 +198,18 @@
      * @private
      */
     getRatio_: function() {
-      const clamped = clamp(this.min, this.max, this.value);
-      this.value = this.snaps ? Math.round(clamped) : clamped;
-      return (this.value - this.min) / (this.max - this.min);
+      return (this.immediateValue_ - this.min) / (this.max - this.min);
+    },
+
+    /** @private */
+    ensureValidValue_: function() {
+      if (this.immediateValue_ == undefined || this.value == undefined)
+        return;
+      let validValue = clamp(this.min, this.max, this.immediateValue_);
+      validValue = this.snaps ? Math.round(validValue) : validValue;
+      this.immediateValue_ = validValue;
+      if (!this.dragging || this.updateValueInstantly)
+        this.value = validValue;
     },
 
     /**
@@ -190,6 +220,7 @@
     stopDragging_: function(pointerId) {
       this.dragging = false;
       this.draggingEventTracker_.removeAll();
+      this.value = this.immediateValue_;
       // If there is a ripple animation in progress, setTimeout will hold off
       // on updating |holdDown_|.
       setTimeout(() => {
@@ -297,6 +328,22 @@
         this.max = this.ticks.length - 1;
         this.min = 0;
       }
+      this.ensureValidValue_();
+      this.updateLabelAndAria_();
+    },
+
+    /**
+     * Update |immediateValue_| which is used for rendering when |value| is
+     * updated either programmatically or from a keyboard input or a mouse drag
+     * (when |updateValueInstantly| is true).
+     * @private
+     */
+    onValueChanged_: function() {
+      if (this.immediateValue_ == this.value)
+        return;
+
+      this.immediateValue_ = this.value;
+      this.ensureValidValue_();
     },
 
     /** @private */
@@ -309,13 +356,13 @@
     /** @private */
     updateLabelAndAria_: function() {
       const ticks = this.ticks;
-      const index = this.value;
+      const index = this.immediateValue_;
       if (!ticks || ticks.length == 0 || index >= ticks.length ||
           !Number.isInteger(index) || !this.snaps) {
-        this.setAttribute('aria-valuetext', this.value);
+        this.setAttribute('aria-valuetext', index);
         this.setAttribute('aria-valuemin', this.min);
         this.setAttribute('aria-valuemax', this.max);
-        this.setAttribute('aria-valuenow', this.value);
+        this.setAttribute('aria-valuenow', index);
         return;
       }
       const tick = ticks[index];
@@ -359,9 +406,8 @@
       let ratio = (clientX - rect.left) / rect.width;
       if (this.isRtl_)
         ratio = 1 - ratio;
-      const newValue = ratio * (this.max - this.min) + this.min;
-      const clamped = clamp(this.min, this.max, newValue);
-      this.value = this.snaps ? Math.round(clamped) : clamped;
+      this.immediateValue_ = ratio * (this.max - this.min) + this.min;
+      this.ensureValidValue_();
     },
 
     _createRipple: function() {
diff --git a/webrunner/BUILD.gn b/webrunner/BUILD.gn
index 7c4a04b..e544f7d 100644
--- a/webrunner/BUILD.gn
+++ b/webrunner/BUILD.gn
@@ -174,6 +174,9 @@
 test("webrunner_browsertests") {
   sources = [
     "browser/context_impl_browsertest.cc",
+    "browser/frame_impl_browsertest.cc",
+    "browser/test_common.cc",
+    "browser/test_common.h",
     "browser/webrunner_browser_test.cc",
     "browser/webrunner_browser_test.h",
     "browser/webrunner_test_launcher.cc",
diff --git a/webrunner/browser/context_impl_browsertest.cc b/webrunner/browser/context_impl_browsertest.cc
index d075a42..6df087af 100644
--- a/webrunner/browser/context_impl_browsertest.cc
+++ b/webrunner/browser/context_impl_browsertest.cc
@@ -11,124 +11,42 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/common/url_constants.h"
 #include "net/cookies/cookie_store.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "net/test/embedded_test_server/http_request.h"
 #include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
-#include "webrunner/browser/frame_impl.h"
+#include "webrunner/browser/test_common.h"
 #include "webrunner/browser/webrunner_browser_test.h"
-#include "webrunner/browser/webrunner_url_request_context_getter.h"
 #include "webrunner/service/common.h"
 
 namespace webrunner {
 
 using testing::_;
-using testing::AllOf;
 using testing::Field;
 using testing::InvokeWithoutArgs;
-using testing::Mock;
 
-using chromium::web::NavigationEvent;
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
 
-const char kPage1Path[] = "/title1.html";
-const char kPage2Path[] = "/title2.html";
-const char kPage1Title[] = "title 1";
-const char kPage2Title[] = "title 2";
-const char kDataUrl[] =
-    "data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
-
-MATCHER(IsSet, "Checks if an optional field is set.") {
-  return !arg.is_null();
-}
-
-// Defines mock methods used by tests to observe NavigationStateChangeEvents
-// and lower-level WebContentsObserver events.
-class MockNavigationObserver : public chromium::web::NavigationEventObserver,
-                               public content::WebContentsObserver {
- public:
-  using content::WebContentsObserver::Observe;
-
-  MockNavigationObserver() = default;
-  ~MockNavigationObserver() override = default;
-
-  // Acknowledges processing of the most recent OnNavigationStateChanged call.
-  void Acknowledge() {
-    DCHECK(navigation_ack_callback_);
-    std::move(navigation_ack_callback_)();
-
-    // Pump the acknowledgement message over IPC.
-    base::RunLoop().RunUntilIdle();
-  }
-
-  MOCK_METHOD1(MockableOnNavigationStateChanged,
-               void(chromium::web::NavigationEvent change));
-
-  // chromium::web::NavigationEventObserver implementation.
-  // Proxies calls to MockableOnNavigationStateChanged(), because GMock does
-  // not work well with fit::Callbacks inside mocked actions.
-  void OnNavigationStateChanged(
-      chromium::web::NavigationEvent change,
-      OnNavigationStateChangedCallback callback) override {
-    MockableOnNavigationStateChanged(std::move(change));
-    navigation_ack_callback_ = std::move(callback);
-  }
-
-  // WebContentsObserver implementation.
-  MOCK_METHOD2(DidFinishLoad,
-               void(content::RenderFrameHost* render_frame_host,
-                    const GURL& validated_url));
-
- private:
-  OnNavigationStateChangedCallback navigation_ack_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(MockNavigationObserver);
-};
-
+// Defines a suite of tests that exercise browser-level configuration and
+// functionality.
 class ContextImplTest : public WebRunnerBrowserTest {
  public:
   ContextImplTest() : navigation_observer_binding_(&navigation_observer_) {}
   ~ContextImplTest() = default;
 
-  MOCK_METHOD1(OnServeHttpRequest,
-               void(const net::test_server::HttpRequest& request));
-
  protected:
+  // Creates a Frame with |navigation_observer_| attached.
   chromium::web::FramePtr CreateFrame() {
-    chromium::web::FramePtr frame;
-    context()->CreateFrame(frame.NewRequest());
-    frame->SetNavigationEventObserver(
-        navigation_observer_binding_.NewBinding());
-    base::RunLoop().RunUntilIdle();
-    return frame;
+    return WebRunnerBrowserTest::CreateFrame(&navigation_observer_);
   }
 
   // Synchronously gets a list of cookies for this BrowserContext.
   net::CookieList GetCookies();
 
-  // Navigates a |controller| to |url|, blocking until navigation is complete.
-  void CheckLoadUrl(const std::string& url,
-                    const std::string& expected_title,
-                    bool is_error,
-                    chromium::web::NavigationController* controller) {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(testing::AllOf(
-                    Field(&NavigationEvent::is_error, is_error),
-                    Field(&NavigationEvent::title, expected_title),
-                    Field(&NavigationEvent::url, url))))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(url, nullptr);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-    navigation_observer_.Acknowledge();
-  }
-
   void TearDownOnMainThread() override {
     navigation_observer_binding_.Unbind();
   }
@@ -141,471 +59,6 @@
   DISALLOW_COPY_AND_ASSIGN(ContextImplTest);
 };
 
-class WebContentsDeletionObserver : public content::WebContentsObserver {
- public:
-  explicit WebContentsDeletionObserver(content::WebContents* web_contents)
-      : content::WebContentsObserver(web_contents) {}
-
-  MOCK_METHOD1(RenderViewDeleted,
-               void(content::RenderViewHost* render_view_host));
-};
-
-// Verifies that the browser will navigate and generate a navigation observer
-// event when LoadUrl() is called.
-IN_PROC_BROWSER_TEST_F(ContextImplTest, NavigateFrame) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, false,
-               controller.get());
-
-  frame.Unbind();
-}
-
-// Tests that navigation errors are reported as navigation events,
-// with the original URL that caused the error.
-IN_PROC_BROWSER_TEST_F(ContextImplTest, NavigateError) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  CheckLoadUrl("http://unresolvable.foo.google.com/foo",
-               "unresolvable.foo.google.com/foo", true, controller.get());
-
-  CheckLoadUrl("http://unresolvable.foo.google.com/foo2",
-               "unresolvable.foo.google.com/foo2", true, controller.get());
-
-  CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, false,
-               controller.get());
-
-  frame.Unbind();
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, NavigateDataFrame) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  CheckLoadUrl(kDataUrl, kDataUrl, false, controller.get());
-
-  frame.Unbind();
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, FrameDeletedBeforeContext) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  // Process the frame creation message.
-  base::RunLoop().RunUntilIdle();
-
-  FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
-  WebContentsDeletionObserver deletion_observer(
-      frame_impl->web_contents_for_test());
-  base::RunLoop run_loop;
-  EXPECT_CALL(deletion_observer, RenderViewDeleted(_))
-      .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-  controller->LoadUrl(url::kAboutBlankURL, nullptr);
-
-  frame.Unbind();
-  run_loop.Run();
-
-  // Check that |context| remains bound after the frame is closed.
-  EXPECT_TRUE(context());
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, ContextDeletedBeforeFrame) {
-  chromium::web::FramePtr frame = CreateFrame();
-  EXPECT_TRUE(frame);
-
-  base::RunLoop run_loop;
-  frame.set_error_handler([&run_loop]() { run_loop.Quit(); });
-  context().Unbind();
-  run_loop.Run();
-  EXPECT_FALSE(frame);
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, GoBackAndForward) {
-  chromium::web::FramePtr frame = CreateFrame();
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL title1(embedded_test_server()->GetURL(kPage1Path));
-  GURL title2(embedded_test_server()->GetURL(kPage2Path));
-
-  CheckLoadUrl(title1.spec(), kPage1Title, false, controller.get());
-  CheckLoadUrl(title2.spec(), kPage2Title, false, controller.get());
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-    controller->GoBack();
-    run_loop.Run();
-    navigation_observer_.Acknowledge();
-  }
-
-  // At the top of the navigation entry list; this should be a no-op.
-  controller->GoBack();
-
-  // Process the navigation request message.
-  base::RunLoop().RunUntilIdle();
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage2Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-    controller->GoForward();
-    run_loop.Run();
-    navigation_observer_.Acknowledge();
-  }
-
-  // At the end of the navigation entry list; this should be a no-op.
-  controller->GoForward();
-
-  // Process the navigation request message.
-  base::RunLoop().RunUntilIdle();
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, ReloadFrame) {
-  chromium::web::FramePtr frame = CreateFrame();
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
-      &ContextImplTest::OnServeHttpRequest, base::Unretained(this)));
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url(embedded_test_server()->GetURL(kPage1Path));
-
-  EXPECT_CALL(*this, OnServeHttpRequest(_));
-  CheckLoadUrl(url.spec(), kPage1Title, false, controller.get());
-
-  navigation_observer_.Observe(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
-
-  // Reload with NO_CACHE.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*this, OnServeHttpRequest(_));
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->Reload(chromium::web::ReloadType::NO_CACHE);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-    navigation_observer_.Acknowledge();
-  }
-  // Reload with PARTIAL_CACHE.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(*this, OnServeHttpRequest(_));
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->Reload(chromium::web::ReloadType::PARTIAL_CACHE);
-    run_loop.Run();
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, GetVisibleEntry) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  // Verify that a Frame returns a null NavigationEntry prior to receiving any
-  // LoadUrl() calls.
-  {
-    base::RunLoop run_loop;
-    controller->GetVisibleEntry(
-        [&run_loop](std::unique_ptr<chromium::web::NavigationEntry> details) {
-          EXPECT_EQ(nullptr, details.get());
-          run_loop.Quit();
-        });
-    run_loop.Run();
-  }
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL title1(embedded_test_server()->GetURL(kPage1Path));
-  GURL title2(embedded_test_server()->GetURL(kPage2Path));
-
-  // Navigate to a page.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-    controller->LoadUrl(title1.spec(), nullptr);
-    run_loop.Run();
-    navigation_observer_.Acknowledge();
-  }
-
-  // Verify that GetVisibleEntry() reflects the new Frame navigation state.
-  {
-    base::RunLoop run_loop;
-    controller->GetVisibleEntry(
-        [&run_loop,
-         &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
-          EXPECT_TRUE(details);
-          EXPECT_EQ(details->url, title1.spec());
-          EXPECT_EQ(details->title, kPage1Title);
-          run_loop.Quit();
-        });
-    run_loop.Run();
-  }
-
-  // Navigate to another page.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage2Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-    controller->LoadUrl(title2.spec(), nullptr);
-    run_loop.Run();
-    navigation_observer_.Acknowledge();
-  }
-
-  // Verify the navigation with GetVisibleEntry().
-  {
-    base::RunLoop run_loop;
-    controller->GetVisibleEntry(
-        [&run_loop,
-         &title2](std::unique_ptr<chromium::web::NavigationEntry> details) {
-          EXPECT_TRUE(details);
-          EXPECT_EQ(details->url, title2.spec());
-          EXPECT_EQ(details->title, kPage2Title);
-          run_loop.Quit();
-        });
-    run_loop.Run();
-  }
-
-  // Navigate back to the first page.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
-    controller->GoBack();
-    run_loop.Run();
-    navigation_observer_.Acknowledge();
-  }
-
-  // Verify the navigation with GetVisibleEntry().
-  {
-    base::RunLoop run_loop;
-    controller->GetVisibleEntry(
-        [&run_loop,
-         &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
-          EXPECT_TRUE(details);
-          EXPECT_EQ(details->url, title1.spec());
-          EXPECT_EQ(details->title, kPage1Title);
-          run_loop.Quit();
-        });
-    run_loop.Run();
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, NoNavigationObserverAttached) {
-  chromium::web::FramePtr frame;
-  context()->CreateFrame(frame.NewRequest());
-  base::RunLoop().RunUntilIdle();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL title1(embedded_test_server()->GetURL(kPage1Path));
-  GURL title2(embedded_test_server()->GetURL(kPage2Path));
-
-  navigation_observer_.Observe(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title1.spec(), nullptr);
-    run_loop.Run();
-  }
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title2.spec(), nullptr);
-    run_loop.Run();
-  }
-}
-
-// Verifies that a Frame will handle navigation observer disconnection events
-// gracefully.
-IN_PROC_BROWSER_TEST_F(ContextImplTest, NavigationObserverDisconnected) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL title1(embedded_test_server()->GetURL(kPage1Path));
-  GURL title2(embedded_test_server()->GetURL(kPage2Path));
-
-  navigation_observer_.Observe(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1));
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title1.spec(), nullptr);
-    run_loop.Run();
-  }
-
-  // Disconnect the observer & spin the runloop to propagate the disconnection
-  // event over IPC.
-  navigation_observer_binding_.Unbind();
-  base::RunLoop().RunUntilIdle();
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title2.spec(), nullptr);
-    run_loop.Run();
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, DISABLED_DelayedNavigationEventAck) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL title1(embedded_test_server()->GetURL(kPage1Path));
-  GURL title2(embedded_test_server()->GetURL(kPage2Path));
-
-  // Expect an navigation event here, but deliberately postpone acknowledgement
-  // until the end of the test.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title1.spec(), nullptr);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-  }
-
-  // Since we have blocked NavigationEventObserver's flow, we must observe the
-  // WebContents events directly via a test-only seam.
-  navigation_observer_.Observe(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
-
-  // Navigate to a second page.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title2.spec(), nullptr);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-  }
-
-  // Navigate to the first page.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(title1.spec(), nullptr);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-  }
-
-  // Since there was no observable change in navigation state since the last
-  // ack, there should be no more NavigationEvents generated.
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_,
-                MockableOnNavigationStateChanged(
-                    testing::AllOf(Field(&NavigationEvent::title, kPage1Title),
-                                   Field(&NavigationEvent::url, IsSet()))))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    navigation_observer_.Acknowledge();
-    run_loop.Run();
-  }
-}
-
-// Observes events specific to the Stop() test case.
-struct WebContentsObserverForStop : public content::WebContentsObserver {
-  using content::WebContentsObserver::Observe;
-  MOCK_METHOD1(DidStartNavigation, void(content::NavigationHandle*));
-  MOCK_METHOD0(NavigationStopped, void());
-};
-
-IN_PROC_BROWSER_TEST_F(ContextImplTest, Stop) {
-  chromium::web::FramePtr frame = CreateFrame();
-
-  chromium::web::NavigationControllerPtr controller;
-  frame->GetNavigationController(controller.NewRequest());
-
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  // Use a request handler that will accept the connection and stall
-  // indefinitely.
-  GURL hung_url(embedded_test_server()->GetURL("/hung"));
-
-  WebContentsObserverForStop observer;
-  observer.Observe(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(observer, DidStartNavigation(_))
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->LoadUrl(hung_url.spec(), nullptr);
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-  }
-
-  EXPECT_TRUE(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
-
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(observer, NavigationStopped())
-        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-    controller->Stop();
-    run_loop.Run();
-    Mock::VerifyAndClearExpectations(this);
-  }
-
-  EXPECT_FALSE(
-      context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
-}
-
 void OnCookiesReceived(net::CookieList* output,
                        base::OnceClosure on_received_cb,
                        const net::CookieList& cookies) {
@@ -701,8 +154,13 @@
   chromium::web::NavigationControllerPtr controller;
   frame->GetNavigationController(controller.NewRequest());
 
-  CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, false,
-               controller.get());
+  base::RunLoop run_loop;
+  EXPECT_CALL(navigation_observer_,
+              MockableOnNavigationStateChanged(
+                  Field(&NavigationDetails::url, url::kAboutBlankURL)))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+  controller->LoadUrl(url::kAboutBlankURL, nullptr);
+  run_loop.Run();
 
   frame.Unbind();
 }
@@ -715,14 +173,12 @@
   chromium::web::NavigationControllerPtr nav;
   frame->GetNavigationController(nav.NewRequest());
 
-  {
-    base::RunLoop run_loop;
-    EXPECT_CALL(navigation_observer_, MockableOnNavigationStateChanged(_))
-        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+  base::RunLoop run_loop;
+  EXPECT_CALL(navigation_observer_, MockableOnNavigationStateChanged(_))
+      .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
 
-    nav->LoadUrl(cookie_url.spec(), nullptr);
-    run_loop.Run();
-  }
+  nav->LoadUrl(cookie_url.spec(), nullptr);
+  run_loop.Run();
 
   auto cookies = GetCookies();
   bool found = false;
diff --git a/webrunner/browser/frame_impl.cc b/webrunner/browser/frame_impl.cc
index ecf931ed..f7c21a9 100644
--- a/webrunner/browser/frame_impl.cc
+++ b/webrunner/browser/frame_impl.cc
@@ -17,6 +17,7 @@
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host_platform.h"
 #include "ui/platform_window/platform_window_init_properties.h"
+#include "ui/wm/core/base_focus_rules.h"
 #include "url/gurl.h"
 #include "webrunner/browser/context_impl.h"
 
@@ -100,13 +101,32 @@
   return is_changed;
 }
 
+class FrameFocusRules : public wm::BaseFocusRules {
+ public:
+  FrameFocusRules() = default;
+  ~FrameFocusRules() override = default;
+
+  // wm::BaseFocusRules implementation.
+  bool SupportsChildActivation(aura::Window*) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrameFocusRules);
+};
+
+bool FrameFocusRules::SupportsChildActivation(aura::Window*) const {
+  // TODO(crbug.com/878439): Return a result based on window properties such as
+  // visibility.
+  return true;
+}
+
 }  // namespace
 
 FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
                      ContextImpl* context,
                      fidl::InterfaceRequest<chromium::web::Frame> frame_request)
     : web_contents_(std::move(web_contents)),
-      focus_controller_(std::make_unique<wm::FocusController>(this)),
+      focus_controller_(
+          std::make_unique<wm::FocusController>(new FrameFocusRules)),
       context_(context),
       binding_(this, std::move(frame_request)) {
   web_contents_->SetDelegate(this);
@@ -117,7 +137,7 @@
   if (window_tree_host_) {
     aura::client::SetFocusClient(root_window(), nullptr);
     wm::SetActivationClient(root_window(), nullptr);
-
+    web_contents_->ClosePage();
     window_tree_host_->Hide();
     window_tree_host_->compositor()->SetVisible(false);
 
@@ -301,10 +321,4 @@
   }
 }
 
-bool FrameImpl::SupportsChildActivation(aura::Window*) const {
-  // TODO(crbug.com/878439): Return a result based on window properties such as
-  // visibility.
-  return true;
-}
-
 }  // namespace webrunner
diff --git a/webrunner/browser/frame_impl.h b/webrunner/browser/frame_impl.h
index 0a81e6e9..959dc69 100644
--- a/webrunner/browser/frame_impl.h
+++ b/webrunner/browser/frame_impl.h
@@ -15,7 +15,6 @@
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/aura/window_tree_host.h"
-#include "ui/wm/core/base_focus_rules.h"
 #include "ui/wm/core/focus_controller.h"
 #include "url/gurl.h"
 #include "webrunner/fidl/chromium/web/cpp/fidl.h"
@@ -37,8 +36,7 @@
 class FrameImpl : public chromium::web::Frame,
                   public chromium::web::NavigationController,
                   public content::WebContentsObserver,
-                  public content::WebContentsDelegate,
-                  public wm::BaseFocusRules {
+                  public content::WebContentsDelegate {
  public:
   FrameImpl(std::unique_ptr<content::WebContents> web_contents,
             ContextImpl* context,
@@ -70,11 +68,11 @@
       override;
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(ContextImplTest, DelayedNavigationEventAck);
-  FRIEND_TEST_ALL_PREFIXES(ContextImplTest, NavigationObserverDisconnected);
-  FRIEND_TEST_ALL_PREFIXES(ContextImplTest, NoNavigationObserverAttached);
-  FRIEND_TEST_ALL_PREFIXES(ContextImplTest, ReloadFrame);
-  FRIEND_TEST_ALL_PREFIXES(ContextImplTest, Stop);
+  FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck);
+  FRIEND_TEST_ALL_PREFIXES(FrameImplTest, NavigationObserverDisconnected);
+  FRIEND_TEST_ALL_PREFIXES(FrameImplTest, NoNavigationObserverAttached);
+  FRIEND_TEST_ALL_PREFIXES(FrameImplTest, ReloadFrame);
+  FRIEND_TEST_ALL_PREFIXES(FrameImplTest, Stop);
 
   aura::Window* root_window() const { return window_tree_host_->window(); }
 
@@ -101,9 +99,6 @@
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override;
 
-  // wm::BaseFocusRules implementation.
-  bool SupportsChildActivation(aura::Window*) const override;
-
   std::unique_ptr<aura::WindowTreeHost> window_tree_host_;
   std::unique_ptr<content::WebContents> web_contents_;
   std::unique_ptr<wm::FocusController> focus_controller_;
diff --git a/webrunner/browser/frame_impl_browsertest.cc b/webrunner/browser/frame_impl_browsertest.cc
new file mode 100644
index 0000000..c4426ae
--- /dev/null
+++ b/webrunner/browser/frame_impl_browsertest.cc
@@ -0,0 +1,527 @@
+// 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 <lib/fidl/cpp/binding.h>
+
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+#include "webrunner/browser/frame_impl.h"
+#include "webrunner/browser/test_common.h"
+#include "webrunner/browser/webrunner_browser_test.h"
+#include "webrunner/service/common.h"
+
+namespace webrunner {
+
+using testing::_;
+using testing::AllOf;
+using testing::Field;
+using testing::InvokeWithoutArgs;
+using testing::Mock;
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+const char kPage1Path[] = "/title1.html";
+const char kPage2Path[] = "/title2.html";
+const char kPage1Title[] = "title 1";
+const char kPage2Title[] = "title 2";
+const char kDataUrl[] =
+    "data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
+
+MATCHER(IsSet, "Checks if an optional field is set.") {
+  return !arg.is_null();
+}
+
+// Defines a suite of tests that exercise Frame-level functionality, such as
+// navigation commands and page events.
+class FrameImplTest : public WebRunnerBrowserTest {
+ public:
+  FrameImplTest() = default;
+  ~FrameImplTest() = default;
+
+  MOCK_METHOD1(OnServeHttpRequest,
+               void(const net::test_server::HttpRequest& request));
+
+ protected:
+  // Creates a Frame with |navigation_observer_| attached.
+  chromium::web::FramePtr CreateFrame() {
+    return WebRunnerBrowserTest::CreateFrame(&navigation_observer_);
+  }
+
+  // Navigates a |controller| to |url|, blocking until navigation is complete.
+  void CheckLoadUrl(const std::string& url,
+                    const std::string& expected_title,
+                    chromium::web::NavigationController* controller) {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, expected_title),
+                    Field(&NavigationDetails::url, url))))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(url, nullptr);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+    navigation_observer_.Acknowledge();
+  }
+
+  testing::StrictMock<MockNavigationObserver> navigation_observer_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrameImplTest);
+};
+
+class WebContentsDeletionObserver : public content::WebContentsObserver {
+ public:
+  explicit WebContentsDeletionObserver(content::WebContents* web_contents)
+      : content::WebContentsObserver(web_contents) {}
+
+  MOCK_METHOD1(RenderViewDeleted,
+               void(content::RenderViewHost* render_view_host));
+};
+
+// Verifies that the browser will navigate and generate a navigation observer
+// event when LoadUrl() is called.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateFrame) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
+
+  frame.Unbind();
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateDataFrame) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  CheckLoadUrl(kDataUrl, kDataUrl, controller.get());
+
+  frame.Unbind();
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, FrameDeletedBeforeContext) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  // Process the frame creation message.
+  base::RunLoop().RunUntilIdle();
+
+  FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
+  WebContentsDeletionObserver deletion_observer(
+      frame_impl->web_contents_for_test());
+  base::RunLoop run_loop;
+  EXPECT_CALL(deletion_observer, RenderViewDeleted(_))
+      .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+  controller->LoadUrl(url::kAboutBlankURL, nullptr);
+
+  frame.Unbind();
+  run_loop.Run();
+
+  // Check that |context| remains bound after the frame is closed.
+  EXPECT_TRUE(context());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ContextDeletedBeforeFrame) {
+  chromium::web::FramePtr frame = CreateFrame();
+  EXPECT_TRUE(frame);
+
+  base::RunLoop run_loop;
+  frame.set_error_handler([&run_loop]() { run_loop.Quit(); });
+  context().Unbind();
+  run_loop.Run();
+  EXPECT_FALSE(frame);
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, GoBackAndForward) {
+  chromium::web::FramePtr frame = CreateFrame();
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL title1(embedded_test_server()->GetURL(kPage1Path));
+  GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+  CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
+  CheckLoadUrl(title2.spec(), kPage2Title, controller.get());
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+    controller->GoBack();
+    run_loop.Run();
+    navigation_observer_.Acknowledge();
+  }
+
+  // At the top of the navigation entry list; this should be a no-op.
+  controller->GoBack();
+
+  // Process the navigation request message.
+  base::RunLoop().RunUntilIdle();
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage2Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+    controller->GoForward();
+    run_loop.Run();
+    navigation_observer_.Acknowledge();
+  }
+
+  // At the end of the navigation entry list; this should be a no-op.
+  controller->GoForward();
+
+  // Process the navigation request message.
+  base::RunLoop().RunUntilIdle();
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ReloadFrame) {
+  chromium::web::FramePtr frame = CreateFrame();
+  chromium::web::NavigationControllerPtr navigation_controller;
+  frame->GetNavigationController(navigation_controller.NewRequest());
+
+  embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
+      &FrameImplTest::OnServeHttpRequest, base::Unretained(this)));
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url(embedded_test_server()->GetURL(kPage1Path));
+
+  EXPECT_CALL(*this, OnServeHttpRequest(_));
+  CheckLoadUrl(url.spec(), kPage1Title, navigation_controller.get());
+
+  navigation_observer_.Observe(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+  // Reload with NO_CACHE.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(*this, OnServeHttpRequest(_));
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    navigation_controller->Reload(chromium::web::ReloadType::NO_CACHE);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+    navigation_observer_.Acknowledge();
+  }
+  // Reload with PARTIAL_CACHE.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(*this, OnServeHttpRequest(_));
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    navigation_controller->Reload(chromium::web::ReloadType::PARTIAL_CACHE);
+    run_loop.Run();
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, GetVisibleEntry) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  // Verify that a Frame returns a null NavigationEntry prior to receiving any
+  // LoadUrl() calls.
+  {
+    base::RunLoop run_loop;
+    controller->GetVisibleEntry(
+        [&run_loop](std::unique_ptr<chromium::web::NavigationEntry> details) {
+          EXPECT_EQ(nullptr, details.get());
+          run_loop.Quit();
+        });
+    run_loop.Run();
+  }
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL title1(embedded_test_server()->GetURL(kPage1Path));
+  GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+  // Navigate to a page.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+    controller->LoadUrl(title1.spec(), nullptr);
+    run_loop.Run();
+    navigation_observer_.Acknowledge();
+  }
+
+  // Verify that GetVisibleEntry() reflects the new Frame navigation state.
+  {
+    base::RunLoop run_loop;
+    controller->GetVisibleEntry(
+        [&run_loop,
+         &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
+          EXPECT_TRUE(details);
+          EXPECT_EQ(details->url, title1.spec());
+          EXPECT_EQ(details->title, kPage1Title);
+          run_loop.Quit();
+        });
+    run_loop.Run();
+  }
+
+  // Navigate to another page.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage2Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+    controller->LoadUrl(title2.spec(), nullptr);
+    run_loop.Run();
+    navigation_observer_.Acknowledge();
+  }
+
+  // Verify the navigation with GetVisibleEntry().
+  {
+    base::RunLoop run_loop;
+    controller->GetVisibleEntry(
+        [&run_loop,
+         &title2](std::unique_ptr<chromium::web::NavigationEntry> details) {
+          EXPECT_TRUE(details);
+          EXPECT_EQ(details->url, title2.spec());
+          EXPECT_EQ(details->title, kPage2Title);
+          run_loop.Quit();
+        });
+    run_loop.Run();
+  }
+
+  // Navigate back to the first page.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+    controller->GoBack();
+    run_loop.Run();
+    navigation_observer_.Acknowledge();
+  }
+
+  // Verify the navigation with GetVisibleEntry().
+  {
+    base::RunLoop run_loop;
+    controller->GetVisibleEntry(
+        [&run_loop,
+         &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
+          EXPECT_TRUE(details);
+          EXPECT_EQ(details->url, title1.spec());
+          EXPECT_EQ(details->title, kPage1Title);
+          run_loop.Quit();
+        });
+    run_loop.Run();
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) {
+  chromium::web::FramePtr frame;
+  context()->CreateFrame(frame.NewRequest());
+  base::RunLoop().RunUntilIdle();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL title1(embedded_test_server()->GetURL(kPage1Path));
+  GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+  navigation_observer_.Observe(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title1.spec(), nullptr);
+    run_loop.Run();
+  }
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title2.spec(), nullptr);
+    run_loop.Run();
+  }
+}
+
+// Verifies that a Frame will handle navigation observer disconnection events
+// gracefully.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL title1(embedded_test_server()->GetURL(kPage1Path));
+  GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+  navigation_observer_.Observe(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1));
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title1.spec(), nullptr);
+    run_loop.Run();
+  }
+
+  // Disconnect the observer & spin the runloop to propagate the disconnection
+  // event over IPC.
+  navigation_observer_bindings().CloseAll();
+  base::RunLoop().RunUntilIdle();
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title2.spec(), nullptr);
+    run_loop.Run();
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, DISABLED_DelayedNavigationEventAck) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL title1(embedded_test_server()->GetURL(kPage1Path));
+  GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+  // Expect an navigation event here, but deliberately postpone acknowledgement
+  // until the end of the test.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title1.spec(), nullptr);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+  }
+
+  // Since we have blocked NavigationEventObserver's flow, we must observe the
+  // WebContents events directly via a test-only seam.
+  navigation_observer_.Observe(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+  // Navigate to a second page.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title2.spec(), nullptr);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+  }
+
+  // Navigate to the first page.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(title1.spec(), nullptr);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+  }
+
+  // Since there was no observable change in navigation state since the last
+  // ack, there should be no more NavigationEvents generated.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(navigation_observer_,
+                MockableOnNavigationStateChanged(testing::AllOf(
+                    Field(&NavigationDetails::title, kPage1Title),
+                    Field(&NavigationDetails::url, IsSet()))))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    navigation_observer_.Acknowledge();
+    run_loop.Run();
+  }
+}
+
+// Observes events specific to the Stop() test case.
+struct WebContentsObserverForStop : public content::WebContentsObserver {
+  using content::WebContentsObserver::Observe;
+  MOCK_METHOD1(DidStartNavigation, void(content::NavigationHandle*));
+  MOCK_METHOD0(NavigationStopped, void());
+};
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, Stop) {
+  chromium::web::FramePtr frame = CreateFrame();
+
+  chromium::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Use a request handler that will accept the connection and stall
+  // indefinitely.
+  GURL hung_url(embedded_test_server()->GetURL("/hung"));
+
+  WebContentsObserverForStop observer;
+  observer.Observe(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(observer, DidStartNavigation(_))
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->LoadUrl(hung_url.spec(), nullptr);
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+  }
+
+  EXPECT_TRUE(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(observer, NavigationStopped())
+        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+    controller->Stop();
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(this);
+  }
+
+  EXPECT_FALSE(
+      context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
+}
+
+}  // namespace webrunner
diff --git a/webrunner/browser/test_common.cc b/webrunner/browser/test_common.cc
new file mode 100644
index 0000000..3365aa2
--- /dev/null
+++ b/webrunner/browser/test_common.cc
@@ -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.
+
+#include "webrunner/browser/test_common.h"
+
+#include <utility>
+
+#include "base/run_loop.h"
+
+namespace webrunner {
+
+MockNavigationObserver::MockNavigationObserver() = default;
+
+MockNavigationObserver::~MockNavigationObserver() = default;
+
+void MockNavigationObserver::Acknowledge() {
+  DCHECK(navigation_ack_callback_);
+  std::move(navigation_ack_callback_)();
+
+  // Pump the acknowledgement message over IPC.
+  base::RunLoop().RunUntilIdle();
+}
+
+void MockNavigationObserver::OnNavigationStateChanged(
+    chromium::web::NavigationEvent change,
+    OnNavigationStateChangedCallback callback) {
+  MockableOnNavigationStateChanged(std::move(change));
+  navigation_ack_callback_ = std::move(callback);
+}
+
+}  // namespace webrunner
diff --git a/webrunner/browser/test_common.h b/webrunner/browser/test_common.h
new file mode 100644
index 0000000..b150e85
--- /dev/null
+++ b/webrunner/browser/test_common.h
@@ -0,0 +1,50 @@
+// 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 WEBRUNNER_BROWSER_TEST_COMMON_H_
+#define WEBRUNNER_BROWSER_TEST_COMMON_H_
+
+#include "content/public/browser/web_contents_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+
+namespace webrunner {
+
+// Defines mock methods used by tests to observe NavigationStateChangeEvents
+// and lower-level WebContentsObserver events.
+class MockNavigationObserver : public chromium::web::NavigationEventObserver,
+                               public content::WebContentsObserver {
+ public:
+  using content::WebContentsObserver::Observe;
+
+  MockNavigationObserver();
+  ~MockNavigationObserver() override;
+
+  // Acknowledges processing of the most recent OnNavigationStateChanged call.
+  void Acknowledge();
+
+  MOCK_METHOD1(MockableOnNavigationStateChanged,
+               void(chromium::web::NavigationEvent change));
+
+  // chromium::web::NavigationEventObserver implementation.
+  // Proxies calls to MockableOnNavigationStateChanged(), because GMock does
+  // not work well with fit::Callbacks inside mocked actions.
+  void OnNavigationStateChanged(
+      chromium::web::NavigationEvent change,
+      OnNavigationStateChangedCallback callback) override;
+
+  // WebContentsObserver implementation.
+  MOCK_METHOD2(DidFinishLoad,
+               void(content::RenderFrameHost* render_frame_host,
+                    const GURL& validated_url));
+
+ private:
+  OnNavigationStateChangedCallback navigation_ack_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockNavigationObserver);
+};
+
+}  // namespace webrunner
+
+#endif  // WEBRUNNER_BROWSER_TEST_COMMON_H_
diff --git a/webrunner/browser/webrunner_browser_test.cc b/webrunner/browser/webrunner_browser_test.cc
index 75c59e2..d541303 100644
--- a/webrunner/browser/webrunner_browser_test.cc
+++ b/webrunner/browser/webrunner_browser_test.cc
@@ -37,6 +37,29 @@
   context_.Unbind();
 }
 
+void WebRunnerBrowserTest::TearDownOnMainThread() {
+  navigation_observer_bindings_.CloseAll();
+}
+
+chromium::web::FramePtr WebRunnerBrowserTest::CreateFrame(
+    chromium::web::NavigationEventObserver* observer) {
+  chromium::web::FramePtr frame;
+  context_->CreateFrame(frame.NewRequest());
+
+  if (observer) {
+    fidl::InterfaceRequest<chromium::web::NavigationEventObserver>
+        observer_request;
+    frame->SetNavigationEventObserver(
+        navigation_observer_bindings_.AddBinding(observer));
+  }
+
+  // Pump the messages so that the caller can use the Frame instance
+  // immediately after this function returns.
+  base::RunLoop().RunUntilIdle();
+
+  return frame;
+}
+
 // static
 void WebRunnerBrowserTest::SetContextClientChannel(zx::channel channel) {
   DCHECK(channel);
diff --git a/webrunner/browser/webrunner_browser_test.h b/webrunner/browser/webrunner_browser_test.h
index a47bd23..7bd5f33 100644
--- a/webrunner/browser/webrunner_browser_test.h
+++ b/webrunner/browser/webrunner_browser_test.h
@@ -5,6 +5,7 @@
 #ifndef WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_TEST_H_
 #define WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_TEST_H_
 
+#include <lib/fidl/cpp/binding_set.h>
 #include <memory>
 
 #include "base/macros.h"
@@ -26,18 +27,31 @@
   // object by WebRunnerBrowserTest.
   static void SetContextClientChannel(zx::channel channel);
 
+  // Creates a Frame for this Context.
+  // |observer|: If set, specifies the navigation observer for the Frame.
+  chromium::web::FramePtr CreateFrame(
+      chromium::web::NavigationEventObserver* observer);
+
   // Gets the client object for the Context service.
   chromium::web::ContextPtr& context() { return context_; }
 
   // Gets the underlying ContextImpl service instance.
   ContextImpl* context_impl() const;
 
+  fidl::BindingSet<chromium::web::NavigationEventObserver>&
+  navigation_observer_bindings() {
+    return navigation_observer_bindings_;
+  }
+
   // content::BrowserTestBase implementation.
   void PreRunTestOnMainThread() override;
   void PostRunTestOnMainThread() override;
+  void TearDownOnMainThread() override;
 
  private:
   chromium::web::ContextPtr context_;
+  fidl::BindingSet<chromium::web::NavigationEventObserver>
+      navigation_observer_bindings_;
 
   DISALLOW_COPY_AND_ASSIGN(WebRunnerBrowserTest);
 };