diff --git a/BUILD.gn b/BUILD.gn
index fccc380..5a3538c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -742,6 +742,7 @@
       "//headless",
       "//headless:headless_shell",
       "//headless:headless_tests",
+      "//webrunner",
     ]
   }
 }
diff --git a/DEPS b/DEPS
index ae76a3d3..8668a0c 100644
--- a/DEPS
+++ b/DEPS
@@ -79,7 +79,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '04bea3bf76e2cf30c438c876b1c2d6847bcf797f',
+  'skia_revision': 'f5b418839f2e43ec9f92ed9a5f873eb9cf63f713',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -91,7 +91,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'a43994c9dd3a46220acfea855cad240960b9e98c',
+  'angle_revision': '5cf4d06cd4604efaec242203b2b327e6daa607ee',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -139,7 +139,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '9e3d4c4b70e0407995dc013e1cd8a92892f18c22',
+  'catapult_revision': 'd95849b99664857365f1ddebbe1f391acbd3a104',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -155,7 +155,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': 'c9c8ed449e6a4a15715c4bb2e9be3a12a1e01250',
+  'feed_revision': '31ad36da1a0f992ca482506ad1ff7f94dd94573b',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
@@ -408,7 +408,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f3f1110ffae1c9c6ca6764a55821012712670fa2',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '33146a20418aff04ca926680f32f793ccfb72c0b',
       'condition': 'checkout_linux',
   },
 
@@ -423,7 +423,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + 'c873de4b9021e002a2a742aee91fbc612c2445d6',
+      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '6eff8cb7641c805410a5d9b998c6c58de9eb6bde',
       'condition': 'checkout_linux',
   },
 
@@ -873,7 +873,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '3c1cb0203b6cfc10389e85a350b2ea6ca29d01ce',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'a21090b7706eb6577f4b305f3e00b04fe833c50d', # commit position 21742
+    Var('webrtc_git') + '/src.git' + '@' + '9d6f73bfb299cc20d55e12ae98e70de803211d7a', # commit position 21742
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/ash/app_list/app_list_presenter_delegate.cc b/ash/app_list/app_list_presenter_delegate.cc
index ef34bb55..e325e5b 100644
--- a/ash/app_list/app_list_presenter_delegate.cc
+++ b/ash/app_list/app_list_presenter_delegate.cc
@@ -12,6 +12,7 @@
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
 #include "ash/shelf/app_list_button.h"
+#include "ash/shelf/back_button.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shelf/shelf_widget.h"
@@ -171,6 +172,16 @@
         app_list_button->bounds().Contains(event->location())) {
       return;
     }
+
+    // If the event happened on the back button, it'll get handled by the
+    // button.
+    BackButton* back_button =
+        Shelf::ForWindow(target)->shelf_widget()->GetBackButton();
+    if (back_button && back_button->GetWidget() &&
+        target == back_button->GetWidget()->GetNativeWindow() &&
+        back_button->bounds().Contains(event->location())) {
+      return;
+    }
   }
 
   aura::Window* window = view_->GetWidget()->GetNativeView()->parent();
diff --git a/ash/components/shortcut_viewer_strings.grdp b/ash/components/shortcut_viewer_strings.grdp
index 58ec4f8..10857de 100644
--- a/ash/components/shortcut_viewer_strings.grdp
+++ b/ash/components/shortcut_viewer_strings.grdp
@@ -2,7 +2,7 @@
 <grit-part>
   <!-- Title -->
   <message name="IDS_KSV_TITLE" desc="The tooltip text of the keyboard shortcut viewer shelf icon.">
-    Keyboard Shortcut Viewer
+    Keyboard Shortcut Helper
   </message>
 
   <!-- Accessibility -->
diff --git a/ash/display/display_move_window_util_unittest.cc b/ash/display/display_move_window_util_unittest.cc
index 73faae55..298b9b2 100644
--- a/ash/display/display_move_window_util_unittest.cc
+++ b/ash/display/display_move_window_util_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
+#include "ash/shelf/shelf_constants.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -449,6 +450,23 @@
             screen->GetDisplayNearestWindow(child->GetNativeWindow()).id());
 }
 
+// Tests that restore bounds is updated with window movement to another display.
+TEST_F(DisplayMoveWindowUtilTest, RestoreMaximizedWindowAfterMovement) {
+  UpdateDisplay("400x300,400x300");
+  aura::Window* w =
+      CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
+  wm::ActivateWindow(w);
+
+  wm::WindowState* window_state = wm::GetWindowState(w);
+  window_state->Maximize();
+  EXPECT_EQ(gfx::Rect(0, 0, 400, 300 - kShelfSize), w->GetBoundsInScreen());
+
+  PerformMoveWindowAccel();
+  EXPECT_EQ(gfx::Rect(400, 0, 400, 300 - kShelfSize), w->GetBoundsInScreen());
+  window_state->Restore();
+  EXPECT_EQ(gfx::Rect(410, 20, 200, 100), w->GetBoundsInScreen());
+}
+
 }  // namespace display_move_window_util
 
 }  // namespace ash
diff --git a/ash/public/cpp/app_list/app_list_struct_traits.h b/ash/public/cpp/app_list/app_list_struct_traits.h
index 7d165685..238f211 100644
--- a/ash/public/cpp/app_list/app_list_struct_traits.h
+++ b/ash/public/cpp/app_list/app_list_struct_traits.h
@@ -95,6 +95,8 @@
         return ash::mojom::SearchResultType::kPlayStoreApp;
       case ash::SearchResultType::kInstantApp:
         return ash::mojom::SearchResultType::kInstantApp;
+      case ash::SearchResultType::kInternalApp:
+        return ash::mojom::SearchResultType::kInternalApp;
       case ash::SearchResultType::kUnknown:
         break;
     }
@@ -114,6 +116,9 @@
       case ash::mojom::SearchResultType::kInstantApp:
         *out = ash::SearchResultType::kInstantApp;
         return true;
+      case ash::mojom::SearchResultType::kInternalApp:
+        *out = ash::SearchResultType::kInternalApp;
+        return true;
     }
     NOTREACHED();
     return false;
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 43999c0..5d6fe1a 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -38,6 +38,7 @@
   kInstalledApp,  // Installed apps.
   kPlayStoreApp,  // Uninstalled apps from playstore.
   kInstantApp,    // Instant apps.
+  kInternalApp,   // Chrome OS apps.
   // Add new values here.
 };
 
diff --git a/ash/public/interfaces/app_list.mojom b/ash/public/interfaces/app_list.mojom
index 7e951dcf..89eb5c19 100644
--- a/ash/public/interfaces/app_list.mojom
+++ b/ash/public/interfaces/app_list.mojom
@@ -56,6 +56,7 @@
   kInstalledApp,  // Installed apps.
   kPlayStoreApp,  // Uninstalled apps from playstore.
   kInstantApp,    // Instant apps.
+  kInternalApp,   // Chrome OS apps.
   // Add new values here.
 };
 
diff --git a/ash/shelf/shelf_button.cc b/ash/shelf/shelf_button.cc
index bab5603..67b4ca9 100644
--- a/ash/shelf/shelf_button.cc
+++ b/ash/shelf/shelf_button.cc
@@ -56,11 +56,14 @@
 // The time threshold before an item can be dragged.
 constexpr int kDragTimeThresholdMs = 300;
 
+// The time threshold before the ink drop should activate on a long press.
+constexpr int kInkDropRippleActivationTimeMs = 650;
+
 // The drag and drop app icon should get scaled by this factor.
 constexpr float kAppIconScale = 1.2f;
 
 // The drag and drop app icon scaling up or down animation transition duration.
-constexpr int kDragDropAppIconScaleTransitionMs = 20;
+constexpr int kDragDropAppIconScaleTransitionMs = 200;
 
 // Simple AnimationDelegate that owns a single ThrobAnimation instance to
 // keep all Draw Attention animations in sync.
@@ -279,10 +282,10 @@
       is_touchable_app_context_menu_enabled_(
           features::IsTouchableAppContextMenuEnabled()) {
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
-  SetInkDropMode(InkDropMode::ON);
+  SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
   set_ink_drop_base_color(kShelfInkDropBaseColor);
   set_ink_drop_visible_opacity(kShelfInkDropVisibleOpacity);
-
+  set_hide_ink_drop_when_showing_context_menu(false);
   const gfx::ShadowValue kShadows[] = {
       gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
       gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
@@ -291,6 +294,8 @@
   icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
 
   // TODO: refactor the layers so each button doesn't require 3.
+  // |icon_view_| needs its own layer so it can be scaled up independently of
+  // the ink drop ripple.
   icon_view_->SetPaintToLayer();
   icon_view_->layer()->SetFillsBoundsOpaquely(false);
   icon_view_->SetHorizontalAlignment(views::ImageView::CENTER);
@@ -393,6 +398,11 @@
   AnimateInkDrop(views::InkDropState::HIDDEN, event);
 }
 
+void ShelfButton::OnMenuClosed() {
+  if (GetInkDrop()->GetTargetInkDropState() != views::InkDropState::DEACTIVATED)
+    GetInkDrop()->AnimateToState(views::InkDropState::DEACTIVATED);
+}
+
 void ShelfButton::ShowContextMenu(const gfx::Point& p,
                                   ui::MenuSourceType source_type) {
   if (!context_menu_controller())
@@ -554,10 +564,20 @@
       drag_timer_.Start(
           FROM_HERE, base::TimeDelta::FromMilliseconds(kDragTimeThresholdMs),
           base::Bind(&ShelfButton::OnTouchDragTimer, base::Unretained(this)));
+      ripple_activation_timer_.Start(
+          FROM_HERE,
+          base::TimeDelta::FromMilliseconds(kInkDropRippleActivationTimeMs),
+          base::Bind(&ShelfButton::OnRippleTimer, base::Unretained(this)));
+      GetInkDrop()->AnimateToState(views::InkDropState::ACTION_PENDING);
       event->SetHandled();
       break;
     case ui::ET_GESTURE_END:
       drag_timer_.Stop();
+      // If the button is being dragged, or there is an active context menu,
+      // for this ShelfButton, don't deactivate the ink drop.
+      if (!(state_ & STATE_DRAGGING) &&
+          !shelf_view_->IsShowingMenuForView(this))
+        GetInkDrop()->AnimateToState(views::InkDropState::DEACTIVATED);
       ClearState(STATE_HOVERED);
       ClearState(STATE_DRAGGING);
       break;
@@ -566,7 +586,10 @@
         shelf_view_->PointerPressedOnButton(this, ShelfView::TOUCH, *event);
         event->SetHandled();
       } else {
+        // The drag went to the bezel and is about to be passed to
+        // ShelfLayoutManager.
         drag_timer_.Stop();
+        GetInkDrop()->AnimateToState(views::InkDropState::HIDDEN);
       }
       break;
     case ui::ET_GESTURE_SCROLL_UPDATE:
@@ -584,9 +607,13 @@
       }
       break;
     case ui::ET_GESTURE_LONG_TAP:
+      GetInkDrop()->AnimateToState(views::InkDropState::ACTIVATED);
       // Handle LONG_TAP to avoid opening the context menu twice.
       event->SetHandled();
       break;
+    case ui::ET_GESTURE_TWO_FINGER_TAP:
+      GetInkDrop()->AnimateToState(views::InkDropState::ACTIVATED);
+      break;
     default:
       break;
   }
@@ -643,16 +670,25 @@
   AddState(STATE_DRAGGING);
 }
 
+void ShelfButton::OnRippleTimer() {
+  if (GetInkDrop()->GetTargetInkDropState() !=
+      views::InkDropState::ACTION_PENDING) {
+    return;
+  }
+  GetInkDrop()->AnimateToState(views::InkDropState::ACTIVATED);
+}
+
 void ShelfButton::ScaleAppIcon(bool scale_up) {
-  ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
+  ui::ScopedLayerAnimationSettings settings(icon_view_->layer()->GetAnimator());
   settings.SetTransitionDuration(
       base::TimeDelta::FromMilliseconds(kDragDropAppIconScaleTransitionMs));
 
   if (scale_up) {
-    layer()->SetTransform(gfx::GetScaleTransform(
-        gfx::Rect(layer()->bounds().size()).CenterPoint(), kAppIconScale));
+    icon_view_->layer()->SetTransform(gfx::GetScaleTransform(
+        gfx::Rect(icon_view_->layer()->bounds().size()).CenterPoint(),
+        kAppIconScale));
   } else {
-    layer()->SetTransform(gfx::Transform());
+    icon_view_->layer()->SetTransform(gfx::Transform());
   }
 }
 
diff --git a/ash/shelf/shelf_button.h b/ash/shelf/shelf_button.h
index 7792b8e..f7264dcd 100644
--- a/ash/shelf/shelf_button.h
+++ b/ash/shelf/shelf_button.h
@@ -65,6 +65,9 @@
   // Called when user started dragging the shelf button.
   void OnDragStarted(const ui::LocatedEvent* event);
 
+  // Callback used when a menu for this ShelfButton is closed.
+  void OnMenuClosed();
+
   // Overrides to views::Button:
   void ShowContextMenu(const gfx::Point& p,
                        ui::MenuSourceType source_type) override;
@@ -105,6 +108,9 @@
   // Invoked when |touch_drag_timer_| fires to show dragging UI.
   void OnTouchDragTimer();
 
+  // Invoked when |ripple_activation_timer_| fires to activate the ink drop.
+  void OnRippleTimer();
+
   // Scales up app icon if |scale_up| is true, otherwise scales it back to
   // normal size.
   void ScaleAppIcon(bool scale_up);
@@ -140,6 +146,9 @@
   // A timer to defer showing drag UI when the shelf button is pressed.
   base::OneShotTimer drag_timer_;
 
+  // A timer to activate the ink drop ripple during a long press.
+  base::OneShotTimer ripple_activation_timer_;
+
   DISALLOW_COPY_AND_ASSIGN(ShelfButton);
 };
 
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index 6212ede..4c215b5 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -406,6 +406,10 @@
   return launcher_menu_runner_.get() && launcher_menu_runner_->IsRunning();
 }
 
+bool ShelfView::IsShowingMenuForView(views::View* view) const {
+  return IsShowingMenu() && menu_owner_ == view;
+}
+
 bool ShelfView::IsShowingOverflowBubble() const {
   return overflow_bubble_.get() && overflow_bubble_->IsShowing();
 }
@@ -1902,7 +1906,7 @@
                    item.title, std::move(*menu_items),
                    model_->GetShelfItemDelegate(item.id)),
                sender, gfx::Point(), false,
-               ui::GetMenuSourceTypeForEvent(*event), ink_drop);
+               ui::GetMenuSourceTypeForEvent(*event));
     } else {
       ink_drop->AnimateToState(views::InkDropState::ACTION_TRIGGERED);
     }
@@ -1926,7 +1930,7 @@
                                      ? kAppContextMenuExecuteCommand
                                      : kNonAppContextMenuExecuteCommand);
   ShowMenu(std::move(menu_model), source, point, true /* context_menu */,
-           source_type, nullptr /* ink_drop */);
+           source_type);
 }
 
 void ShelfView::ShowContextMenuForView(views::View* source,
@@ -1943,7 +1947,7 @@
         std::make_unique<ShelfContextMenuModel>(
             std::vector<mojom::MenuItemPtr>(), nullptr, display_id);
     menu_model->set_histogram_name(kNonAppContextMenuExecuteCommand);
-    ShowMenu(std::move(menu_model), source, point, true, source_type, nullptr);
+    ShowMenu(std::move(menu_model), source, point, true, source_type);
     return;
   }
 
@@ -1962,10 +1966,11 @@
                          views::View* source,
                          const gfx::Point& click_point,
                          bool context_menu,
-                         ui::MenuSourceType source_type,
-                         views::InkDrop* ink_drop) {
+                         ui::MenuSourceType source_type) {
+  DCHECK(!IsShowingMenu());
   if (menu_model->GetItemCount() == 0)
     return;
+  menu_owner_ = source;
 
   menu_model_ = std::move(menu_model);
   closing_event_time_ = base::TimeTicks();
@@ -1987,7 +1992,7 @@
 
   launcher_menu_runner_ = std::make_unique<views::MenuRunner>(
       menu_model_.get(), run_types,
-      base::Bind(&ShelfView::OnMenuClosed, base::Unretained(this), ink_drop));
+      base::Bind(&ShelfView::OnMenuClosed, base::Unretained(this), source));
 
   // NOTE: if you convert to HAS_MNEMONICS be sure to update menu building code.
   launcher_menu_runner_->RunMenuAt(
@@ -1996,7 +2001,8 @@
       GetMenuAnchorPosition(item, context_menu), source_type);
 }
 
-void ShelfView::OnMenuClosed(views::InkDrop* ink_drop) {
+void ShelfView::OnMenuClosed(views::View* source) {
+  menu_owner_ = nullptr;
   context_menu_id_ = ShelfID();
 
   closing_event_time_ = launcher_menu_runner_->closing_event_time();
@@ -2008,9 +2014,9 @@
         base::TimeTicks::Now() - shelf_button_context_menu_time_);
     shelf_button_context_menu_time_ = base::TimeTicks();
   }
-
-  if (ink_drop)
-    ink_drop->AnimateToState(views::InkDropState::DEACTIVATED);
+  const ShelfItem* item = ShelfItemForView(source);
+  if (item)
+    static_cast<ShelfButton*>(source)->OnMenuClosed();
 
   launcher_menu_runner_.reset();
   menu_model_.reset();
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index b529e42..86ba4e6 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -90,6 +90,10 @@
   // Returns true if we're showing a menu.
   bool IsShowingMenu() const;
 
+  // Returns true if we're showing a menu for |view|. |view| could be a
+  // ShelfButton or the ShelfView.
+  bool IsShowingMenuForView(views::View* view) const;
+
   // Returns true if overflow bubble is shown.
   bool IsShowingOverflowBubble() const;
 
@@ -370,11 +374,10 @@
                 views::View* source,
                 const gfx::Point& click_point,
                 bool context_menu,
-                ui::MenuSourceType source_type,
-                views::InkDrop* ink_drop);
+                ui::MenuSourceType source_type);
 
   // Callback for MenuRunner.
-  void OnMenuClosed(views::InkDrop* ink_drop);
+  void OnMenuClosed(views::View* source);
 
   // Overridden from views::BoundsAnimatorObserver:
   void OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) override;
@@ -433,6 +436,10 @@
   // |dragging_| is set only if the mouse is dragged far enough.
   ShelfButton* drag_view_ = nullptr;
 
+  // The view showing a context menu. This can be either a ShelfView or
+  // ShelfButton.
+  views::View* menu_owner_ = nullptr;
+
   // Position of the mouse down event in |drag_view_|'s coordinates.
   gfx::Point drag_origin_;
 
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 05df0ccf..e4b1879 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -340,6 +340,10 @@
   return shelf_view_->GetAppListButton();
 }
 
+BackButton* ShelfWidget::GetBackButton() const {
+  return shelf_view_->GetBackButton();
+}
+
 app_list::ApplicationDragAndDropHost*
 ShelfWidget::GetDragAndDropHostForAppList() {
   return shelf_view_;
diff --git a/ash/shelf/shelf_widget.h b/ash/shelf/shelf_widget.h
index f8bc1997..b7da8ef 100644
--- a/ash/shelf/shelf_widget.h
+++ b/ash/shelf/shelf_widget.h
@@ -24,6 +24,7 @@
 namespace ash {
 enum class AnimationChangeType;
 class AppListButton;
+class BackButton;
 class FocusCycler;
 class LoginShelfView;
 class Shelf;
@@ -91,6 +92,9 @@
   // Returns the button that opens the app launcher.
   AppListButton* GetAppListButton() const;
 
+  // Returns the browser back button.
+  BackButton* GetBackButton() const;
+
   // Returns the ApplicationDragAndDropHost for this shelf.
   app_list::ApplicationDragAndDropHost* GetDragAndDropHostForAppList();
 
diff --git a/ash/shell.cc b/ash/shell.cc
index 67124fd..c8d3026 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -146,6 +146,8 @@
 #include "base/memory/ptr_util.h"
 #include "base/sys_info.h"
 #include "base/trace_event/trace_event.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/session_manager_client.h"
 #include "chromeos/system/devicemode.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -1195,6 +1197,17 @@
     observer.OnShellInitialized();
 
   user_metrics_recorder_->OnShellInitialized();
+
+  // By this point ash shell should have initialized its D-Bus signal
+  // listeners, so emit ash-initialized upstart signal to start Chrome OS tasks
+  // that expect that ash is listening to D-Bus signals they emit. For example,
+  // hammerd, which handles detachable base state, communicates the base state
+  // purely by emitting D-Bus signals, and thus has to be run whenever ash is
+  // started so ash (DetachableBaseHandler in particular) gets the proper view
+  // of the current detachable base state.
+  chromeos::DBusThreadManager::Get()
+      ->GetSessionManagerClient()
+      ->EmitAshInitialized();
 }
 
 void Shell::InitializeDisplayManager() {
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index a7ca2f5..946477a 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -37,6 +37,7 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
+#include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/core/easy_resize_window_targeter.h"
 #include "ui/wm/core/window_util.h"
 #include "ui/wm/public/activation_client.h"
@@ -144,6 +145,12 @@
     return false;
   }
   aura::Window* root = Shell::GetRootWindowForDisplayId(display_id);
+  // Update restore bounds to target root window.
+  if (window_state->HasRestoreBounds()) {
+    gfx::Rect restore_bounds = window_state->GetRestoreBoundsInParent();
+    ::wm::ConvertRectToScreen(root, &restore_bounds);
+    window_state->SetRestoreBoundsInScreen(restore_bounds);
+  }
   return root && MoveWindowToRoot(window, root);
 }
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index f2cf35f..686f25d8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1147,6 +1147,8 @@
       "base_paths_posix.h",
       "base_paths_win.cc",
       "base_paths_win.h",
+      "metrics/persistent_histogram_storage.cc",
+      "metrics/persistent_histogram_storage.h",
       "posix/unix_domain_socket.cc",
     ]
 
@@ -2251,6 +2253,7 @@
     "metrics/histogram_unittest.cc",
     "metrics/metrics_hashes_unittest.cc",
     "metrics/persistent_histogram_allocator_unittest.cc",
+    "metrics/persistent_histogram_storage_unittest.cc",
     "metrics/persistent_memory_allocator_unittest.cc",
     "metrics/persistent_sample_map_unittest.cc",
     "metrics/sample_map_unittest.cc",
@@ -3083,9 +3086,9 @@
 #                  and are confident that the transition was ok.
 fuzzer_test("utf_string_conversions_regression_fuzzer") {
   sources = [
-    "strings/utf_string_conversions_regression_fuzzer.cc",
     "strings/old_utf_string_conversions.cc",
     "strings/old_utf_string_conversions.h",
+    "strings/utf_string_conversions_regression_fuzzer.cc",
   ]
   deps = [
     ":base",
diff --git a/base/android/build_info.cc b/base/android/build_info.cc
index 151c045..6de8a75 100644
--- a/base/android/build_info.cc
+++ b/base/android/build_info.cc
@@ -73,9 +73,7 @@
       installer_package_name_(StrDupParam(params, 16)),
       abi_name_(StrDupParam(params, 17)),
       firebase_app_id_(StrDupParam(params, 18)),
-      custom_themes_(StrDupParam(params, 19)),
-      resources_version_(StrDupParam(params, 20)),
-      extracted_file_suffix_(params[21]),
+      extracted_file_suffix_(params[19]),
       java_exception_info_(NULL) {}
 
 // static
diff --git a/base/android/build_info.h b/base/android/build_info.h
index 1d8f510..07b0cfd68 100644
--- a/base/android/build_info.h
+++ b/base/android/build_info.h
@@ -103,10 +103,6 @@
   // Will be empty string if no app id is assigned.
   const char* firebase_app_id() const { return firebase_app_id_; }
 
-  const char* custom_themes() const { return custom_themes_; }
-
-  const char* resources_version() const { return resources_version_; }
-
   const char* build_type() const {
     return build_type_;
   }
@@ -159,8 +155,6 @@
   const char* const installer_package_name_;
   const char* const abi_name_;
   const char* const firebase_app_id_;
-  const char* const custom_themes_;
-  const char* const resources_version_;
   // Not needed by breakpad.
   const std::string extracted_file_suffix_;
   // This is set via set_java_exception_info, not at constructor time.
diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java
index c4372d4..91b02a6 100644
--- a/base/android/java/src/org/chromium/base/BuildInfo.java
+++ b/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -45,10 +45,6 @@
     public final String androidBuildFingerprint;
     /** A string that is different each time the apk changes. */
     public final String extractedFileSuffix;
-    /** Whether or not the device has apps installed for using custom themes. */
-    public final String customThemes;
-    /** Product version as stored in Android resources. */
-    public final String resourcesVersion;
 
     private static class Holder { private static BuildInfo sInstance = new BuildInfo(); }
 
@@ -63,7 +59,7 @@
                 buildInfo.packageName, String.valueOf(buildInfo.versionCode), buildInfo.versionName,
                 buildInfo.androidBuildFingerprint, buildInfo.gmsVersionCode,
                 buildInfo.installerPackageName, buildInfo.abiString, BuildConfig.FIREBASE_APP_ID,
-                buildInfo.customThemes, buildInfo.resourcesVersion, buildInfo.extractedFileSuffix,
+                buildInfo.extractedFileSuffix,
         };
     }
 
@@ -114,31 +110,6 @@
             gmsVersionCode = gmsPackageInfo != null ? String.valueOf(gmsPackageInfo.versionCode)
                                                     : "gms versionCode not available.";
 
-            String hasCustomThemes = "true";
-            try {
-                // Substratum is a theme engine that enables users to use custom themes provided
-                // by theme apps. Sometimes these can cause crashs if not installed correctly.
-                // These crashes can be difficult to debug, so knowing if the theme manager is
-                // present on the device is useful (http://crbug.com/820591).
-                pm.getPackageInfo("projekt.substratum", 0);
-            } catch (NameNotFoundException e) {
-                hasCustomThemes = "false";
-            }
-            customThemes = hasCustomThemes;
-
-            String currentResourcesVersion;
-            try {
-                // This value can be compared with the actual product version to determine if
-                // corrupted resources were the cause of a crash. This can happen if the app
-                // loads resources from the outdated package  during an update
-                // (http://crbug.com/820591).
-                currentResourcesVersion = ContextUtils.getApplicationContext().getString(
-                        BuildConfig.R_STRING_PRODUCT_VERSION);
-            } catch (Exception e) {
-                currentResourcesVersion = "Not found";
-            }
-            resourcesVersion = currentResourcesVersion;
-
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                 abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS);
             } else {
diff --git a/base/android/java/templates/BuildConfig.template b/base/android/java/templates/BuildConfig.template
index 1006d12e..2b4d1a6f 100644
--- a/base/android/java/templates/BuildConfig.template
+++ b/base/android/java/templates/BuildConfig.template
@@ -57,14 +57,4 @@
 #else
     public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = {};
 #endif
-
-    // The ID of the android string resource that stores the product version.
-    // This layer of indirection is necessary to make the resource dependency
-    // optional for android_apk targets/base_java (ex. for cronet).
-#if defined(_RESOURCES_VERSION_VARIABLE)
-    public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = _RESOURCES_VERSION_VARIABLE;
-#else
-    // Default value, do not use.
-    public static MAYBE_FINAL int R_STRING_PRODUCT_VERSION = 0;
-#endif
 }
diff --git a/base/android/jni_generator/README.md b/base/android/jni_generator/README.md
new file mode 100644
index 0000000..17ad150c
--- /dev/null
+++ b/base/android/jni_generator/README.md
@@ -0,0 +1,118 @@
+# Overview
+JNI (Java Native Interface) is the mechanism that enables Java code to call
+native functions, and native code to call Java functions.
+
+ * Native code calls into Java using apis from `<jni.h>`, which basically mirror
+   Java's reflection APIs.
+ * Java code calls native functions by declaring body-less functions with the
+  `native` keyword, and then calling them as normal Java functions.
+
+`jni_generator` generates boiler-plate code with the goal of making our code:
+ 1. easier to write, and
+ 2. typesafe.
+
+`jni_generator` uses regular expressions to parse .Java files, so don't do
+anything too fancy. E.g.:
+ * Classes must be either explicitly imported, or are assumed to be in
+the same package. To use `java.lang` classes, add an explicit import.
+ * Inner classes need to be referenced through the outer class. E.g.:
+   `void call(Outer.Inner inner)`
+
+The presense of any JNI within a class will result in ProGuard obfuscation for
+the class to be disabled.
+
+### Exposing Native Methods
+
+**Without Crazy Linker:**
+ * Java->Native calls are exported from the shared library and lazily resolved
+   by the runtime (via `dlsym()`).
+
+**With Crazy Linker:**
+ * Java->Native calls are explicitly registered with JNI on the native side.
+   Explicit registration is necessary because crazy linker provides its own
+   `dlsym()`, but JNI is hardcoded to use the system's `dlsym()`.
+   * The logic to explicitly register stubs is generated by
+     `jni_registration_generator.py`.
+     * This script finds all native methods by scanning all source `.java` files
+       of an APK. Inefficient, but very convenient.
+   * Since `dlsym()` is not used in this case, we use a linker script to avoid
+     the cost of exporting symbols from the shared library (refer to
+     `//build/config/android:hide_all_but_jni_onload`).
+ * `jni_registration_generator.py` exposes two registrations methods:
+   * `RegisterNonMainDexNatives` - Registers native functions needed by multiple
+     process types (e.g. Rendereres, GPU process).
+   * `RegisterMainDexNatives` - Registers native functions needed only by the
+     browser process.
+
+### Exposing Java Methods
+
+Java methods just need to be annotated with `@CalledByNative`. The generated
+functions can be put into a namespace using `@JNINamespace("your_namespace")`.
+
+## Usage
+
+Because the generator does not generate any source files, generated headers must
+not be `#included` by multiple sources. If there are Java functions that need to
+be called by multiple sources, one source should be chosen to expose the
+functions to the others via additional wrapper functions.
+
+### Calling Java -> Native
+
+ * Methods marked as `native` will have stubs generated for them that forward
+   calls to C++ function (that you must write).
+ * If the first parameter is a C++ object (e.g. `long mNativePointer`), then the
+   bindings will automatically generate the appropriate cast and call into C++
+   code (JNI itself is only C).
+
+### Calling Native -> Java
+
+ * Methods annotated with `@CalledByNative` will have stubs generated for them.
+ * Just call the generated stubs defined in generated `.h` files.
+
+### Java Objects and Garbage Collection
+
+All pointers to Java objects must be registered with JNI in order to prevent
+garbage collection from invalidating them.
+
+For Strings & Arrays - it's common practice to use the `//base/android/jni_*`
+helpers to convert them to `std::vectors` and `std::strings` as soon as
+possible.
+
+For other objects - use smart pointers to store them:
+ * `ScopedJavaLocalRef<>` - When lifetime is the current function's scope.
+ * `ScopedJavaGlobalRef<>` - When lifetime is longer than the current function's
+   scope.
+ * `JavaObjectWeakGlobalRef<>` - Weak reference (do not prevent garbage
+   collection).
+ * `JavaParamRef<>` - Use to accept any of the above as a parameter to a
+   function without creating a redundant registration.
+
+### Additional Guidelines / Advice
+
+Minimize the surface API between the two sides. Rather than calling multiple
+functions across boundaries, call only one (and then on the other side, call as
+many little functions as required).
+
+If a Java object "owns" a native one, store the pointer via
+`"long mNativeClassName"`. Ensure to eventually call a native method to delete
+the object. For example, have a `close()` that deletes the native object.
+
+The best way to pass "compound" types across in either direction is to
+create an inner class with PODs and a factory function. If possible, make mark
+all the fields as "final".
+
+## Build Rules
+
+ * `generate_jni` - Generates a header file with stubs for given `.java` files
+ * `generate_jar_jni` - Generates a header file with stubs for a given `.jar`
+   file
+ * `generate_jni_registration` - Generates a header file with functions to
+   register native-side JNI methods (required only when using crazy linker).
+
+Refer to [//build/config/android/rules.gni](https://cs.chromium.org/chromium/src/build/config/android/rules.gni)
+for more about the GN templates.
+
+## Changing `jni_generator`
+
+ * Python unit tests live in `jni_generator_tests.py`
+ * A working demo app exists as `//base/android/jni_generator:sample_jni_apk`
diff --git a/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java
index f1f39e4..ba3abe7 100644
--- a/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java
+++ b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java
@@ -30,64 +30,6 @@
 //   * link a native executable to prove the generated header + cc file are self-contained.
 // All comments are informational only, and are ignored by the jni generator.
 //
-// Binding C/C++ with Java is not trivial, especially when ownership and object lifetime
-// semantics needs to be managed across boundaries.
-// Following a few guidelines will make the code simpler and less buggy:
-//
-// - Never write any JNI "by hand". Rely on the bindings generator to have a thin
-// layer of type-safety.
-//
-// - Treat the types from the other side as "opaque" as possible. Do not inspect any
-// object directly, but rather, rely on well-defined getters / setters.
-//
-// - Minimize the surface API between the two sides, and rather than calling multiple
-// functions across boundaries, call only one (and then, internally in the other side,
-// call as many little functions as required).
-//
-// - If a Java object "owns" a native object, stash the pointer in a "long mNativeClassName".
-// Note that it needs to have a "destruction path", i.e., it must eventually call a method
-// to delete the native object (for example, the java object has a "close()" method that
-// in turn deletes the native object). Avoid relying on finalizers: those run in a different
-// thread and makes the native lifetime management more difficult.
-//
-// - For native object "owning" java objects:
-//   - If there's a strong 1:1 to relationship between native and java, the best way is to
-//   stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the
-//   java object can be GC'd once the native object is destroyed but note that this global strong
-//   ref implies a new GC root, so be sure it will not leak and it must never rely on being
-//   triggered (transitively) from a java side GC.
-//   - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether
-//   that reference is still valid before de-referencing it. Note that you will need another
-//   java-side object to be holding a strong reference to this java object while it is in use, to
-//   avoid unpredictable GC of the object before native side has finished with it.
-//
-// - The best way to pass "compound" datatypes across in either direction is to create an inner
-// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the
-// fields as "final"). See examples with "InnerStructB" below.
-//
-// - It's simpler to create thin wrappers with a well defined JNI interface than to
-// expose a lot of internal details. This is specially significant for system classes where it's
-// simpler to wrap factory methods and a few getters / setters than expose the entire class.
-//
-// - Iterate over containers where they are originally owned, then create inner structs or
-// directly call methods on the other side. It's much simpler than trying to amalgamate
-// java and stl containers.
-//
-// An important note about qualified class name resolution:
-// The generator doesn't compile the class and have little context about the
-// classes being passed through the JNI layers. It adds a few simple rules:
-//
-// - all classes are either explicitly imported, or they are assumed to be in
-// the same package.
-//
-// - Inner class needs to be done through an import and usage of the
-// outer class, so that the generator knows how to qualify it:
-// import foo.bar.Zoo;
-// void call(Zoo.Inner);
-//
-// - implicitly imported classes aren't supported, so in order to pass
-// things like Runnable, please import java.lang.Runnable;
-//
 // This JNINamespace annotation indicates that all native methods should be
 // generated inside this namespace, including the native class that this
 // object binds to.
diff --git a/base/memory/ref_counted_memory.cc b/base/memory/ref_counted_memory.cc
index be981e3..7999d90 100644
--- a/base/memory/ref_counted_memory.cc
+++ b/base/memory/ref_counted_memory.cc
@@ -4,6 +4,8 @@
 
 #include "base/memory/ref_counted_memory.h"
 
+#include <utility>
+
 #include "base/logging.h"
 
 namespace base {
@@ -80,4 +82,24 @@
   return data_.size();
 }
 
+RefCountedSharedMemory::RefCountedSharedMemory(
+    std::unique_ptr<SharedMemory> shm,
+    size_t size)
+    : shm_(std::move(shm)), size_(size) {
+  DCHECK(shm_);
+  DCHECK(shm_->memory());
+  DCHECK_GT(size_, 0U);
+  DCHECK_LE(size_, shm_->mapped_size());
+}
+
+RefCountedSharedMemory::~RefCountedSharedMemory() = default;
+
+const unsigned char* RefCountedSharedMemory::front() const {
+  return reinterpret_cast<const unsigned char*>(shm_->memory());
+}
+
+size_t RefCountedSharedMemory::size() const {
+  return size_;
+}
+
 }  //  namespace base
diff --git a/base/memory/ref_counted_memory.h b/base/memory/ref_counted_memory.h
index 9d29922..82a3eeb1 100644
--- a/base/memory/ref_counted_memory.h
+++ b/base/memory/ref_counted_memory.h
@@ -7,12 +7,14 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "base/base_export.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
 
 namespace base {
 
@@ -136,6 +138,27 @@
   DISALLOW_COPY_AND_ASSIGN(RefCountedString);
 };
 
+// An implementation of RefCountedMemory, where the bytes are stored in shared
+// memory.
+class BASE_EXPORT RefCountedSharedMemory : public RefCountedMemory {
+ public:
+  // Constructs a RefCountedMemory object by taking ownership of an already
+  // mapped SharedMemory object.
+  RefCountedSharedMemory(std::unique_ptr<SharedMemory> shm, size_t size);
+
+  // RefCountedMemory:
+  const unsigned char* front() const override;
+  size_t size() const override;
+
+ private:
+  ~RefCountedSharedMemory() override;
+
+  const std::unique_ptr<SharedMemory> shm_;
+  const size_t size_;
+
+  DISALLOW_COPY_AND_ASSIGN(RefCountedSharedMemory);
+};
+
 }  // namespace base
 
 #endif  // BASE_MEMORY_REF_COUNTED_MEMORY_H_
diff --git a/base/memory/ref_counted_memory_unittest.cc b/base/memory/ref_counted_memory_unittest.cc
index 72046e52..43f7bbe 100644
--- a/base/memory/ref_counted_memory_unittest.cc
+++ b/base/memory/ref_counted_memory_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <stdint.h>
 
+#include <utility>
+
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -70,6 +72,20 @@
   EXPECT_EQ('e', mem->front()[9]);
 }
 
+TEST(RefCountedMemoryUnitTest, RefCountedSharedMemory) {
+  static const char kData[] = "shm_dummy_data";
+  auto shm = std::make_unique<SharedMemory>();
+  ASSERT_TRUE(shm->CreateAndMapAnonymous(sizeof(kData)));
+  memcpy(shm->memory(), kData, sizeof(kData));
+
+  auto mem =
+      MakeRefCounted<RefCountedSharedMemory>(std::move(shm), sizeof(kData));
+  ASSERT_EQ(sizeof(kData), mem->size());
+  EXPECT_EQ('s', mem->front()[0]);
+  EXPECT_EQ('h', mem->front()[1]);
+  EXPECT_EQ('_', mem->front()[9]);
+}
+
 TEST(RefCountedMemoryUnitTest, Equals) {
   std::string s1("same");
   scoped_refptr<RefCountedMemory> mem1 = RefCountedString::TakeString(&s1);
diff --git a/base/metrics/persistent_histogram_storage.cc b/base/metrics/persistent_histogram_storage.cc
new file mode 100644
index 0000000..cbacca8
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage.cc
@@ -0,0 +1,92 @@
+// 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/metrics/persistent_histogram_storage.h"
+
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/logging.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace {
+
+constexpr size_t kAllocSize = 1 << 20;  // 1 MiB
+
+}  // namespace
+
+namespace base {
+
+PersistentHistogramStorage::PersistentHistogramStorage(
+    StringPiece allocator_name,
+    StorageDirCreation storage_dir_create_action)
+    : storage_dir_create_action_(storage_dir_create_action) {
+  DCHECK(!allocator_name.empty());
+  DCHECK(IsStringASCII(allocator_name));
+
+  GlobalHistogramAllocator::CreateWithLocalMemory(kAllocSize,
+                                                  0,  // No identifier.
+                                                  allocator_name);
+  GlobalHistogramAllocator::Get()->CreateTrackingHistograms(allocator_name);
+}
+
+PersistentHistogramStorage::~PersistentHistogramStorage() {
+  PersistentHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
+  allocator->UpdateTrackingHistograms();
+
+  // Stop if the storage base directory has not been properly set.
+  if (storage_base_dir_.empty()) {
+    LOG(ERROR)
+        << "Could not write \"" << allocator->Name()
+        << "\" persistent histograms to file as the storage base directory "
+           "is not properly set.";
+    return;
+  }
+
+  FilePath storage_dir = storage_base_dir_.AppendASCII(allocator->Name());
+
+  if (storage_dir_create_action_ == StorageDirCreation::kEnable) {
+    // Stop if |storage_dir| does not exist and cannot be created after an
+    // attempt.
+    if (!CreateDirectory(storage_dir)) {
+      LOG(ERROR) << "Could not write \"" << allocator->Name()
+                 << "\" persistent histograms to file as the storage directory "
+                    "cannot be created.";
+      return;
+    }
+  } else if (!DirectoryExists(storage_dir)) {
+    // Stop if |storage_dir| does not exist. That can happen if the product
+    // hasn't been installed yet or if it has been uninstalled.
+    // TODO(chengx): Investigate if there is a need to update setup_main.cc or
+    // test_installer.py so that a LOG(ERROR) statement can be added here
+    // without breaking the test.
+    return;
+  }
+
+  // Save data using the current time as the filename. The actual filename
+  // doesn't matter (so long as it ends with the correct extension) but this
+  // works as well as anything.
+  Time::Exploded exploded;
+  Time::Now().LocalExplode(&exploded);
+  const FilePath file_path =
+      storage_dir
+          .AppendASCII(StringPrintf("%04d%02d%02d%02d%02d%02d", exploded.year,
+                                    exploded.month, exploded.day_of_month,
+                                    exploded.hour, exploded.minute,
+                                    exploded.second))
+          .AddExtension(PersistentMemoryAllocator::kFileExtension);
+
+  StringPiece contents(static_cast<const char*>(allocator->data()),
+                       allocator->used());
+  if (!ImportantFileWriter::WriteFileAtomically(file_path, contents)) {
+    LOG(ERROR) << "Persistent histograms fail to write to file: "
+               << file_path.value();
+  }
+}
+
+}  // namespace base
diff --git a/base/metrics/persistent_histogram_storage.h b/base/metrics/persistent_histogram_storage.h
new file mode 100644
index 0000000..f5883b7
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage.h
@@ -0,0 +1,61 @@
+// 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 BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
+#define BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+// This class creates a fixed sized persistent memory to allow histograms to be
+// stored in it. When a PersistentHistogramStorage is destructed, histograms
+// recorded during its lifetime are persisted in the directory
+// |storage_base_dir_|/|allocator_name| (see the ctor for allocator_name).
+// Histograms are not persisted if the storage directory does not exist on
+// destruction. PersistentHistogramStorage should be instantiated as early as
+// possible in the process lifetime and should never be instantiated again.
+// Persisted histograms will eventually be reported by Chrome.
+class BASE_EXPORT PersistentHistogramStorage {
+ public:
+  enum class StorageDirCreation { kEnable, kDisable };
+
+  // Creates a process-wide storage location for histograms that will be written
+  // to a file within a directory provided by |set_storage_base_dir()| on
+  // destruction.
+  // The |allocator_name| is used both as an internal name for the allocator,
+  // well as the leaf directory name for the file to which the histograms are
+  // persisted. The string must be ASCII.
+  // |storage_dir_create_action| determines that if this instance is
+  // responsible for creating the storage directory.
+  PersistentHistogramStorage(StringPiece allocator_name,
+                             StorageDirCreation storage_dir_create_action);
+
+  ~PersistentHistogramStorage();
+
+  // The storage directory isn't always known during initial construction so
+  // it's set separately. The last one wins if there are multiple calls to this
+  // method.
+  void set_storage_base_dir(const FilePath& storage_base_dir) {
+    storage_base_dir_ = storage_base_dir;
+  }
+
+ private:
+  // Metrics files are written into directory
+  // |storage_base_dir_|/|allocator_name| (see the ctor for allocator_name).
+  FilePath storage_base_dir_;
+
+  // A flag indicating if the class instance is responsible for creating the
+  // storage directory.
+  const StorageDirCreation storage_dir_create_action_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorage);
+};
+
+}  // namespace base
+
+#endif  // BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
diff --git a/base/metrics/persistent_histogram_storage_unittest.cc b/base/metrics/persistent_histogram_storage_unittest.cc
new file mode 100644
index 0000000..4a9b34b
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage_unittest.cc
@@ -0,0 +1,75 @@
+// 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/metrics/persistent_histogram_storage.h"
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Name of the allocator for storing histograms.
+constexpr char kTestHistogramAllocatorName[] = "TestMetrics";
+
+}  // namespace
+
+class PersistentHistogramStorageTest : public testing::Test {
+ protected:
+  PersistentHistogramStorageTest() = default;
+  ~PersistentHistogramStorageTest() override = default;
+
+  // Creates a unique temporary directory, and sets the test storage directory.
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    test_storage_dir_ =
+        temp_dir_path().AppendASCII(kTestHistogramAllocatorName);
+  }
+
+  // Gets the path to the temporary directory.
+  const FilePath& temp_dir_path() { return temp_dir_.GetPath(); }
+
+  const FilePath& test_storage_dir() { return test_storage_dir_; }
+
+ private:
+  // A temporary directory where all file IO operations take place.
+  ScopedTempDir temp_dir_;
+
+  // The directory into which metrics files are written.
+  FilePath test_storage_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorageTest);
+};
+
+#if !defined(OS_NACL)
+TEST_F(PersistentHistogramStorageTest, HistogramWriteTest) {
+  auto persistent_histogram_storage =
+      std::make_unique<PersistentHistogramStorage>(
+          kTestHistogramAllocatorName,
+          PersistentHistogramStorage::StorageDirCreation::kEnable);
+
+  persistent_histogram_storage->set_storage_base_dir(temp_dir_path());
+
+  // Log some random data.
+  UMA_HISTOGRAM_BOOLEAN("Some.Test.Metric", true);
+
+  // Deleting the object causes the data to be written to the disk.
+  persistent_histogram_storage.reset();
+
+  // The storage directory and the histogram file are created during the
+  // destruction of the PersistentHistogramStorage instance.
+  EXPECT_TRUE(DirectoryExists(test_storage_dir()));
+  EXPECT_FALSE(IsDirectoryEmpty(test_storage_dir()));
+}
+#endif  // !defined(OS_NACL)
+
+}  // namespace base
diff --git a/base/profiler/native_stack_sampler_mac.cc b/base/profiler/native_stack_sampler_mac.cc
index e72283e1..93f944e 100644
--- a/base/profiler/native_stack_sampler_mac.cc
+++ b/base/profiler/native_stack_sampler_mac.cc
@@ -13,6 +13,7 @@
 #include <mach/kern_return.h>
 #include <mach/mach.h>
 #include <mach/thread_act.h>
+#include <mach/vm_map.h>
 #include <pthread.h>
 #include <sys/resource.h>
 #include <sys/syslimits.h>
@@ -95,6 +96,18 @@
   return std::string();
 }
 
+// Returns the size of the _TEXT segment of the module loaded at |module_addr|.
+size_t GetModuleTextSize(const void* module_addr) {
+  const mach_header_64* mach_header =
+      reinterpret_cast<const mach_header_64*>(module_addr);
+  DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
+
+  unsigned long module_size;
+  getsegmentdata(mach_header, SEG_TEXT, &module_size);
+
+  return module_size;
+}
+
 // Gets the index for the Module containing |instruction_pointer| in
 // |modules|, adding it if it's not already present. Returns
 // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be
@@ -122,16 +135,11 @@
       base::FilePath(inf.dli_fname));
   modules->push_back(module);
 
-  const mach_header_64* mach_header =
-      reinterpret_cast<const mach_header_64*>(inf.dli_fbase);
-  DCHECK_EQ(MH_MAGIC_64, mach_header->magic);
-
-  unsigned long module_size;
-  getsegmentdata(mach_header, SEG_TEXT, &module_size);
-  uintptr_t base_module_address = reinterpret_cast<uintptr_t>(mach_header);
+  uintptr_t base_module_address = reinterpret_cast<uintptr_t>(inf.dli_fbase);
   size_t index = modules->size() - 1;
-  profile_module_index->emplace_back(base_module_address,
-                                     base_module_address + module_size, index);
+  profile_module_index->emplace_back(
+      base_module_address,
+      base_module_address + GetModuleTextSize(inf.dli_fbase), index);
   return index;
 }
 
@@ -228,6 +236,39 @@
       (((1 << __builtin_popcount(UNWIND_X86_64_RBP_FRAME_OFFSET))) - 1));
 }
 
+// True if the unwind from |leaf_frame_rip| may trigger a crash bug in
+// unw_init_local. If so, the stack walk should be aborted at the leaf frame.
+bool MayTriggerUnwInitLocalCrash(uint64_t leaf_frame_rip) {
+  // The issue here is a bug in unw_init_local that, in some unwinds, results in
+  // attempts to access memory at the address immediately following the address
+  // range of the library. When the library is the last of the mapped libraries
+  // that address is in a different memory region. Starting with 10.13.4 beta
+  // releases it appears that this region is sometimes either unmapped or mapped
+  // without read access, resulting in crashes on the attempted access. It's not
+  // clear what circumstances result in this situation; attempts to reproduce on
+  // a 10.13.4 beta did not trigger the issue.
+  //
+  // The workaround is to check if the memory address that would be accessed is
+  // readable, and if not, abort the stack walk before calling unw_init_local.
+  // As of 2018/03/19 about 0.1% of non-idle stacks on the UI and GPU main
+  // threads have a leaf frame in the last library. Since the issue appears to
+  // only occur some of the time it's expected that the quantity of lost samples
+  // will be lower than 0.1%, possibly significantly lower.
+  //
+  // TODO(lgrey): Add references above to LLVM/Radar bugs on unw_init_local once
+  // filed.
+  Dl_info info;
+  if (dladdr(reinterpret_cast<const void*>(leaf_frame_rip), &info) == 0)
+    return false;
+  uint64_t unused;
+  vm_size_t size = sizeof(unused);
+  return vm_read_overwrite(current_task(),
+                           reinterpret_cast<vm_address_t>(info.dli_fbase) +
+                               GetModuleTextSize(info.dli_fbase),
+                           sizeof(unused),
+                           reinterpret_cast<vm_address_t>(&unused), &size) != 0;
+}
+
 // Walks the stack represented by |unwind_context|, calling back to the provided
 // lambda for each frame. Returns false if an error occurred, otherwise returns
 // true.
@@ -557,11 +598,20 @@
   auto* current_modules = current_modules_;
   auto* profile_module_index = &profile_module_index_;
 
-  // Unwinding sigtramp remotely is very fragile. It's a complex DWARF unwind
-  // that needs to restore the entire thread context which was saved by the
-  // kernel when the interrupt occurred. Bail instead of risking a crash.
+  // Check for two execution cases where we're unable to unwind, and if found,
+  // record the first frame and and bail:
+  //
+  // 1. In sigtramp: Unwinding this from another thread is very fragile. It's a
+  // complex DWARF unwind that needs to restore the entire thread context which
+  // was saved by the kernel when the interrupt occurred.
+  //
+  // 2. In the last mapped module and the memory past the module is
+  // inaccessible: unw_init_local has a bug where it attempts to access the
+  // memory immediately after the module, resulting in crashes. See
+  // MayTriggerUnwInitLocalCrash for details.
   uintptr_t ip = thread_state.__rip;
-  if (ip >= sigtramp_start_ && ip < sigtramp_end_) {
+  if ((ip >= sigtramp_start_ && ip < sigtramp_end_) ||
+      MayTriggerUnwInitLocalCrash(ip)) {
     sample->frames.emplace_back(
         ip, GetModuleIndex(ip, current_modules, profile_module_index));
     return;
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index a5834e1..e3d70e3 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -93,6 +93,7 @@
 class CommandBufferClientImpl;
 class CommandBufferLocal;
 class GpuState;
+class MaterialDesignController;
 }
 namespace net {
 class MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
@@ -218,6 +219,7 @@
   friend class cronet::CronetURLRequestContext;
   friend class mojo::CoreLibraryInitializer;
   friend class resource_coordinator::TabManagerDelegate;  // crbug.com/778703
+  friend class ui::MaterialDesignController;
   friend class ScopedAllowBlockingForTesting;
   friend class StackSamplingProfiler;
 
diff --git a/base/values.cc b/base/values.cc
index 0dc55790..c0ac5db8 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -554,8 +554,7 @@
         return false;
       return std::equal(std::begin(lhs.dict_), std::end(lhs.dict_),
                         std::begin(rhs.dict_),
-                        [](const Value::DictStorage::value_type& u,
-                           const Value::DictStorage::value_type& v) {
+                        [](const auto& u, const auto& v) {
                           return std::tie(u.first, *u.second) ==
                                  std::tie(v.first, *v.second);
                         });
diff --git a/build/android/templates/res/values/version_strings.xml.template b/build/android/templates/res/values/version_strings.xml.template
deleted file mode 100644
index 889f4710..0000000
--- a/build/android/templates/res/values/version_strings.xml.template
+++ /dev/null
@@ -1,10 +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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-  <string name="{{resource_name}}" translatable="false" tools:ignore="UnusedResources">{{product_version}}</string>
-</resources>
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index ce66704..5de06ce 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1714,11 +1714,6 @@
         if (defined(invoker.firebase_app_id)) {
           defines += [ "_FIREBASE_APP_ID=${invoker.firebase_app_id}" ]
         }
-        if (defined(invoker.resources_version_variable)) {
-          defines += [
-            "_RESOURCES_VERSION_VARIABLE=${invoker.resources_version_variable}",
-          ]
-        }
       }
     }
   }
@@ -2033,34 +2028,6 @@
           "$_base_path.resources.main-dex-proguard.txt"
     }
     _generated_proguard_config = "$_base_path.resources.proguard.txt"
-
-    if (_generate_buildconfig_java) {
-      _version_resources_target = "${_template_name}__version_resources"
-      _version_resources_target_tmpl = "${_version_resources_target}_template"
-      _version_res_dir = "$target_gen_dir/$_template_name/res/"
-      _version_res_file = "$_version_res_dir/values/version_strings.xml"
-      _product_version_resource_name = "product_version"
-      jinja_template(_version_resources_target_tmpl) {
-        input =
-            "//build/android/templates/res/values/version_strings.xml.template"
-        output = _version_res_file
-        variables = [
-          "product_version=$_version_name",
-          "resource_name=$_product_version_resource_name",
-        ]
-      }
-      android_resources(_version_resources_target) {
-        deps = [
-          ":$_version_resources_target_tmpl",
-        ]
-        resource_dirs = []
-        generated_resource_dirs = [ _version_res_dir ]
-        generated_resource_files = [ _version_res_file ]
-        custom_package = "org.chromium.base"
-      }
-      _deps += [ ":$_version_resources_target" ]
-    }
-
     _compile_resources_target = "${_template_name}__compile_resources"
     compile_resources(_compile_resources_target) {
       forward_variables_from(invoker,
@@ -2195,9 +2162,6 @@
         use_final_fields = true
         build_config = _build_config
         enable_multidex = _enable_multidex
-        resources_version_variable =
-            "org.chromium.base.R.string.${_product_version_resource_name}"
-
         deps = [
           ":$_build_config_target",
         ]
diff --git a/build/config/clang/BUILD.gn b/build/config/clang/BUILD.gn
index 522efa7..7ac3dc8 100644
--- a/build/config/clang/BUILD.gn
+++ b/build/config/clang/BUILD.gn
@@ -35,6 +35,16 @@
       "find-bad-constructs",
     ]
 
+    # This option should be safe to use by default, and this also
+    # works with distributed build systems such as icecc.
+    # TODO(mostynb@vewd.com): update the plugin and remove these flags.
+    cflags += [
+      "-Xclang",
+      "-plugin-arg-find-bad-constructs",
+      "-Xclang",
+      "no-realpath",
+    ]
+
     if (is_linux || is_android || is_fuchsia) {
       cflags += [
         "-Xclang",
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 67db085..dc076cb 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -175,6 +175,8 @@
     "raster/tile_task.h",
     "raster/zero_copy_raster_buffer_provider.cc",
     "raster/zero_copy_raster_buffer_provider.h",
+    "resources/cross_thread_shared_bitmap.cc",
+    "resources/cross_thread_shared_bitmap.h",
     "resources/display_resource_provider.cc",
     "resources/display_resource_provider.h",
     "resources/layer_tree_resource_provider.cc",
@@ -188,6 +190,8 @@
     "resources/resource_provider.h",
     "resources/scoped_ui_resource.cc",
     "resources/scoped_ui_resource.h",
+    "resources/shared_bitmap_id_registrar.cc",
+    "resources/shared_bitmap_id_registrar.h",
     "resources/ui_resource_bitmap.cc",
     "resources/ui_resource_bitmap.h",
     "resources/ui_resource_client.h",
diff --git a/cc/layers/texture_layer.cc b/cc/layers/texture_layer.cc
index 935ef9a..f0fa766a 100644
--- a/cc/layers/texture_layer.cc
+++ b/cc/layers/texture_layer.cc
@@ -23,7 +23,8 @@
   return scoped_refptr<TextureLayer>(new TextureLayer(client));
 }
 
-TextureLayer::TextureLayer(TextureLayerClient* client) : client_(client) {}
+TextureLayer::TextureLayer(TextureLayerClient* client)
+    : client_(client), weak_ptr_factory_(this) {}
 
 TextureLayer::~TextureLayer() = default;
 
@@ -154,6 +155,15 @@
     // commit is called complete.
     SetNextCommitWaitsForActivation();
   }
+  if (host) {
+    // When attached to a new LayerTreHost, all previously registered
+    // SharedBitmapIds will need to be re-sent to the new TextureLayerImpl
+    // representing this layer on the compositor thread.
+    to_register_bitmaps_.insert(
+        std::make_move_iterator(registered_bitmaps_.begin()),
+        std::make_move_iterator(registered_bitmaps_.end()));
+    registered_bitmaps_.clear();
+  }
   Layer::SetLayerTreeHost(host);
 }
 
@@ -166,7 +176,8 @@
   if (client_) {
     viz::TransferableResource resource;
     std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-    if (client_->PrepareTransferableResource(&resource, &release_callback)) {
+    if (client_->PrepareTransferableResource(this, &resource,
+                                             &release_callback)) {
       // Already within a commit, no need to do another one immediately.
       bool requires_commit = false;
       SetTransferableResourceInternal(resource, std::move(release_callback),
@@ -210,6 +221,49 @@
                                            std::move(release_callback));
     needs_set_resource_ = false;
   }
+  for (auto& pair : to_register_bitmaps_)
+    texture_layer->RegisterSharedBitmapId(pair.first, pair.second);
+  // Store the registered SharedBitmapIds in case we get a new TextureLayerImpl,
+  // in a new tree, to re-send them to.
+  registered_bitmaps_.insert(
+      std::make_move_iterator(to_register_bitmaps_.begin()),
+      std::make_move_iterator(to_register_bitmaps_.end()));
+  to_register_bitmaps_.clear();
+  for (const auto& id : to_unregister_bitmap_ids_)
+    texture_layer->UnregisterSharedBitmapId(id);
+  to_unregister_bitmap_ids_.clear();
+}
+
+SharedBitmapIdRegistration TextureLayer::RegisterSharedBitmapId(
+    const viz::SharedBitmapId& id,
+    scoped_refptr<CrossThreadSharedBitmap> bitmap) {
+  DCHECK(to_register_bitmaps_.find(id) == to_register_bitmaps_.end());
+  DCHECK(registered_bitmaps_.find(id) == registered_bitmaps_.end());
+  to_register_bitmaps_[id] = std::move(bitmap);
+  base::Erase(to_unregister_bitmap_ids_, id);
+  // This does not SetNeedsCommit() to be as lazy as possible. Notifying a
+  // SharedBitmapId is not needed until it is used, and using it will require
+  // a commit, so we can wait for that commit before forwarding the
+  // notification instead of forcing it to happen as a side effect of this
+  // method.
+  SetNeedsPushProperties();
+  return SharedBitmapIdRegistration(weak_ptr_factory_.GetWeakPtr(), id);
+}
+
+void TextureLayer::UnregisterSharedBitmapId(viz::SharedBitmapId id) {
+  // If we didn't get to sending the registration to the compositor thread yet,
+  // just remove it.
+  to_register_bitmaps_.erase(id);
+  // Since we also track all previously sent registrations, we must remove that
+  // to in order to prevent re-registering on another LayerTreeHost.
+  registered_bitmaps_.erase(id);
+
+  to_unregister_bitmap_ids_.push_back(id);
+  // Unregistering a SharedBitmapId needs to happen eventually to prevent
+  // leaking the SharedMemory in the display compositor. But this attempts to be
+  // lazy and not force a commit prematurely, so just requests a
+  // PushPropertiesTo() without requesting a commit.
+  SetNeedsPushProperties();
 }
 
 TextureLayer::TransferableResourceHolder::MainThreadReference::
diff --git a/cc/layers/texture_layer.h b/cc/layers/texture_layer.h
index ae19b371..af32a87 100644
--- a/cc/layers/texture_layer.h
+++ b/cc/layers/texture_layer.h
@@ -9,10 +9,13 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
 #include "cc/cc_export.h"
 #include "cc/layers/layer.h"
+#include "cc/resources/cross_thread_shared_bitmap.h"
+#include "cc/resources/shared_bitmap_id_registrar.h"
 #include "components/viz/common/resources/transferable_resource.h"
 
 namespace gpu {
@@ -25,10 +28,14 @@
 
 namespace cc {
 class SingleReleaseCallback;
+class TextureLayer;
 class TextureLayerClient;
 
-// A Layer containing a the rendered output of a plugin instance.
-class CC_EXPORT TextureLayer : public Layer {
+// A Layer containing a the rendered output of a plugin instance. It can be used
+// to display gpu or software resources, depending if the compositor is working
+// in gpu or software compositing mode (the resources must match the compositing
+// mode).
+class CC_EXPORT TextureLayer : public Layer, SharedBitmapIdRegistrar {
  public:
   class CC_EXPORT TransferableResourceHolder
       : public base::RefCountedThreadSafe<TransferableResourceHolder> {
@@ -143,6 +150,17 @@
   bool IsSnapped() override;
   void PushPropertiesTo(LayerImpl* layer) override;
 
+  // Request a mapping from SharedBitmapId to SharedMemory be registered via the
+  // LayerTreeFrameSink with the display compositor. Once this mapping is
+  // registered, the SharedBitmapId can be used in TransferableResources given
+  // to the TextureLayer for display. The SharedBitmapId registration will end
+  // when the returned SharedBitmapIdRegistration object is destroyed.
+  // Implemented as a SharedBitmapIdRegistrar interface so that clients can
+  // have a limited API access.
+  SharedBitmapIdRegistration RegisterSharedBitmapId(
+      const viz::SharedBitmapId& id,
+      scoped_refptr<CrossThreadSharedBitmap> bitmap) override;
+
  protected:
   explicit TextureLayer(TextureLayerClient* client);
   ~TextureLayer() override;
@@ -154,6 +172,12 @@
       std::unique_ptr<viz::SingleReleaseCallback> release_callback,
       bool requires_commit);
 
+  // Friends to give access to UnregisterSharedBitmapId().
+  friend SharedBitmapIdRegistration;
+  // Remove a mapping from SharedBitmapId to SharedMemory in the display
+  // compositor.
+  void UnregisterSharedBitmapId(viz::SharedBitmapId id);
+
   TextureLayerClient* client_;
 
   bool flipped_ = true;
@@ -168,6 +192,24 @@
   std::unique_ptr<TransferableResourceHolder::MainThreadReference> holder_ref_;
   bool needs_set_resource_ = false;
 
+  // The set of SharedBitmapIds to register with the LayerTreeFrameSink on the
+  // compositor thread. These requests are forwarded to the TextureLayerImpl to
+  // use, then stored in |registered_bitmaps_| to re-send if the
+  // TextureLayerImpl object attached to this layer changes, by moving out of
+  // the LayerTreeHost.
+  base::flat_map<viz::SharedBitmapId, scoped_refptr<CrossThreadSharedBitmap>>
+      to_register_bitmaps_;
+  // The set of previously registered SharedBitmapIds for the current
+  // LayerTreeHost. If the LayerTreeHost changes, these must be re-sent to the
+  // (new) TextureLayerImpl to be re-registered.
+  base::flat_map<viz::SharedBitmapId, scoped_refptr<CrossThreadSharedBitmap>>
+      registered_bitmaps_;
+  // The SharedBitmapIds to unregister on the compositor thread, passed to the
+  // TextureLayerImpl.
+  std::vector<viz::SharedBitmapId> to_unregister_bitmap_ids_;
+
+  base::WeakPtrFactory<TextureLayer> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(TextureLayer);
 };
 
diff --git a/cc/layers/texture_layer_client.h b/cc/layers/texture_layer_client.h
index a535d50..71c217e 100644
--- a/cc/layers/texture_layer_client.h
+++ b/cc/layers/texture_layer_client.h
@@ -12,6 +12,7 @@
 }
 
 namespace cc {
+class SharedBitmapIdRegistrar;
 
 class TextureLayerClient {
  public:
@@ -19,6 +20,7 @@
   // Returns false if no new data is available
   // and the old mailbox is to be reused.
   virtual bool PrepareTransferableResource(
+      SharedBitmapIdRegistrar* bitmap_registar,
       viz::TransferableResource* transferable_resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) = 0;
 
diff --git a/cc/layers/texture_layer_impl.cc b/cc/layers/texture_layer_impl.cc
index b57570f..b0203d5 100644
--- a/cc/layers/texture_layer_impl.cc
+++ b/cc/layers/texture_layer_impl.cc
@@ -10,10 +10,12 @@
 #include <vector>
 
 #include "base/strings/stringprintf.h"
+#include "cc/trees/layer_tree_frame_sink.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/occlusion.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/platform_color.h"
 #include "components/viz/common/resources/single_release_callback.h"
 
@@ -24,17 +26,15 @@
 
 TextureLayerImpl::~TextureLayerImpl() {
   FreeTransferableResource();
-}
 
-void TextureLayerImpl::SetTransferableResource(
-    const viz::TransferableResource& resource,
-    std::unique_ptr<viz::SingleReleaseCallback> release_callback) {
-  DCHECK_EQ(resource.mailbox_holder.mailbox.IsZero(), !release_callback);
-  FreeTransferableResource();
-  transferable_resource_ = resource;
-  release_callback_ = std::move(release_callback);
-  own_resource_ = true;
-  SetNeedsPushProperties();
+  LayerTreeFrameSink* sink = layer_tree_impl()->layer_tree_frame_sink();
+  // The LayerTreeFrameSink may be gone, in which case there's no need to
+  // unregister anything.
+  if (sink) {
+    for (const auto& pair : registered_bitmaps_) {
+      sink->DidDeleteSharedBitmap(pair.first);
+    }
+  }
 }
 
 std::unique_ptr<LayerImpl> TextureLayerImpl::CreateLayerImpl(
@@ -61,6 +61,12 @@
                                            std::move(release_callback_));
     own_resource_ = false;
   }
+  for (auto& pair : to_register_bitmaps_)
+    texture_layer->RegisterSharedBitmapId(pair.first, std::move(pair.second));
+  to_register_bitmaps_.clear();
+  for (const auto& id : to_unregister_bitmap_ids_)
+    texture_layer->UnregisterSharedBitmapId(id);
+  to_unregister_bitmap_ids_.clear();
 }
 
 bool TextureLayerImpl::WillDraw(DrawMode draw_mode,
@@ -97,6 +103,25 @@
                                    AppendQuadsData* append_quads_data) {
   DCHECK(resource_id_);
 
+  LayerTreeFrameSink* sink = layer_tree_impl()->layer_tree_frame_sink();
+  for (const auto& pair : to_register_bitmaps_) {
+    // Because we may want to notify a display compositor about this
+    // base::SharedMemory more than one time, we need to be able to keep
+    // making handles to share with it, so we can't close the
+    // base::SharedMemory.
+    mojo::ScopedSharedBufferHandle handle =
+        viz::bitmap_allocation::DuplicateWithoutClosingMappedBitmap(
+            pair.second->shared_memory(), pair.second->size(),
+            pair.second->format());
+    sink->DidAllocateSharedBitmap(std::move(handle), pair.first);
+  }
+  // All |to_register_bitmaps_| have been registered above, so we can move them
+  // all to the |registered_bitmaps_|.
+  registered_bitmaps_.insert(
+      std::make_move_iterator(to_register_bitmaps_.begin()),
+      std::make_move_iterator(to_register_bitmaps_.end()));
+  to_register_bitmaps_.clear();
+
   SkColor bg_color =
       blend_background_color_ ? background_color() : SK_ColorTRANSPARENT;
   bool are_contents_opaque =
@@ -143,6 +168,19 @@
 void TextureLayerImpl::ReleaseResources() {
   FreeTransferableResource();
   resource_id_ = 0;
+
+  // The LayerTreeFrameSink is gone and being replaced, so we will have to
+  // re-register all SharedBitmapIds on the new LayerTreeFrameSink. We don't
+  // need to do that until the SharedBitmapIds will be used, in AppendQuads(),
+  // but we mark them all as to be registered here.
+  to_register_bitmaps_.insert(
+      std::make_move_iterator(registered_bitmaps_.begin()),
+      std::make_move_iterator(registered_bitmaps_.end()));
+  registered_bitmaps_.clear();
+  // The |to_unregister_bitmap_ids_| are kept since the active layer will re-
+  // register its SharedBitmapIds with a new LayerTreeFrameSink in the future,
+  // so we must remember that we want to unregister it (or avoid registering at
+  // all) instead.
 }
 
 void TextureLayerImpl::SetPremultipliedAlpha(bool premultiplied_alpha) {
@@ -186,12 +224,60 @@
   SetNeedsPushProperties();
 }
 
+void TextureLayerImpl::SetTransferableResource(
+    const viz::TransferableResource& resource,
+    std::unique_ptr<viz::SingleReleaseCallback> release_callback) {
+  DCHECK_EQ(resource.mailbox_holder.mailbox.IsZero(), !release_callback);
+  FreeTransferableResource();
+  transferable_resource_ = resource;
+  release_callback_ = std::move(release_callback);
+  own_resource_ = true;
+  SetNeedsPushProperties();
+}
+
+void TextureLayerImpl::RegisterSharedBitmapId(
+    viz::SharedBitmapId id,
+    scoped_refptr<CrossThreadSharedBitmap> bitmap) {
+  // If a TextureLayer leaves and rejoins a tree without the TextureLayerImpl
+  // being destroyed, then it will re-request registration of ids that are still
+  // registered on the impl side, so we can just ignore these requests.
+  if (registered_bitmaps_.find(id) == registered_bitmaps_.end()) {
+    // If this is a pending layer, these will be moved to the active layer when
+    // we PushPropertiesTo(). Otherwise, we don't need to notify these to the
+    // LayerTreeFrameSink until we're going to use them, so defer it until
+    // AppendQuads().
+    to_register_bitmaps_[id] = std::move(bitmap);
+  }
+  base::Erase(to_unregister_bitmap_ids_, id);
+  SetNeedsPushProperties();
+}
+
+void TextureLayerImpl::UnregisterSharedBitmapId(viz::SharedBitmapId id) {
+  if (IsActive()) {
+    LayerTreeFrameSink* sink = layer_tree_impl()->layer_tree_frame_sink();
+    if (sink && registered_bitmaps_.find(id) != registered_bitmaps_.end())
+      sink->DidDeleteSharedBitmap(id);
+    to_register_bitmaps_.erase(id);
+    registered_bitmaps_.erase(id);
+  } else {
+    // The active layer will unregister. We do this because it may be using the
+    // SharedBitmapId, so we should remove the SharedBitmapId only after we've
+    // had a chance to replace it with activation.
+    to_unregister_bitmap_ids_.push_back(id);
+    SetNeedsPushProperties();
+  }
+}
+
 const char* TextureLayerImpl::LayerTypeAsString() const {
   return "cc::TextureLayerImpl";
 }
 
 void TextureLayerImpl::FreeTransferableResource() {
   if (own_resource_) {
+    // TODO(crbug.com/826886): Software resources should be kept alive.
+    // if (transferable_resource_.is_software)
+    //  return;
+
     DCHECK(!resource_id_);
     if (release_callback_) {
       // We didn't use the resource, but the client might need the SyncToken
@@ -202,6 +288,9 @@
     transferable_resource_ = viz::TransferableResource();
     release_callback_ = nullptr;
   } else if (resource_id_) {
+    // TODO(crbug.com/826886): Ownership of software resources should be
+    // reclaimed, including the ReleaseCalback, without running it.
+
     DCHECK(!own_resource_);
     auto* resource_provider = layer_tree_impl()->resource_provider();
     resource_provider->RemoveImportedResource(resource_id_);
diff --git a/cc/layers/texture_layer_impl.h b/cc/layers/texture_layer_impl.h
index d688902..49b6be2f 100644
--- a/cc/layers/texture_layer_impl.h
+++ b/cc/layers/texture_layer_impl.h
@@ -8,10 +8,12 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "cc/cc_export.h"
 #include "cc/layers/layer_impl.h"
+#include "cc/resources/cross_thread_shared_bitmap.h"
 #include "components/viz/common/resources/transferable_resource.h"
 
 namespace viz {
@@ -60,6 +62,20 @@
       const viz::TransferableResource& resource,
       std::unique_ptr<viz::SingleReleaseCallback> release_callback);
 
+  // These methods notify the display compositor, through the
+  // CompositorFrameSink, of the existance of a SharedBitmapId and its
+  // mapping to a SharedMemory in |bitmap|. Then this SharedBitmapId can be used
+  // in TransferableResources inserted on the layer while it is registered. If
+  // the layer is destroyed, the SharedBitmapId will be unregistered
+  // automatically, and if the CompositorFrameSink is replaced, it will be
+  // re-registered on the new one. The SharedMemory must be kept alive while it
+  // is registered.
+  // If this is a pending layer, the registration is deferred to the active
+  // layer.
+  void RegisterSharedBitmapId(viz::SharedBitmapId id,
+                              scoped_refptr<CrossThreadSharedBitmap> bitmap);
+  void UnregisterSharedBitmapId(viz::SharedBitmapId id);
+
  private:
   TextureLayerImpl(LayerTreeImpl* tree_impl, int id);
 
@@ -87,6 +103,26 @@
   viz::ResourceId resource_id_ = 0;
   std::unique_ptr<viz::SingleReleaseCallback> release_callback_;
 
+  // As a pending layer, the set of SharedBitmapIds and the underlying
+  // base::SharedMemory that must be notified to the display compositor through
+  // the LayerTreeFrameSink. These will be passed to the active layer. As an
+  // active layer, the set of SharedBitmapIds that need to be registered but
+  // have not been yet, since it is done lazily.
+  base::flat_map<viz::SharedBitmapId, scoped_refptr<CrossThreadSharedBitmap>>
+      to_register_bitmaps_;
+
+  // For active layers only. The set of SharedBitmapIds and ownership of the
+  // underlying base::SharedMemory that have been notified to the display
+  // compositor through the LayerTreeFrameSink. These will need to be
+  // re-registered if the LayerTreeFrameSink changes (ie ReleaseResources()
+  // occurs).
+  base::flat_map<viz::SharedBitmapId, scoped_refptr<CrossThreadSharedBitmap>>
+      registered_bitmaps_;
+
+  // As a pending layer, the set of SharedBitmapIds that the active layer should
+  // unregister.
+  std::vector<viz::SharedBitmapId> to_unregister_bitmap_ids_;
+
   DISALLOW_COPY_AND_ASSIGN(TextureLayerImpl);
 };
 
diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc
index fbdbf44..2d9b4a88 100644
--- a/cc/layers/texture_layer_unittest.cc
+++ b/cc/layers/texture_layer_unittest.cc
@@ -38,8 +38,12 @@
 #include "cc/trees/single_thread_proxy.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/gpu/raster_context_provider.h"
+#include "components/viz/common/quads/shared_bitmap.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/returned_resource.h"
 #include "components/viz/common/resources/transferable_resource.h"
+#include "components/viz/service/display/software_output_device.h"
+#include "components/viz/test/fake_output_surface.h"
 #include "components/viz/test/test_layer_tree_frame_sink.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -926,6 +930,7 @@
  public:
   // TextureLayerClient implementation.
   bool PrepareTransferableResource(
+      SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override {
     if (layer_tree_host()->SourceFrameNumber() == 1) {
@@ -1004,6 +1009,7 @@
 
   // TextureLayerClient implementation.
   bool PrepareTransferableResource(
+      SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override {
     ++prepare_called_;
@@ -1123,6 +1129,7 @@
  public:
   // TextureLayerClient implementation.
   bool PrepareTransferableResource(
+      SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override {
     *resource = viz::TransferableResource::MakeGL(
@@ -1330,5 +1337,556 @@
 
 SINGLE_AND_MULTI_THREAD_TEST_F(TextureLayerWithResourceImplThreadDeleted);
 
+class StubTextureLayerClient : public TextureLayerClient {
+ public:
+  // TextureLayerClient implementation.
+  bool PrepareTransferableResource(
+      SharedBitmapIdRegistrar* bitmap_registrar,
+      viz::TransferableResource* resource,
+      std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override {
+    return false;
+  }
+};
+
+class SoftwareLayerTreeHostClient : public StubLayerTreeHostClient {
+ public:
+  SoftwareLayerTreeHostClient() = default;
+  ~SoftwareLayerTreeHostClient() override = default;
+
+  // Caller responsible for unsetting this and maintaining the host's lifetime.
+  void SetLayerTreeHost(LayerTreeHost* host) { host_ = host; }
+
+  // StubLayerTreeHostClient overrides.
+  void RequestNewLayerTreeFrameSink() override {
+    auto sink = FakeLayerTreeFrameSink::CreateSoftware();
+    frame_sink_ = sink.get();
+    host_->SetLayerTreeFrameSink(std::move(sink));
+  }
+
+  FakeLayerTreeFrameSink* frame_sink() const { return frame_sink_; }
+
+ private:
+  FakeLayerTreeFrameSink* frame_sink_ = nullptr;
+  LayerTreeHost* host_ = nullptr;
+};
+
+class SoftwareTextureLayerTest : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    root_ = Layer::Create();
+    root_->SetBounds(gfx::Size(10, 10));
+
+    // A drawable layer so that frames always get drawn.
+    solid_color_layer_ = SolidColorLayer::Create();
+    solid_color_layer_->SetIsDrawable(true);
+    solid_color_layer_->SetBackgroundColor(SK_ColorRED);
+    solid_color_layer_->SetBounds(gfx::Size(10, 10));
+    root_->AddChild(solid_color_layer_);
+
+    texture_layer_ = TextureLayer::CreateForMailbox(&client_);
+    texture_layer_->SetIsDrawable(true);
+    texture_layer_->SetBounds(gfx::Size(10, 10));
+    layer_tree_host()->SetRootLayer(root_);
+    LayerTreeTest::SetupTree();
+  }
+
+  std::unique_ptr<viz::TestLayerTreeFrameSink> CreateLayerTreeFrameSink(
+      const viz::RendererSettings& renderer_settings,
+      double refresh_rate,
+      scoped_refptr<viz::ContextProvider> compositor_context_provider,
+      scoped_refptr<viz::RasterContextProvider> worker_context_provider)
+      override {
+    constexpr bool disable_display_vsync = false;
+    bool synchronous_composite =
+        !HasImplThread() &&
+        !layer_tree_host()->GetSettings().single_thread_proxy_scheduler;
+    auto sink = std::make_unique<viz::TestLayerTreeFrameSink>(
+        nullptr, nullptr, shared_bitmap_manager(), gpu_memory_buffer_manager(),
+        renderer_settings, ImplThreadTaskRunner(), synchronous_composite,
+        disable_display_vsync, refresh_rate);
+    frame_sink_ = sink.get();
+    num_frame_sinks_created_++;
+    return sink;
+  }
+
+  std::unique_ptr<viz::OutputSurface> CreateDisplayOutputSurfaceOnThread(
+      scoped_refptr<viz::ContextProvider> compositor_context_provider)
+      override {
+    return viz::FakeOutputSurface::CreateSoftware(
+        std::make_unique<viz::SoftwareOutputDevice>());
+  }
+
+  StubTextureLayerClient client_;
+  scoped_refptr<Layer> root_;
+  scoped_refptr<SolidColorLayer> solid_color_layer_;
+  scoped_refptr<TextureLayer> texture_layer_;
+  viz::TestLayerTreeFrameSink* frame_sink_ = nullptr;
+  int num_frame_sinks_created_ = 0;
+};
+
+class SoftwareTextureLayerSwitchTreesTest : public SoftwareTextureLayerTest {
+ protected:
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+
+    gfx::Size size(1, 1);
+    viz::ResourceFormat format = viz::RGBA_8888;
+
+    id_ = viz::SharedBitmap::GenerateId();
+    bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+  }
+
+  void DidCommitAndDrawFrame() override {
+    step_ = layer_tree_host()->SourceFrameNumber();
+    switch (step_) {
+      case 1:
+        // The test starts by inserting the TextureLayer to the tree.
+        root_->AddChild(texture_layer_);
+        // And registers a SharedBitmapId, which should be given to the
+        // LayerTreeFrameSink.
+        registration_ = texture_layer_->RegisterSharedBitmapId(id_, bitmap_);
+        // Give the TextureLayer a resource so it contributes to the frame. It
+        // doesn't need to register the SharedBitmapId otherwise.
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 2:
+        // When the layer is removed from the tree, the bitmap should be
+        // unregistered.
+        texture_layer_->RemoveFromParent();
+        break;
+      case 3:
+        // When the layer is added to a new tree, the SharedBitmapId is
+        // registered again.
+        root_->AddChild(texture_layer_);
+        break;
+      case 4:
+        // If the layer is removed and added back to the same tree in one
+        // commit, there should be no side effects, the bitmap stays
+        // registered.
+        texture_layer_->RemoveFromParent();
+        root_->AddChild(texture_layer_);
+        break;
+      case 5:
+        // Release the TransferableResource before shutdown.
+        texture_layer_->ClearClient();
+        break;
+      case 6:
+        EndTest();
+    }
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    switch (step_) {
+      case 0:
+        // Before commit 1, the |texture_layer_| has no SharedBitmapId yet.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 1:
+        // For commit 1, we added a SharedBitmapId to |texture_layer_|.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id_);
+        verified_frames_++;
+        break;
+      case 2:
+        // For commit 2, we removed |texture_layer_| from the tree.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 3:
+        // For commit 3, we added |texture_layer_| back to the tree.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id_);
+        verified_frames_++;
+        break;
+      case 4:
+        // For commit 3, we removed+added |texture_layer_| back to the tree.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id_);
+        verified_frames_++;
+        break;
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(5, verified_frames_); }
+
+  int step_ = 0;
+  int verified_frames_ = 0;
+  viz::SharedBitmapId id_;
+  SharedBitmapIdRegistration registration_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTextureLayerSwitchTreesTest);
+
+class SoftwareTextureLayerMultipleRegisterTest
+    : public SoftwareTextureLayerTest {
+ protected:
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+
+    gfx::Size size(1, 1);
+    viz::ResourceFormat format = viz::RGBA_8888;
+
+    id1_ = viz::SharedBitmap::GenerateId();
+    bitmap1_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id1_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+    id2_ = viz::SharedBitmap::GenerateId();
+    bitmap2_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id2_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+  }
+
+  void DidCommitAndDrawFrame() override {
+    step_ = layer_tree_host()->SourceFrameNumber();
+    switch (step_) {
+      case 1:
+        // The test starts by inserting the TextureLayer to the tree.
+        root_->AddChild(texture_layer_);
+        // And registers 2 SharedBitmapIds, which should be given to the
+        // LayerTreeFrameSink.
+        registration1_ = texture_layer_->RegisterSharedBitmapId(id1_, bitmap1_);
+        registration2_ = texture_layer_->RegisterSharedBitmapId(id2_, bitmap2_);
+        // Give the TextureLayer a resource so it contributes to the frame. It
+        // doesn't need to register the SharedBitmapId otherwise.
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id1_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 2:
+        // Drop one registration, and force a commit and SubmitCompositorFrame
+        // so that we can see it.
+        registration2_ = SharedBitmapIdRegistration();
+        texture_layer_->SetNeedsDisplay();
+        break;
+      case 3:
+        // Drop the other registration.
+        texture_layer_->ClearClient();
+        registration1_ = SharedBitmapIdRegistration();
+        break;
+      case 4:
+        EndTest();
+    }
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    switch (step_) {
+      case 0:
+        // Before commit 1, the |texture_layer_| has no SharedBitmapId yet.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 1:
+        // For commit 1, we added 2 SharedBitmapIds to |texture_layer_|.
+        EXPECT_EQ(2u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 2:
+        // For commit 2, we removed one SharedBitmapId.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id1_);
+        verified_frames_++;
+        break;
+      case 3:
+        // For commit 3, we removed the other SharedBitmapId.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(4, verified_frames_); }
+
+  int step_ = 0;
+  int verified_frames_ = 0;
+  viz::SharedBitmapId id1_;
+  viz::SharedBitmapId id2_;
+  SharedBitmapIdRegistration registration1_;
+  SharedBitmapIdRegistration registration2_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap1_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap2_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTextureLayerMultipleRegisterTest);
+
+class SoftwareTextureLayerRegisterUnregisterTest
+    : public SoftwareTextureLayerTest {
+ protected:
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+
+    gfx::Size size(1, 1);
+    viz::ResourceFormat format = viz::RGBA_8888;
+
+    id1_ = viz::SharedBitmap::GenerateId();
+    bitmap1_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id1_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+    id2_ = viz::SharedBitmap::GenerateId();
+    bitmap2_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id2_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+  }
+
+  void DidCommitAndDrawFrame() override {
+    step_ = layer_tree_host()->SourceFrameNumber();
+    switch (step_) {
+      case 1:
+        // The test starts by inserting the TextureLayer to the tree.
+        root_->AddChild(texture_layer_);
+        // And registers 2 SharedBitmapIds, which would be given to the
+        // LayerTreeFrameSink. But we unregister one.
+        {
+          registration1_ =
+              texture_layer_->RegisterSharedBitmapId(id1_, bitmap1_);
+          // We explicitly drop this registration by letting it go out of scope
+          // and being destroyed. Versus the registration1_ which we drop by
+          // assigning an empty registration to it. Both should do the same
+          // thing.
+          SharedBitmapIdRegistration temp_reg =
+              texture_layer_->RegisterSharedBitmapId(id2_, bitmap2_);
+        }
+        // Give the TextureLayer a resource so it contributes to the frame. It
+        // doesn't need to register the SharedBitmapId otherwise.
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id1_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 2:
+        // Drop the other registration.
+        texture_layer_->ClearClient();
+        registration1_ = SharedBitmapIdRegistration();
+        break;
+      case 3:
+        EndTest();
+    }
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    switch (step_) {
+      case 0:
+        // Before commit 1, the |texture_layer_| has no SharedBitmapId yet.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 1:
+        // For commit 1, we added 1 SharedBitmapId to |texture_layer_|.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id1_);
+        verified_frames_++;
+        break;
+      case 2:
+        // For commit 2, we removed the other SharedBitmapId.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(3, verified_frames_); }
+
+  int step_ = 0;
+  int verified_frames_ = 0;
+  viz::SharedBitmapId id1_;
+  viz::SharedBitmapId id2_;
+  SharedBitmapIdRegistration registration1_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap1_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap2_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTextureLayerRegisterUnregisterTest);
+
+class SoftwareTextureLayerLoseFrameSinkTest : public SoftwareTextureLayerTest {
+ protected:
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+
+    gfx::Size size(1, 1);
+    viz::ResourceFormat format = viz::RGBA_8888;
+
+    id_ = viz::SharedBitmap::GenerateId();
+    bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+  }
+
+  void DidCommitAndDrawFrame() override {
+    step_ = layer_tree_host()->SourceFrameNumber();
+    switch (step_) {
+      case 1:
+        // The test starts by inserting the TextureLayer to the tree.
+        root_->AddChild(texture_layer_);
+        // And registers a SharedBitmapId, which should be given to the
+        // LayerTreeFrameSink.
+        registration_ = texture_layer_->RegisterSharedBitmapId(id_, bitmap_);
+        // Give the TextureLayer a resource so it contributes to the frame. It
+        // doesn't need to register the SharedBitmapId otherwise.
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 2:
+        // The frame sink is lost. The host will make a new one and submit
+        // another frame, with the id being registered again.
+        layer_tree_host()->SetVisible(false);
+        first_frame_sink_ =
+            layer_tree_host()->ReleaseLayerTreeFrameSink().get();
+        layer_tree_host()->SetVisible(true);
+        texture_layer_->SetNeedsDisplay();
+
+        // TODO(crbug.com/826886): We shouldn't need SetTransferableResource(),
+        // but right now the TextureLayerImpl will drop the software resource
+        // when the frame sink is lost. It needs to be able to handle this for
+        // VizDisplayCompositor.
+        texture_layer_->ClearClient();
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 3:
+        // Release the TransferableResource before shutdown.
+        texture_layer_->ClearClient();
+        break;
+      case 4:
+        EndTest();
+    }
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    switch (step_) {
+      case 0:
+        // Before commit 1, the |texture_layer_| has no SharedBitmapId yet.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 1:
+        // For commit 1, we added a SharedBitmapId to |texture_layer_|.
+        EXPECT_EQ(1, num_frame_sinks_created_);
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id_);
+        verified_frames_++;
+        break;
+      case 2:
+        // For commit 2, we should still have the SharedBitmapId in the new
+        // frame sink.
+        EXPECT_EQ(2, num_frame_sinks_created_);
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(3, verified_frames_); }
+
+  int step_ = 0;
+  int verified_frames_ = 0;
+  viz::SharedBitmapId id_;
+  SharedBitmapIdRegistration registration_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap_;
+  // Keeps a pointer value of the first frame sink, which will be removed
+  // from the host and destroyed.
+  void* first_frame_sink_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTextureLayerLoseFrameSinkTest);
+
+class SoftwareTextureLayerUnregisterRegisterTest
+    : public SoftwareTextureLayerTest {
+ protected:
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+
+    gfx::Size size(1, 1);
+    viz::ResourceFormat format = viz::RGBA_8888;
+
+    id_ = viz::SharedBitmap::GenerateId();
+    bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
+        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        format);
+  }
+
+  void DidCommitAndDrawFrame() override {
+    step_ = layer_tree_host()->SourceFrameNumber();
+    switch (step_) {
+      case 1:
+        // The test starts by inserting the TextureLayer to the tree.
+        root_->AddChild(texture_layer_);
+
+        // We do a Register request, Unregister request, and then another
+        // Register request. The final register request should stick.
+        // And registers 2 SharedBitmapIds, which would be given to the
+        // LayerTreeFrameSink. But we unregister one.
+        {
+          // Register-Unregister-
+          SharedBitmapIdRegistration temp_reg =
+              texture_layer_->RegisterSharedBitmapId(id_, bitmap_);
+        }
+        // Register.
+        registration_ = texture_layer_->RegisterSharedBitmapId(id_, bitmap_);
+
+        // Give the TextureLayer a resource so it contributes to the frame. It
+        // doesn't need to register the SharedBitmapId otherwise.
+        texture_layer_->SetTransferableResource(
+            viz::TransferableResource::MakeSoftware(id_, 0, gfx::Size(1, 1),
+                                                    viz::RGBA_8888),
+            viz::SingleReleaseCallback::Create(
+                base::BindOnce([](const gpu::SyncToken&, bool) {})));
+        break;
+      case 2:
+        // Release the TransferableResource before shutdown.
+        texture_layer_->ClearClient();
+        break;
+      case 3:
+        EndTest();
+    }
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    switch (step_) {
+      case 0:
+        // Before commit 1, the |texture_layer_| has no SharedBitmapId yet.
+        EXPECT_EQ(0u, frame_sink_->owned_bitmaps().size());
+        verified_frames_++;
+        break;
+      case 1:
+        // For commit 1, we added 1 SharedBitmapId to |texture_layer_|.
+        EXPECT_EQ(1u, frame_sink_->owned_bitmaps().size());
+        EXPECT_EQ(*frame_sink_->owned_bitmaps().begin(), id_);
+        verified_frames_++;
+        break;
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(2, verified_frames_); }
+
+  int step_ = 0;
+  int verified_frames_ = 0;
+  viz::SharedBitmapId id_;
+  SharedBitmapIdRegistration registration_;
+  scoped_refptr<CrossThreadSharedBitmap> bitmap_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTextureLayerUnregisterRegisterTest);
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/resources/cross_thread_shared_bitmap.cc b/cc/resources/cross_thread_shared_bitmap.cc
new file mode 100644
index 0000000..642bd39
--- /dev/null
+++ b/cc/resources/cross_thread_shared_bitmap.cc
@@ -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.
+
+#include "cc/resources/cross_thread_shared_bitmap.h"
+
+namespace cc {
+
+CrossThreadSharedBitmap::CrossThreadSharedBitmap(
+    const viz::SharedBitmapId& id,
+    std::unique_ptr<base::SharedMemory> memory,
+    const gfx::Size& size,
+    viz::ResourceFormat format)
+    : id_(id), memory_(std::move(memory)), size_(size), format_(format) {}
+
+CrossThreadSharedBitmap::~CrossThreadSharedBitmap() = default;
+
+}  // namespace cc
diff --git a/cc/resources/cross_thread_shared_bitmap.h b/cc/resources/cross_thread_shared_bitmap.h
new file mode 100644
index 0000000..1a436e9
--- /dev/null
+++ b/cc/resources/cross_thread_shared_bitmap.h
@@ -0,0 +1,51 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_RESOURCES_CROSS_THREAD_SHARED_BITMAP_H_
+#define CC_RESOURCES_CROSS_THREAD_SHARED_BITMAP_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "cc/cc_export.h"
+#include "components/viz/common/quads/shared_bitmap.h"
+#include "components/viz/common/resources/resource_format.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace cc {
+
+// This class holds ownership of a base::SharedMemory segment for use as a
+// composited resource, and is refcounted in order to share ownership with the
+// LayerTreeHost, via TextureLayer, which needs access to the base::SharedMemory
+// from the compositor thread.
+// Because all the fields exposed are const, they can be used from any thread
+// without conflict, as they only read existing states.
+class CC_EXPORT CrossThreadSharedBitmap
+    : public base::RefCountedThreadSafe<CrossThreadSharedBitmap> {
+ public:
+  CrossThreadSharedBitmap(const viz::SharedBitmapId& id,
+                          std::unique_ptr<base::SharedMemory> memory,
+                          const gfx::Size& size,
+                          viz::ResourceFormat format);
+
+  const viz::SharedBitmapId& id() const { return id_; }
+  const base::SharedMemory* shared_memory() const { return memory_.get(); }
+  const gfx::Size& size() const { return size_; }
+  viz::ResourceFormat format() const { return format_; }
+
+ private:
+  friend base::RefCountedThreadSafe<CrossThreadSharedBitmap>;
+
+  ~CrossThreadSharedBitmap();
+
+  const viz::SharedBitmapId id_;
+  const std::unique_ptr<const base::SharedMemory> memory_;
+  const gfx::Size size_;
+  const viz::ResourceFormat format_;
+};
+
+}  // namespace cc
+
+#endif  // CC_RESOURCES_CROSS_THREAD_SHARED_BITMAP_H_
diff --git a/cc/resources/shared_bitmap_id_registrar.cc b/cc/resources/shared_bitmap_id_registrar.cc
new file mode 100644
index 0000000..8ab3886
--- /dev/null
+++ b/cc/resources/shared_bitmap_id_registrar.cc
@@ -0,0 +1,35 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/resources/shared_bitmap_id_registrar.h"
+
+#include "cc/layers/texture_layer.h"
+
+namespace cc {
+
+SharedBitmapIdRegistration::SharedBitmapIdRegistration() = default;
+
+SharedBitmapIdRegistration::SharedBitmapIdRegistration(
+    base::WeakPtr<TextureLayer> layer_ptr,
+    const viz::SharedBitmapId& id)
+    : layer_ptr_(std::move(layer_ptr)), id_(id) {}
+
+SharedBitmapIdRegistration::~SharedBitmapIdRegistration() {
+  if (layer_ptr_)
+    layer_ptr_->UnregisterSharedBitmapId(id_);
+}
+
+SharedBitmapIdRegistration::SharedBitmapIdRegistration(
+    SharedBitmapIdRegistration&&) = default;
+
+SharedBitmapIdRegistration& SharedBitmapIdRegistration::operator=(
+    SharedBitmapIdRegistration&& other) {
+  if (layer_ptr_)
+    layer_ptr_->UnregisterSharedBitmapId(id_);
+  layer_ptr_ = std::move(other.layer_ptr_);
+  id_ = std::move(other.id_);
+  return *this;
+}
+
+}  // namespace cc
diff --git a/cc/resources/shared_bitmap_id_registrar.h b/cc/resources/shared_bitmap_id_registrar.h
new file mode 100644
index 0000000..8586a240
--- /dev/null
+++ b/cc/resources/shared_bitmap_id_registrar.h
@@ -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.
+
+#ifndef CC_RESOURCES_SHARED_BITMAP_ID_REGISTRAR_H_
+#define CC_RESOURCES_SHARED_BITMAP_ID_REGISTRAR_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/cc_export.h"
+#include "components/viz/common/quads/shared_bitmap.h"
+
+namespace cc {
+class CrossThreadSharedBitmap;
+class SharedBitmapIdRegistration;
+class TextureLayer;
+
+// An interface exposed to clients of TextureLayer for registering
+// SharedBitmapIds that they will be using in viz::TransferableResources given
+// to the TextureLayer. SharedBitmapId-SharedMemory pairs registered as such are
+// then given to the display compositor, and the mapping between the pair is
+// kept valid while the returned SharedBitmapIdRegistration is kept alive.
+//
+// These mappings are per-layer-tree. So if a client has multiple TextureLayers
+// in the same tree, and wants to use the SharedBitmapId in more than one of
+// them over time, it still only should register with a single TextureLayer. But
+// if the TextureLayer is removed from the tree, they would need to be
+// registered with another TextureLayer that is in each tree where they are
+// being used.
+class CC_EXPORT SharedBitmapIdRegistrar {
+ public:
+  virtual ~SharedBitmapIdRegistrar() = default;
+  virtual SharedBitmapIdRegistration RegisterSharedBitmapId(
+      const viz::SharedBitmapId& id,
+      scoped_refptr<CrossThreadSharedBitmap> bitmap) = 0;
+};
+
+// A scoped object that maintains a mapping of SharedBitmapId to SharedMemory
+// that was registered through SharedBitmapIdRegistrar for the display
+// compositor. Keep this object alive while the SharedBitmapId may be used
+// in viz::TransferableResources given to the TextureLayer. Typically that means
+// as long as the client keeps the SharedMemory alive with a reference to the
+// CrossThreadSharedBitmap, which it should keep alive at least until the
+// TextureLayer calls back to the ReleaseCallback indicating the display
+// compositor is no longer using the resource. When this object is destroyed, or
+// assigned to, then the mapping registration will be dropped from the display
+// compositor, and the SharedBitmapId will no longer be able to be used in the
+// TextureLayer.
+class CC_EXPORT SharedBitmapIdRegistration {
+ public:
+  SharedBitmapIdRegistration();
+  ~SharedBitmapIdRegistration();
+
+  SharedBitmapIdRegistration(SharedBitmapIdRegistration&&);
+  SharedBitmapIdRegistration& operator=(SharedBitmapIdRegistration&&);
+
+ private:
+  // Constructed by TextureLayer only, then held by the client as long
+  // as they wish to
+  friend TextureLayer;
+  SharedBitmapIdRegistration(base::WeakPtr<TextureLayer> layer_ptr,
+                             const viz::SharedBitmapId& id);
+
+  base::WeakPtr<TextureLayer> layer_ptr_;
+  viz::SharedBitmapId id_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedBitmapIdRegistration);
+};
+
+}  // namespace cc
+
+#endif  // CC_RESOURCES_SHARED_BITMAP_ID_REGISTRAR_H_
diff --git a/cc/test/fake_tile_manager.cc b/cc/test/fake_tile_manager.cc
index 716c8ec9..cb172443 100644
--- a/cc/test/fake_tile_manager.cc
+++ b/cc/test/fake_tile_manager.cc
@@ -46,7 +46,6 @@
   SetDecodedImageTracker(&decoded_image_tracker_);
   SetResources(resource_pool, &image_decode_cache_, GetGlobalTaskGraphRunner(),
                GetGlobalRasterBufferProvider(),
-               std::numeric_limits<size_t>::max(),
                false /* use_gpu_rasterization */);
   SetTileTaskManagerForTesting(std::make_unique<FakeTileTaskManagerImpl>());
 }
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index c2e98e8..217a6ff5 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -415,13 +415,11 @@
                                ImageDecodeCache* image_decode_cache,
                                TaskGraphRunner* task_graph_runner,
                                RasterBufferProvider* raster_buffer_provider,
-                               size_t scheduled_raster_task_limit,
                                bool use_gpu_rasterization) {
   DCHECK(!tile_task_manager_);
   DCHECK(task_graph_runner);
 
   use_gpu_rasterization_ = use_gpu_rasterization;
-  scheduled_raster_task_limit_ = scheduled_raster_task_limit;
   resource_pool_ = resource_pool;
   image_controller_.SetImageDecodeCache(image_decode_cache);
   tile_task_manager_ = TileTaskManagerImpl::Create(task_graph_runner);
diff --git a/cc/tiles/tile_manager.h b/cc/tiles/tile_manager.h
index 4170c15..38bcea6 100644
--- a/cc/tiles/tile_manager.h
+++ b/cc/tiles/tile_manager.h
@@ -156,7 +156,6 @@
                     ImageDecodeCache* image_decode_cache,
                     TaskGraphRunner* task_graph_runner,
                     RasterBufferProvider* raster_buffer_provider,
-                    size_t scheduled_raster_task_limit,
                     bool use_gpu_rasterization);
 
   // This causes any completed raster work to finalize, so that tiles get up to
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index b2c90a8..8b9fb90 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2611,12 +2611,8 @@
     task_graph_runner = single_thread_synchronous_task_graph_runner_.get();
   }
 
-  // TODO(vmpstr): Initialize tile task limit at ctor time.
   tile_manager_.SetResources(resource_pool_.get(), image_decode_cache_.get(),
                              task_graph_runner, raster_buffer_provider_.get(),
-                             is_synchronous_single_threaded_
-                                 ? std::numeric_limits<size_t>::max()
-                                 : settings_.scheduled_raster_task_limit,
                              use_gpu_rasterization_);
   tile_manager_.SetCheckerImagingForceDisabled(
       settings_.only_checker_images_with_gpu_raster && !use_gpu_rasterization_);
diff --git a/chrome/VERSION b/chrome/VERSION
index 20cd49367..35ec0b1 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=67
 MINOR=0
-BUILD=3384
+BUILD=3385
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 5b9b71d..13d986f8 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -613,6 +613,7 @@
     "//chrome/test/data/translate/",
     "//chrome/test/media_router/resources/",
     "//components/test/data/payments/",
+    "//content/test/data/android/authenticator.html",
     "//content/test/data/android/geolocation.html",
     "//content/test/data/android/installedapp.html",
     "//content/test/data/android/media_permissions.html",
diff --git a/chrome/android/java/res/layout/data_reduction_stats_layout.xml b/chrome/android/java/res/layout/data_reduction_stats_layout.xml
index caa363e..e6a8368 100644
--- a/chrome/android/java/res/layout/data_reduction_stats_layout.xml
+++ b/chrome/android/java/res/layout/data_reduction_stats_layout.xml
@@ -16,14 +16,9 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
+        android:gravity="center_horizontal"
         android:baselineAligned="false"
-        android:orientation="horizontal" >
-
-        <LinearLayout
-            android:layout_height="wrap_content"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:orientation="vertical" >
+        android:orientation="vertical" >
 
             <LinearLayout
                 android:layout_width="wrap_content"
@@ -31,17 +26,20 @@
                 android:orientation="horizontal" >
 
                 <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:singleLine="true"
-                    android:text="@string/data_reduction_savings_label"
-                    android:textAppearance="@style/PreferenceCategoryTextStyle" />
-
-                <TextView
                     android:id="@+id/data_reduction_savings"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
+                    android:layout_marginTop="3dp"
+                    android:singleLine="true"
+                    android:textColor="@color/light_active_color"
+                    android:textSize="23sp" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="3dp"
                     android:paddingStart="3dp"
+                    android:text="@string/data_reduction_savings_label"
                     android:textAppearance="@style/PreferenceCategoryTextStyle" />
 
             </LinearLayout>
@@ -53,31 +51,20 @@
                 android:orientation="horizontal" >
 
                 <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:singleLine="true"
-                    android:text="@string/data_reduction_usage_label"
-                    android:textAppearance="@style/DataReductionPrefSecondaryStyle" />
-
-                <TextView
                     android:id="@+id/data_reduction_usage"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:textAppearance="@style/DataReductionPrefSecondaryStyle" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
                     android:paddingStart="3dp"
+                    android:text="@string/data_reduction_usage_label"
                     android:textAppearance="@style/DataReductionPrefSecondaryStyle" />
             </LinearLayout>
 
-        </LinearLayout>
-
-        <TextView
-            android:id="@+id/data_reduction_percent"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:layout_marginTop="0dp"
-            android:includeFontPadding="false"
-            android:textColor="@color/light_active_color"
-            android:textSize="34sp" />
-
     </LinearLayout>
 
     <include layout="@layout/data_usage_chart" />
diff --git a/chrome/android/java/res/layout/data_usage_breakdown.xml b/chrome/android/java/res/layout/data_usage_breakdown.xml
index 64032ffd..39ccaf8 100644
--- a/chrome/android/java/res/layout/data_usage_breakdown.xml
+++ b/chrome/android/java/res/layout/data_usage_breakdown.xml
@@ -20,7 +20,7 @@
         android:paddingBottom="12dp"
         android:paddingTop="26dp"
         android:singleLine="true"
-        android:text="@string/data_reduction_data_usage_breakdown_title"
+        android:text="@string/details_link"
         android:textAppearance="@style/PreferenceCategoryTextStyle" />
 
     <TableLayout
@@ -41,7 +41,6 @@
                 android:layout_width="0dp"
                 android:layout_weight="0"
                 android:layout_gravity="start"
-                android:textColor="@color/data_reduction_breakdown_light_text_color"
                 style="@style/DataUsageBreakdownColumnLabel" />
 
             <TextView
@@ -62,7 +61,7 @@
                 android:drawablePadding="3dp"
                 android:drawableStart="@drawable/data_reduction_breakdown_sort_arrow"
                 android:gravity="end"
-                style="@style/DataUsageBreakdownColumnLabel" />
+                style="@style/DataUsageBreakdownSavedColumnLabel" />
 
         </TableRow>
 
diff --git a/chrome/android/java/res/layout/data_usage_breakdown_row.xml b/chrome/android/java/res/layout/data_usage_breakdown_row.xml
index 6d5af58..df7f6b29 100644
--- a/chrome/android/java/res/layout/data_usage_breakdown_row.xml
+++ b/chrome/android/java/res/layout/data_usage_breakdown_row.xml
@@ -28,5 +28,5 @@
         android:layout_width="wrap_content"
         android:layout_marginStart="24dp"
         android:gravity="end"
-        style="@style/DataUsageBreakdownColumnItem" />
+        style="@style/DataUsageBreakdownSavedColumnItem" />
 </TableRow>
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index 06f4d2b..4faa3d4 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -703,16 +703,20 @@
         <item name="android:paddingBottom">10dp</item>
         <item name="android:paddingTop">10dp</item>
         <item name="android:singleLine">true</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:textAppearance">@style/RobotoMediumStyle</item>
+        <item name="android:textAppearance">@style/BlackButtonText</item>
+    </style>
+    <style name="DataUsageBreakdownSavedColumnLabel" parent="DataUsageBreakdownColumnLabel">
+        <item name="android:textAppearance">@style/BlueButtonText2</item>
     </style>
     <style name="DataUsageBreakdownColumnItem">
         <item name="android:layout_height">wrap_content</item>
         <item name="android:paddingBottom">10dp</item>
         <item name="android:paddingTop">10dp</item>
         <item name="android:singleLine">true</item>
-        <item name="android:textColor">@color/data_reduction_breakdown_text_color</item>
-        <item name="android:textSize">12sp</item>
+        <item name="android:textAppearance">@style/BlackCaption</item>
+    </style>
+    <style name="DataUsageBreakdownSavedColumnItem" parent="DataUsageBreakdownColumnItem">
+        <item name="android:textAppearance">@style/BlueLink3</item>
     </style>
 
     <!-- Incognito New Tab Page -->
diff --git a/chrome/android/java/res/values/colors.xml b/chrome/android/java/res/values/colors.xml
index 9ab9319b..40af7798 100644
--- a/chrome/android/java/res/values/colors.xml
+++ b/chrome/android/java/res/values/colors.xml
@@ -91,11 +91,9 @@
     <color name="pref_dragged_row_background">@color/white_alpha_90</color>
 
     <!-- Data Saver Colors -->
-    <color name="data_reduction_compressed_color">@color/pref_accent_color</color>
-    <color name="data_reduction_original_color">#E1E1E1</color>
+    <color name="data_reduction_compressed_color">#E1E1E1</color>
+    <color name="data_reduction_original_color">@color/pref_accent_color</color>
     <color name="data_reduction_chart_background_color">@color/google_grey_50</color>
-    <color name="data_reduction_breakdown_text_color">#212121</color>
-    <color name="data_reduction_breakdown_light_text_color">#737373</color>
 
     <!-- Compositor Tab Title Colors -->
     <color name="compositor_tab_title_bar_text">@color/black_alpha_87</color>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 598026f..b20d4886 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -259,6 +259,7 @@
     public static final String VR_BROWSING_IN_CUSTOM_TAB = "VrBrowsingInCustomTab";
     public static final String VR_BROWSING_NATIVE_ANDROID_UI = "VrBrowsingNativeAndroidUi";
     public static final String VR_ICON_IN_DAYDREAM_HOME = "VrIconInDaydreamHome";
+    public static final String WEB_AUTH = "WebAuthentication";
     public static final String WEB_PAYMENTS = "WebPayments";
     public static final String WEB_PAYMENTS_METHOD_SECTION_ORDER_V2 =
             "WebPaymentsMethodSectionOrderV2";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
index 6f78e7ef..7bc014b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
@@ -49,8 +49,6 @@
     public static final String EXCEPTION_INFO = "exception_info";
     public static final String PROCESS_TYPE = "ptype";
     public static final String EARLY_JAVA_EXCEPTION = "early_java_exception";
-    public static final String CUSTOM_THEMES = "custom_themes";
-    public static final String RESOURCES_VERSION = "resources_version";
 
     private static final String CRASH_DUMP_DIR = "Crash Reports";
     private static final String FILE_PREFIX = "chromium-browser-minidump-";
@@ -139,8 +137,6 @@
         addPairedString(PACKAGE,
                 String.format("%s v%s (%s)", BuildConfig.FIREBASE_APP_ID, buildInfo.versionCode,
                         buildInfo.versionName));
-        addPairedString(CUSTOM_THEMES, buildInfo.customThemes);
-        addPairedString(RESOURCES_VERSION, buildInfo.resourcesVersion);
 
         addString(mBoundary);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
index ff9392d..49d5c9ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -544,6 +544,9 @@
 
     private boolean shouldShowAndroidControls() {
         if (mBrowserControlsAndroidViewHidden) return false;
+        if (getTab() != null && getTab().getControlsOffsetHelper().isControlsOffsetOverridden()) {
+            return true;
+        }
 
         boolean showControls = !drawControlsAsTexture();
         ContentViewCore contentViewCore = getActiveContentViewCore();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
index 81a0f1d..3d1f511 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
@@ -206,8 +206,8 @@
      * @param dialog The dialog to be cancelled.
      */
     public void cancelDialog(ModalDialogView dialog) {
-        dismissDialog(dialog);
         dialog.getController().onCancel();
+        dismissDialog(dialog);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
index 6c68d7d..72fd1eed 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.modaldialog;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.res.Resources;
 import android.view.Gravity;
 import android.view.View;
@@ -18,6 +20,7 @@
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBrowserControlsOffsetHelper;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
 import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
@@ -25,11 +28,16 @@
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.UiUtils;
+import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
 /**
  * The presenter that displays a single tab modal dialog.
  */
-public class TabModalPresenter extends ModalDialogManager.Presenter {
+public class TabModalPresenter
+        extends ModalDialogManager.Presenter implements TabBrowserControlsOffsetHelper.Observer {
+    // TODO(huayinz): Confirm duration with UX.
+    private static final int ENTER_EXIT_ANIMATION_DURATION_MS = 200;
+
     /** The activity displaying the dialogs. */
     private final ChromeActivity mChromeActivity;
 
@@ -54,6 +62,12 @@
     /** Whether the dialog container is brought to the front in its parent. */
     private boolean mContainerIsAtFront;
 
+    /**
+     * Whether an enter animation on the dialog container should run when
+     * {@link #onBrowserControlsFullyVisible} is called.
+     */
+    private boolean mRunEnterAnimationOnCallback;
+
     /** Whether the action bar on selected text is temporarily cleared for showing dialogs. */
     private boolean mDidClearTextControls;
 
@@ -64,6 +78,9 @@
      */
     private View mDefaultNextSiblingView;
 
+    /** Enter and exit animation duration that can be overwritten in tests. */
+    private int mEnterExitAnimationDurationMs;
+
     /**
      * Constructor for initializing dialog container.
      * @param chromeActivity The activity displaying the dialogs.
@@ -71,6 +88,7 @@
     public TabModalPresenter(ChromeActivity chromeActivity) {
         mChromeActivity = chromeActivity;
         mHasBottomControls = mChromeActivity.getBottomSheet() != null;
+        mEnterExitAnimationDurationMs = ENTER_EXIT_ANIMATION_DURATION_MS;
 
         if (mHasBottomControls) {
             mBottomSheetObserver = new EmptyBottomSheetObserver() {
@@ -91,11 +109,12 @@
     protected void addDialogView(View dialogView) {
         if (mDialogContainer == null) initDialogContainer();
         setBrowserControlsAccess(true);
-        mDialogContainer.setVisibility(View.VISIBLE);
-
-        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
-                MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
-        mDialogContainer.addView(dialogView, params);
+        // Don't show the dialog container before browser controls are guaranteed fully visible.
+        if (mActiveTab.getControlsOffsetHelper().areBrowserControlsFullyVisible()) {
+            runEnterAnimation(dialogView);
+        } else {
+            mRunEnterAnimationOnCallback = true;
+        }
         mChromeActivity.addViewObscuringAllTabs(mDialogContainer);
         updateContainerHierarchy(true);
     }
@@ -103,13 +122,27 @@
     @Override
     protected void removeDialogView(View dialogView) {
         setBrowserControlsAccess(false);
-        // Clear focus so that keyboard can hide accordingly while entering tab switcher.
-        dialogView.clearFocus();
-        mDialogContainer.removeView(dialogView);
-        mDialogContainer.setVisibility(View.GONE);
+        // Don't run exit animation if enter animation has not yet started.
+        if (mRunEnterAnimationOnCallback) {
+            mRunEnterAnimationOnCallback = false;
+        } else {
+            // Clear focus so that keyboard can hide accordingly while entering tab switcher.
+            dialogView.clearFocus();
+            runExitAnimation(dialogView);
+        }
         mChromeActivity.removeViewObscuringAllTabs(mDialogContainer);
     }
 
+    @Override
+    public void onBrowserControlsFullyVisible(Tab tab) {
+        if (getModalDialog() == null) return;
+        assert mActiveTab == tab;
+        if (mRunEnterAnimationOnCallback) {
+            mRunEnterAnimationOnCallback = false;
+            runEnterAnimation(getModalDialog().getView());
+        }
+    }
+
     /**
      * Change view hierarchy for the dialog container to be either the front most or beneath the
      * toolbar.
@@ -146,6 +179,7 @@
         dialogContainerStub.setLayoutResource(R.layout.modal_dialog_container);
 
         mDialogContainer = (ViewGroup) dialogContainerStub.inflate();
+        mDialogContainer.setVisibility(View.GONE);
         mContainerParent = (ViewGroup) mDialogContainer.getParent();
         // The default sibling view is the next view of the dialog container stub in main.xml and
         // should not be removed from its parent.
@@ -192,6 +226,7 @@
             assert mActiveTab
                     != null : "Tab modal dialogs should be shown on top of an active tab.";
 
+            mActiveTab.getControlsOffsetHelper().addObserver(this);
             // Hide contextual search panel so that bottom toolbar will not be
             // obscured and back press is not overridden.
             ContextualSearchManager contextualSearchManager =
@@ -213,8 +248,6 @@
             }
 
             // Force toolbar to show and disable overflow menu.
-            // TODO(huayinz): figure out a way to avoid |UpdateBrowserControlsState| being blocked
-            // by render process stalled due to javascript dialog.
             mActiveTab.onTabModalDialogStateChanged(true);
 
             if (mHasBottomControls) {
@@ -225,6 +258,7 @@
             }
             menuButton.setEnabled(false);
         } else {
+            mActiveTab.getControlsOffsetHelper().removeObserver(this);
             // Show the action bar back if it was dismissed when the dialogs were showing.
             if (mDidClearTextControls) {
                 mDidClearTextControls = false;
@@ -242,6 +276,45 @@
         }
     }
 
+    /**
+     * Helper method to run fade-in animation when the specified dialog view is shown.
+     * @param dialogView The dialog view to be shown.
+     */
+    private void runEnterAnimation(View dialogView) {
+        mDialogContainer.animate().cancel();
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
+        mDialogContainer.addView(dialogView, params);
+        mDialogContainer.setAlpha(0f);
+        mDialogContainer.setVisibility(View.VISIBLE);
+        mDialogContainer.animate()
+                .setDuration(mEnterExitAnimationDurationMs)
+                .alpha(1f)
+                .setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE)
+                .setListener(null)
+                .start();
+    }
+
+    /**
+     * Helper method to run fade-out animation when the specified dialog view is dismissed.
+     * @param dialogView The dismissed dialog view.
+     */
+    private void runExitAnimation(View dialogView) {
+        mDialogContainer.animate().cancel();
+        mDialogContainer.animate()
+                .setDuration(mEnterExitAnimationDurationMs)
+                .alpha(0f)
+                .setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mDialogContainer.setVisibility(View.GONE);
+                        mDialogContainer.removeView(dialogView);
+                    }
+                })
+                .start();
+    }
+
     @VisibleForTesting
     View getDialogContainerForTest() {
         return mDialogContainer;
@@ -251,4 +324,9 @@
     ViewGroup getContainerParentForTest() {
         return mContainerParent;
     }
+
+    @VisibleForTesting
+    void disableAnimationForTest() {
+        mEnterExitAnimationDurationMs = 0;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java b/chrome/android/java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java
index 52f0be1..dbec85eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java
@@ -7,6 +7,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.installedapp.InstalledAppProviderFactory;
 import org.chromium.chrome.browser.payments.PaymentRequestFactory;
+import org.chromium.chrome.browser.webauth.AuthenticatorFactory;
 import org.chromium.chrome.browser.webshare.ShareServiceImplementationFactory;
 import org.chromium.content_public.browser.InterfaceRegistrar;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -14,6 +15,7 @@
 import org.chromium.installedapp.mojom.InstalledAppProvider;
 import org.chromium.payments.mojom.PaymentRequest;
 import org.chromium.services.service_manager.InterfaceRegistry;
+import org.chromium.webauth.mojom.Authenticator;
 import org.chromium.webshare.mojom.ShareService;
 
 @SuppressWarnings("MultipleTopLevelClassesInFile")
@@ -46,6 +48,7 @@
                     PaymentRequest.MANAGER, new PaymentRequestFactory(renderFrameHost));
             registry.addInterface(
                     InstalledAppProvider.MANAGER, new InstalledAppProviderFactory(renderFrameHost));
+            registry.addInterface(Authenticator.MANAGER, new AuthenticatorFactory(renderFrameHost));
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
index f9bbc4e..bd5a559 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java
@@ -561,6 +561,22 @@
     }
 
     /**
+     * Ask the native code to publish the internal page asychronously.
+     * @param profile Profile for current user.
+     * @param offlineId ID of the offline page to publish.
+     * @param title Title of the offline page.
+     * @param url Url of the offline page.
+     * @param filePath Path to the file for the offline page.
+     * @param size Length of the offline page file.
+     * @param publishedCallback Function to call when publishing is done.
+     */
+    public void publishInternalPage(Profile profile, long offlineId, String title, String url,
+            String filePath, long size, Callback<OfflinePageItem> publishedCallback) {
+        nativePublishInternalPage(mNativeOfflinePageBridge, profile, offlineId, title, url,
+                filePath, size, publishedCallback);
+    }
+
+    /**
      * Whether or not the underlying offline page model is loaded.
      */
     public boolean isOfflinePageModelLoaded() {
@@ -839,6 +855,10 @@
     @VisibleForTesting
     native void nativeDeletePagesByOfflineId(
             long nativeOfflinePageBridge, long[] offlineIds, Callback<Integer> callback);
+    @VisibleForTesting
+    private native void nativePublishInternalPage(long nativeOfflinePageBridge, Profile profile,
+            long offlineId, String title, String url, String filePath, long size,
+            Callback<OfflinePageItem> publishedCallback);
 
     private native void nativeSelectPageForOnlineUrl(
             long nativeOfflinePageBridge, String onlineUrl, int tabId,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index 595d3b8..ef043fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -354,7 +354,8 @@
      * @param currentTab The current tab from which the page is being shared.
      * @param shareCallback The callback to be used to send the ShareParams. This will only be
      *                      called if this function call returns true.
-     * @return true if the sharing of the page is possible and the callback will be invoked.
+     * @return true if the sharing of the page is possible.  The callback will be invoked if
+     *                      publishing the page succeeds.
      */
     public static boolean maybeShareOfflinePage(
             final Activity activity, Tab tab, final Callback<ShareParams> shareCallback) {
@@ -382,30 +383,16 @@
         offlinePageBridge.acquireFileAccessPermission(webContents, (granted) -> {
             if (!granted) return;
 
-            final String tabTitle = tab.getTitle();
+            // If the page is not in a public location, we must publish it before sharing it.
+            if (offlinePageBridge.isInPrivateDirectory(offlinePath)) {
+                publishThenShareInternalPage(
+                        activity, tab.getProfile(), offlinePageBridge, offlinePage, shareCallback);
+                return;
+            }
+
+            final String pageTitle = tab.getTitle();
             final File offlinePageFile = new File(offlinePath);
-            AsyncTask<Void, Void, Uri> task = new AsyncTask<Void, Void, Uri>() {
-                @Override
-                protected Uri doInBackground(Void... v) {
-                    // If we have a content or file URI, we will not have a filename, just return
-                    // the URI.
-                    if (offlinePath.isEmpty()) {
-                        Uri uri = Uri.parse(pageUrl);
-                        assert(isSchemeContentOrFile(uri));
-                        return uri;
-                    }
-                    return ChromeFileProvider.generateUri(activity, offlinePageFile);
-                }
-                @Override
-                protected void onPostExecute(Uri uri) {
-                    ShareParams shareParams = new ShareParams.Builder(activity, tabTitle, pageUrl)
-                                                      .setShareDirectly(false)
-                                                      .setOfflineUri(uri)
-                                                      .build();
-                    shareCallback.onResult(shareParams);
-                }
-            };
-            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            sharePage(activity, pageUrl, pageTitle, offlinePath, offlinePageFile, shareCallback);
         });
 
         return true;
@@ -413,8 +400,9 @@
 
     /**
      * Check to see if the offline page is sharable.
+     * @param offlinePageBridge Bridge to native code for offline pages use.
      * @param offlinePage Page to check for sharability.
-     * @param pageUri Uri of the page to check
+     * @param pageUri Uri of the page to check.
      * @return true if this page can be shared.
      */
     public static boolean isOfflinePageShareable(
@@ -457,6 +445,75 @@
     }
 
     /**
+     * For internal pages, we must publish them, then share them.
+     * @param offlinePageBridge Bridge to native code for offline pages use.
+     * @param offlinePage Page to publish and share.
+     * @param shareCallback The callback to be used to send the ShareParams.
+     */
+    public static void publishThenShareInternalPage(final Activity activity, Profile profile,
+            OfflinePageBridge offlinePageBridge, OfflinePageItem offlinePage,
+            final Callback<ShareParams> shareCallback) {
+        Callback<OfflinePageItem> publishPageCallback =
+                new PublishPageCallback(activity, shareCallback);
+        offlinePageBridge.publishInternalPage(profile, offlinePage.getOfflineId(),
+                offlinePage.getTitle(), offlinePage.getUrl(), offlinePage.getFilePath(),
+                offlinePage.getFileSize(), publishPageCallback);
+    }
+
+    /**
+     * Callback from the native code to publish a page.  This will take the page, now in
+     * a public directory, and share it.
+     */
+    public static void sharePublishedPage(OfflinePageItem page, final Activity activity,
+            final Callback<ShareParams> shareCallback) {
+        if (page == null) {
+            // Set the source component name to null to indicate failure, causing ShareHelper.share
+            // to return.
+            ShareParams shareParams =
+                    new ShareParams.Builder(activity, page.getTitle(), page.getUrl())
+                            .setShareDirectly(false)
+                            .setOfflineUri(Uri.parse(page.getUrl()))
+                            .setSourcePackageName(null)
+                            .build();
+            shareCallback.onResult(shareParams);
+            return;
+        }
+        final String pageUrl = page.getUrl();
+        final String pageTitle = page.getTitle();
+        final File offlinePageFile = new File(page.getFilePath());
+        sharePage(activity, pageUrl, pageTitle, page.getFilePath(), offlinePageFile, shareCallback);
+    }
+
+    /**
+     * Share the page.
+     */
+    public static void sharePage(Activity activity, String pageUrl, String pageTitle,
+            String offlinePath, File offlinePageFile, final Callback<ShareParams> shareCallback) {
+        AsyncTask<Void, Void, Uri> task = new AsyncTask<Void, Void, Uri>() {
+            @Override
+            protected Uri doInBackground(Void... v) {
+                // If we have a content or file URI, we will not have a filename, just return the
+                // URI.
+                if (offlinePath.isEmpty()) {
+                    Uri uri = Uri.parse(pageUrl);
+                    assert(isSchemeContentOrFile(uri));
+                    return uri;
+                }
+                return ChromeFileProvider.generateUri(activity, offlinePageFile);
+            }
+            @Override
+            protected void onPostExecute(Uri uri) {
+                ShareParams shareParams = new ShareParams.Builder(activity, pageTitle, pageUrl)
+                                                  .setShareDirectly(false)
+                                                  .setOfflineUri(uri)
+                                                  .build();
+                shareCallback.onResult(shareParams);
+            }
+        };
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
+    /**
      * Retrieves the extra request header to reload the offline page.
      * @param tab The current tab.
      * @return The extra request header string.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/PublishPageCallback.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/PublishPageCallback.java
new file mode 100644
index 0000000..8e5f0e9
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/PublishPageCallback.java
@@ -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.
+
+package org.chromium.chrome.browser.offlinepages;
+
+import android.app.Activity;
+
+import org.chromium.base.Callback;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.share.ShareParams;
+
+/**
+ * This callback will save the state we need when the JNI call is done, and start the next stage of
+ * processing for sharing.
+ */
+public class PublishPageCallback implements Callback<OfflinePageItem> {
+    private Callback<ShareParams> mShareCallback;
+    private Activity mActivity;
+    private static final String TAG = "PublishPageCallback";
+
+    /** Create a callback for use when page publishing is completed. */
+    public PublishPageCallback(Activity activity, Callback<ShareParams> shareCallback) {
+        mActivity = activity;
+        mShareCallback = shareCallback;
+    }
+
+    @Override
+    @CalledByNative
+    /** Report results of publishing. */
+    public void onResult(OfflinePageItem page) {
+        OfflinePageUtils.sharePublishedPage(page, mActivity, mShareCallback);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionProxyUma.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionProxyUma.java
index 0e9b64c..ad211e6d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionProxyUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionProxyUma.java
@@ -20,8 +20,6 @@
             "DataReductionProxy.UserViewedOriginalSize";
     public static final String USER_VIEWED_SAVINGS_SIZE_HISTOGRAM_NAME =
             "DataReductionProxy.UserViewedSavingsSize";
-    public static final String USER_VIEWED_SAVINGS_PERCENT_HISTOGRAM_NAME =
-            "DataReductionProxy.UserViewedSavingsPercent";
 
     // Represent the possible user actions in the various data reduction promos and settings menu.
     // This must remain in sync with DataReductionProxy.UIAction in
@@ -88,19 +86,15 @@
      * savings in the UI.
      * @param compressedTotalBytes The total data used as shown to the user.
      * @param originalTotalBytes Original total size as shown to the user.
-     * @param percentage Percentage savings as shown to the user.
      */
     public static void dataReductionProxyUserViewedSavings(
-            long compressedTotalBytes, long originalTotalBytes, double percentage) {
+            long compressedTotalBytes, long originalTotalBytes) {
         // The byte counts are stored in KB. The largest histogram bucket is set to ~1 TB.
         RecordHistogram.recordCustomCountHistogram(USER_VIEWED_ORIGINAL_SIZE_HISTOGRAM_NAME,
                 (int) (originalTotalBytes / 1024), 1, 1000 * 1000 * 1000, 100);
         RecordHistogram.recordCustomCountHistogram(USER_VIEWED_SAVINGS_SIZE_HISTOGRAM_NAME,
                 (int) ((originalTotalBytes - compressedTotalBytes) / 1024), 1, 1000 * 1000 * 1000,
                 100);
-
-        RecordHistogram.recordPercentageHistogram(
-                USER_VIEWED_SAVINGS_PERCENT_HISTOGRAM_NAME, (int) percentage);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionSiteBreakdownView.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionSiteBreakdownView.java
index 396e5fc..ef67a03 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionSiteBreakdownView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionSiteBreakdownView.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.support.annotation.ColorInt;
 import android.text.format.Formatter;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -44,10 +43,6 @@
     private TextView mDataSavedTitle;
     private List<DataReductionDataUseItem> mDataUseItems;
     private boolean mTextViewsNeedAttributesSet;
-    @ColorInt
-    private int mTextColor;
-    @ColorInt
-    private int mLightTextColor;
 
     public DataReductionSiteBreakdownView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -59,10 +54,6 @@
         mTableLayout = (TableLayout) findViewById(R.id.data_reduction_proxy_breakdown_table);
         mDataUsedTitle = (TextView) findViewById(R.id.data_reduction_breakdown_used_title);
         mDataSavedTitle = (TextView) findViewById(R.id.data_reduction_breakdown_saved_title);
-        mTextColor = ApiCompatibilityUtils.getColor(
-                getContext().getResources(), R.color.data_reduction_breakdown_text_color);
-        mLightTextColor = ApiCompatibilityUtils.getColor(
-                getContext().getResources(), R.color.data_reduction_breakdown_light_text_color);
 
         mDataUsedTitle.setOnClickListener(new OnClickListener() {
             @Override
@@ -119,7 +110,6 @@
     }
 
     private void setTextViewSortedAttributes(TextView textView) {
-        textView.setTextColor(mTextColor);
         Drawable arrowDrawable = getStartCompoundDrawable(textView);
         // If the drawable has not been created yet, set mTextViewsNeedAttributesSet so that
         // onMeasure will set the attributes after the drawable is created.
@@ -127,12 +117,14 @@
             mTextViewsNeedAttributesSet = true;
             return;
         }
+        // Expose the arrow for this sorted column using its text color.
         arrowDrawable.mutate();
         arrowDrawable.setAlpha(255);
+        arrowDrawable.setColorFilter(new android.graphics.PorterDuffColorFilter(
+                textView.getCurrentTextColor(), android.graphics.PorterDuff.Mode.SRC_IN));
     }
 
     private void setTextViewUnsortedAttributes(TextView textView) {
-        textView.setTextColor(mLightTextColor);
         Drawable arrowDrawable = getStartCompoundDrawable(textView);
         // If the drawable has not been created yet, set mTextViewsNeedAttributesSet so that
         // onMeasure will set the attributes after the drawable is created.
@@ -140,8 +132,10 @@
             mTextViewsNeedAttributesSet = true;
             return;
         }
+        // Clear the arrow from this unsorted column.
         arrowDrawable.mutate();
         arrowDrawable.setAlpha(0);
+        arrowDrawable.clearColorFilter();
     }
 
     private Drawable getStartCompoundDrawable(TextView textView) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionStatsPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionStatsPreference.java
index 691992b..aca1e3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionStatsPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionStatsPreference.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.preferences.datareduction;
 
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
 import static android.text.format.DateUtils.FORMAT_NO_YEAR;
 import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
 
@@ -50,11 +51,8 @@
     private NetworkStatsHistory mReceivedNetworkStatsHistory;
     private List<DataReductionDataUseItem> mSiteBreakdownItems;
 
-    private TextView mOriginalSizeTextView;
-    private TextView mReceivedSizeTextView;
     private TextView mDataSavingsTextView;
     private TextView mDataUsageTextView;
-    private TextView mPercentReductionTextView;
     private TextView mStartDateTextView;
     private TextView mEndDateTextView;
     private Button mResetStatisticsButton;
@@ -66,7 +64,6 @@
     private CharSequence mOriginalTotalPhrase;
     private CharSequence mSavingsTotalPhrase;
     private CharSequence mReceivedTotalPhrase;
-    private String mPercentReductionPhrase;
     private String mStartDatePhrase;
     private String mEndDatePhrase;
 
@@ -156,13 +153,10 @@
 
     private void setDetailText() {
         updateDetailData();
-        mPercentReductionTextView.setText(mPercentReductionPhrase);
         mStartDateTextView.setText(mStartDatePhrase);
         mEndDateTextView.setText(mEndDatePhrase);
         if (mDataUsageTextView != null) mDataUsageTextView.setText(mReceivedTotalPhrase);
         if (mDataSavingsTextView != null) mDataSavingsTextView.setText(mSavingsTotalPhrase);
-        if (mOriginalSizeTextView != null) mOriginalSizeTextView.setText(mOriginalTotalPhrase);
-        if (mReceivedSizeTextView != null) mReceivedSizeTextView.setText(mReceivedTotalPhrase);
     }
 
     /**
@@ -183,7 +177,6 @@
         super.onBindView(view);
         mDataUsageTextView = (TextView) view.findViewById(R.id.data_reduction_usage);
         mDataSavingsTextView = (TextView) view.findViewById(R.id.data_reduction_savings);
-        mPercentReductionTextView = (TextView) view.findViewById(R.id.data_reduction_percent);
         mStartDateTextView = (TextView) view.findViewById(R.id.data_reduction_start_date);
         mEndDateTextView = (TextView) view.findViewById(R.id.data_reduction_end_date);
         mDataReductionBreakdownView =
@@ -270,7 +263,7 @@
      * Update data reduction statistics whenever the chart's inspection
      * range changes. In particular, this creates strings describing the total
      * original size of all data received over the date range, the total size
-     * of all data received (after compression), the percent data reduction
+     * of all data received (after compression), and the percent data reduction
      * and the range of dates over which these statistics apply.
      */
     // TODO(crbug.com/635567): Fix this properly.
@@ -287,22 +280,15 @@
         mOriginalTotalPhrase = FileSizeUtil.formatFileSize(context, originalTotalBytes);
         final long savingsTotalBytes = originalTotalBytes - compressedTotalBytes;
         mSavingsTotalPhrase = FileSizeUtil.formatFileSize(context, savingsTotalBytes);
-
-        float percentage = 0.0f;
-        if (originalTotalBytes > 0L && originalTotalBytes > compressedTotalBytes) {
-            percentage = (originalTotalBytes - compressedTotalBytes) / (float) originalTotalBytes;
-        }
-        mPercentReductionPhrase = String.format("%.0f%%", 100.0 * percentage);
-
         mStartDatePhrase = formatDate(context, start);
         mEndDatePhrase = formatDate(context, end);
 
         DataReductionProxyUma.dataReductionProxyUserViewedSavings(
-                compressedTotalBytes, originalTotalBytes, 100.0 * percentage);
+                compressedTotalBytes, originalTotalBytes);
     }
 
     private static String formatDate(Context context, long millisSinceEpoch) {
-        final int flags = FORMAT_SHOW_DATE | FORMAT_NO_YEAR;
+        final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH | FORMAT_NO_YEAR;
         return DateUtils.formatDateTime(context, millisSinceEpoch, flags).toString();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 818b3a5..683d8fe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -87,7 +87,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
-import org.chromium.chrome.browser.tabmodel.TabModelImpl;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabReparentingParams;
 import org.chromium.chrome.browser.util.ColorUtils;
@@ -364,9 +363,7 @@
     private TabRedirectHandler mTabRedirectHandler;
 
     private FullscreenManager mFullscreenManager;
-    private float mPreviousTopControlsOffsetY = Float.NaN;
-    private float mPreviousBottomControlsOffsetY = Float.NaN;
-    private float mPreviousContentOffsetY = Float.NaN;
+    private final TabBrowserControlsOffsetHelper mControlsOffsetHelper;
 
     /**
      * Indicates whether this tab is detached from any activity and its corresponding
@@ -617,6 +614,8 @@
         ContextualSearchTabHelper.createForTab(this);
         MediaSessionTabHelper.createForTab(this);
 
+        mControlsOffsetHelper = new TabBrowserControlsOffsetHelper(this);
+
         if (creationState != null) {
             mTabUma = new TabUma(creationState);
             if (frozenState == null) {
@@ -2017,10 +2016,7 @@
                             LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
             notifyContentChanged();
         }
-        FullscreenManager fullscreenManager = getFullscreenManager();
-        if (fullscreenManager != null) {
-            fullscreenManager.setPositionsForTabToNonFullscreen();
-        }
+        mControlsOffsetHelper.showAndroidControls(false);
     }
 
     /**
@@ -2106,9 +2102,7 @@
             mInfoBarContainer = null;
         }
 
-        mPreviousTopControlsOffsetY = Float.NaN;
-        mPreviousBottomControlsOffsetY = Float.NaN;
-        mPreviousContentOffsetY = Float.NaN;
+        mControlsOffsetHelper.clearPreviousPositions();
 
         mNeedsReload = false;
     }
@@ -2806,33 +2800,6 @@
     }
 
     /**
-     * Called when offset values related with fullscreen functionality has been changed by the
-     * compositor.
-     * @param topControlsOffsetY The Y offset of the top controls in physical pixels.
-     *    {@code Float.NaN} if the value is invalid and the cached value should be used.
-     * @param bottomControlsOffsetY The Y offset of the bottom controls in physical pixels.
-     *    {@code Float.NaN} if the value is invalid and the cached value should be used.
-     * @param contentOffsetY The Y offset of the content in physical pixels.
-     */
-    void onOffsetsChanged(
-            float topControlsOffsetY, float bottomControlsOffsetY, float contentOffsetY) {
-        if (!Float.isNaN(topControlsOffsetY)) mPreviousTopControlsOffsetY = topControlsOffsetY;
-        if (!Float.isNaN(bottomControlsOffsetY)) {
-            mPreviousBottomControlsOffsetY = bottomControlsOffsetY;
-        }
-        if (!Float.isNaN(contentOffsetY)) mPreviousContentOffsetY = contentOffsetY;
-
-        if (mFullscreenManager == null) return;
-        if (isShowingSadTab() || isNativePage()) {
-            mFullscreenManager.setPositionsForTabToNonFullscreen();
-        } else {
-            mFullscreenManager.setPositionsForTab(mPreviousTopControlsOffsetY,
-                    mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
-        }
-        TabModelImpl.setActualTabSwitchLatencyMetricRequired();
-    }
-
-    /**
      * Push state about whether or not the browser controls can show or hide to the renderer.
      */
     public void updateFullscreenEnabledState() {
@@ -2963,24 +2930,7 @@
      */
     public void setFullscreenManager(FullscreenManager manager) {
         mFullscreenManager = manager;
-        if (mFullscreenManager != null) {
-            boolean topOffsetsInitialized = !Float.isNaN(mPreviousTopControlsOffsetY)
-                    && !Float.isNaN(mPreviousContentOffsetY);
-            boolean bottomOffsetsInitialized =
-                    !Float.isNaN(mPreviousBottomControlsOffsetY);
-            boolean isChromeHomeEnabled = FeatureUtilities.isChromeHomeEnabled();
-
-            // Make sure the dominant control offsets have been set.
-            if ((!topOffsetsInitialized && !isChromeHomeEnabled)
-                    || (!bottomOffsetsInitialized && isChromeHomeEnabled)) {
-                mFullscreenManager.setPositionsForTabToNonFullscreen();
-            } else {
-                mFullscreenManager.setPositionsForTab(mPreviousTopControlsOffsetY,
-                        mPreviousBottomControlsOffsetY,
-                        mPreviousContentOffsetY);
-            }
-            updateFullscreenEnabledState();
-        }
+        mControlsOffsetHelper.resetPositions();
     }
 
     /**
@@ -3338,7 +3288,7 @@
         mIsRendererUnresponsive = true;
         if (mFullscreenManager == null) return;
 
-        mFullscreenManager.setPositionsForTabToNonFullscreen();
+        mControlsOffsetHelper.showAndroidControls(false);
         updateBrowserControlsState(BrowserControlsState.SHOWN, false);
     }
 
@@ -3438,9 +3388,27 @@
      */
     public void onTabModalDialogStateChanged(boolean isShowing) {
         mIsShowingTabModalDialog = isShowing;
-        if (mFullscreenManager == null) return;
-        mFullscreenManager.setPositionsForTabToNonFullscreen();
-        updateBrowserControlsState(BrowserControlsState.SHOWN, false);
+        // Also need to update browser control state after dismissal to refresh the constraints.
+        if (isShowing && areRendererInputEventsIgnored()) {
+            mControlsOffsetHelper.showAndroidControls(true);
+        } else {
+            updateBrowserControlsState(BrowserControlsState.SHOWN,
+                    !mControlsOffsetHelper.isControlsOffsetOverridden());
+        }
+    }
+
+    /**
+     * @return Whether input events from the renderer are ignored on the browser side.
+     */
+    boolean areRendererInputEventsIgnored() {
+        return nativeAreRendererInputEventsIgnored(mNativeTabAndroid);
+    }
+
+    /**
+     * @return The {@link TabBrowserControlsOffsetHelper} for this tab.
+     */
+    public TabBrowserControlsOffsetHelper getControlsOffsetHelper() {
+        return mControlsOffsetHelper;
     }
 
     @CalledByNative
@@ -3573,4 +3541,5 @@
     private native void nativeAttachDetachedTab(long nativeTabAndroid);
     private native void nativeMediaDownloadInProductHelpDismissed(long nativeTabAndroid);
     private native int nativeGetCurrentRenderProcessId(long nativeTabAndroid);
+    private native boolean nativeAreRendererInputEventsIgnored(long nativeTabAndroid);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
new file mode 100644
index 0000000..e8769de
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
@@ -0,0 +1,229 @@
+// 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.tab;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+
+import org.chromium.base.ObserverList;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
+import org.chromium.chrome.browser.tabmodel.TabModelImpl;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+
+/**
+ * Handles browser controls offset for a Tab.
+ */
+public class TabBrowserControlsOffsetHelper {
+    /**
+     * Maximum duration for the control container slide-in animation. Note that this value matches
+     * the one in browser_controls_offset_manager.cc.
+     */
+    private static final int MAX_CONTROLS_ANIMATION_DURATION_MS = 200;
+
+    /**
+     * An interface for notification about browser controls offset updates.
+     */
+    public interface Observer {
+        /**
+         * Called when the browser controls are fully visible on screen.
+         */
+        void onBrowserControlsFullyVisible(Tab tab);
+    }
+
+    private final Tab mTab;
+    private final ObserverList<Observer> mObservers = new ObserverList<>();
+
+    private float mPreviousTopControlsOffsetY = Float.NaN;
+    private float mPreviousBottomControlsOffsetY = Float.NaN;
+    private float mPreviousContentOffsetY = Float.NaN;
+
+    /**
+     * Whether the Android browser controls offset is overridden. This handles top controls only.
+     */
+    private boolean mIsControlsOffsetOverridden;
+
+    /**
+     * The animator for slide-in animation on the Android controls.
+     */
+    private ValueAnimator mControlsAnimator;
+
+    /**
+     * @param tab The {@link Tab} that this class is associated with.
+     */
+    TabBrowserControlsOffsetHelper(Tab tab) {
+        mTab = tab;
+    }
+
+    /**
+     * @return Whether the Android browser controls offset is overridden on the browser side.
+     */
+    public boolean isControlsOffsetOverridden() {
+        return mIsControlsOffsetOverridden;
+    }
+
+    /**
+     * @return Whether the browser controls are fully visible on screen.
+     */
+    public boolean areBrowserControlsFullyVisible() {
+        final FullscreenManager manager = mTab.getFullscreenManager();
+        return Float.compare(0f, manager.getBrowserControlHiddenRatio()) == 0;
+    }
+
+    /**
+     * @param observer The observer to be added to get notifications from this class.
+     */
+    public void addObserver(Observer observer) {
+        mObservers.addObserver(observer);
+    }
+
+    /**
+     * @param observer The observer to be removed to cancel notifications from this class.
+     */
+    public void removeObserver(Observer observer) {
+        mObservers.removeObserver(observer);
+    }
+
+    /**
+     * Called when offset values related with fullscreen functionality has been changed by the
+     * compositor.
+     * @param topControlsOffsetY The Y offset of the top controls in physical pixels.
+     *    {@code Float.NaN} if the value is invalid and the cached value should be used.
+     * @param bottomControlsOffsetY The Y offset of the bottom controls in physical pixels.
+     *    {@code Float.NaN} if the value is invalid and the cached value should be used.
+     * @param contentOffsetY The Y offset of the content in physical pixels.
+     */
+    void onOffsetsChanged(
+            float topControlsOffsetY, float bottomControlsOffsetY, float contentOffsetY) {
+        // Cancel any animation on the Android controls and let compositor drive the offset updates.
+        resetControlsOffsetOverridden();
+
+        if (!Float.isNaN(topControlsOffsetY)) mPreviousTopControlsOffsetY = topControlsOffsetY;
+        if (!Float.isNaN(bottomControlsOffsetY)) {
+            mPreviousBottomControlsOffsetY = bottomControlsOffsetY;
+        }
+        if (!Float.isNaN(contentOffsetY)) mPreviousContentOffsetY = contentOffsetY;
+
+        if (mTab.getFullscreenManager() == null) return;
+        if (mTab.isShowingSadTab() || mTab.isNativePage()) {
+            showAndroidControls(false);
+        } else {
+            updateFullscreenManagerOffsets(false, mPreviousTopControlsOffsetY,
+                    mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
+        }
+        TabModelImpl.setActualTabSwitchLatencyMetricRequired();
+    }
+
+    /**
+     * Shows the Android browser controls view.
+     * @param animate Whether a slide-in animation should be run.
+     */
+    void showAndroidControls(boolean animate) {
+        if (mTab.getFullscreenManager() == null) return;
+
+        if (animate) {
+            runBrowserDrivenShowAnimation();
+        } else {
+            updateFullscreenManagerOffsets(true, Float.NaN, Float.NaN, Float.NaN);
+        }
+    }
+
+    /**
+     * Resets the controls positions in {@link FullscreenManager} to the cached positions.
+     */
+    void resetPositions() {
+        resetControlsOffsetOverridden();
+        if (mTab.getFullscreenManager() == null) return;
+
+        boolean topOffsetsInitialized =
+                !Float.isNaN(mPreviousTopControlsOffsetY) && !Float.isNaN(mPreviousContentOffsetY);
+        boolean bottomOffsetsInitialized = !Float.isNaN(mPreviousBottomControlsOffsetY);
+        boolean isChromeHomeEnabled = FeatureUtilities.isChromeHomeEnabled();
+
+        // Make sure the dominant control offsets have been set.
+        if ((!topOffsetsInitialized && !isChromeHomeEnabled)
+                || (!bottomOffsetsInitialized && isChromeHomeEnabled)) {
+            showAndroidControls(false);
+        } else {
+            updateFullscreenManagerOffsets(false, mPreviousTopControlsOffsetY,
+                    mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
+        }
+        mTab.updateFullscreenEnabledState();
+    }
+
+    /**
+     * Clears the cached browser controls positions.
+     */
+    void clearPreviousPositions() {
+        mPreviousTopControlsOffsetY = Float.NaN;
+        mPreviousBottomControlsOffsetY = Float.NaN;
+        mPreviousContentOffsetY = Float.NaN;
+    }
+
+    /**
+     * Helper method to update offsets in {@link FullscreenManager} and notify offset changes to
+     * observers if necessary.
+     */
+    private void updateFullscreenManagerOffsets(boolean toNonFullscreen, float topControlsOffset,
+            float bottomControlsOffset, float topContentOffset) {
+        final FullscreenManager manager = mTab.getFullscreenManager();
+        if (toNonFullscreen) {
+            manager.setPositionsForTabToNonFullscreen();
+        } else {
+            manager.setPositionsForTab(topControlsOffset, bottomControlsOffset, topContentOffset);
+        }
+
+        if (!areBrowserControlsFullyVisible()) return;
+        for (Observer observer : mObservers) {
+            observer.onBrowserControlsFullyVisible(mTab);
+        }
+    }
+
+    /**
+     * Helper method to cancel overridden offset on Android browser controls.
+     */
+    private void resetControlsOffsetOverridden() {
+        if (!mIsControlsOffsetOverridden) return;
+        if (mControlsAnimator != null) mControlsAnimator.cancel();
+        mIsControlsOffsetOverridden = false;
+    }
+
+    /**
+     * Helper method to run slide-in animations on the Android browser controls views.
+     */
+    private void runBrowserDrivenShowAnimation() {
+        if (mControlsAnimator != null) return;
+
+        mIsControlsOffsetOverridden = true;
+
+        final FullscreenManager manager = mTab.getFullscreenManager();
+        final float hiddenRatio = manager.getBrowserControlHiddenRatio();
+        final float topControlHeight = manager.getTopControlsHeight();
+        final float topControlOffset = hiddenRatio == 0f ? 0f : hiddenRatio * -topControlHeight;
+
+        // Set animation start value to current renderer controls offset.
+        mControlsAnimator = ValueAnimator.ofFloat(topControlOffset, 0f);
+        mControlsAnimator.setDuration(
+                (long) Math.abs(hiddenRatio * MAX_CONTROLS_ANIMATION_DURATION_MS));
+        mControlsAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mControlsAnimator = null;
+                mPreviousTopControlsOffsetY = 0f;
+                mPreviousContentOffsetY = topControlHeight;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                updateFullscreenManagerOffsets(false, topControlHeight, 0, topControlHeight);
+            }
+        });
+        mControlsAnimator.addUpdateListener((animator) -> {
+            updateFullscreenManagerOffsets(
+                    false, (float) animator.getAnimatedValue(), 0, topControlHeight);
+        });
+        mControlsAnimator.start();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
index 168d3c4..7898ebf8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
@@ -30,12 +30,14 @@
 
     @Override
     public void onTopControlsChanged(float topControlsOffsetY, float topContentOffsetY) {
-        mTab.onOffsetsChanged(topControlsOffsetY, Float.NaN, topContentOffsetY);
+        mTab.getControlsOffsetHelper().onOffsetsChanged(
+                topControlsOffsetY, Float.NaN, topContentOffsetY);
     }
 
     @Override
     public void onBottomControlsChanged(float bottomControlsOffsetY, float bottomContentOffsetY) {
-        mTab.onOffsetsChanged(Float.NaN, bottomControlsOffsetY, Float.NaN);
+        mTab.getControlsOffsetHelper().onOffsetsChanged(
+                Float.NaN, bottomControlsOffsetY, Float.NaN);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
index 0359aece..6315a9b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
@@ -242,6 +242,9 @@
         // Don't eat the event if we don't have a handler.
         if (mSwipeHandler == null) return false;
 
+        // Don't react on touch events if the toolbar container is not fully visible.
+        if (!isFullyVisible()) return true;
+
         // If we have ACTION_DOWN in this context, that means either no child consumed the event or
         // this class is the top UI at the event position. Then, we don't need to feed the event to
         // mGestureDetector here because the event is already once fed in onInterceptTouchEvent().
@@ -256,6 +259,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (!isFullyVisible()) return true;
         if (mSwipeHandler == null || isOnTabStrip(event)) return false;
 
         return mSwipeRecognizer.onTouchEvent(event);
@@ -265,6 +269,13 @@
         return e.getY() <= mTabStripHeight;
     }
 
+    /**
+     * @return Whether or not the control container is fully visible on screen.
+     */
+    private boolean isFullyVisible() {
+        return Float.compare(0f, getTranslationY()) == 0;
+    }
+
     private class SwipeRecognizerImpl extends SwipeRecognizer {
         public SwipeRecognizerImpl(Context context) {
             super(context);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index a87727d..a469fd7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -605,6 +605,8 @@
     public static void maybeHandleVrIntentPreNative(ChromeActivity activity, Intent intent) {
         if (!VrIntentUtils.isVrIntent(intent)) return;
 
+        if (sInstance != null) sInstance.swapHostActivity(activity);
+
         // If we're already in VR, nothing to do here.
         if (sInstance != null && sInstance.mInVr) return;
         if (DEBUG_LOGS) Log.i(TAG, "maybeHandleVrIntentPreNative: preparing for transition");
@@ -1016,6 +1018,7 @@
         assert mActivity != null;
         if (mActivity == activity) return;
         mActivity = activity;
+        mListeningForWebVrActivateBeforePause = false;
         if (mVrDaydreamApi != null) mVrDaydreamApi.close();
         mVrDaydreamApi = getVrClassesWrapper().createVrDaydreamApi(mActivity);
     }
@@ -1306,13 +1309,29 @@
             return;
         }
 
-        // Note that cancelling the animation below is what causes us to enter VR mode when Chrome
-        // is cold-started. We start an intermediate activity to cancel the animation which causes
-        // onPause and onResume to be called and we enter VR mode in onResume (because we set the
-        // mEnterVrOnStartup bit above). If Chrome is already running, onResume which will be called
-        // after VrShellDelegate#onNewIntentWithNative which will cancel the animation and enter VR
-        // after that.
-        if (!mPaused) cancelStartupAnimationIfNeeded();
+        if (!mPaused) {
+            // Note that cancelling the animation below is what causes us to enter VR mode. We start
+            // an intermediate activity to cancel the animation which causes onPause and onResume to
+            // be called and we enter VR mode in onResume (because we set the mEnterVrOnStartup bit
+            // above). If Chrome is already running, onResume which will be called after
+            // VrShellDelegate#onNewIntentWithNative which will cancel the animation and enter VR
+            // after that.
+            if (!cancelStartupAnimationIfNeeded()) {
+                // If we didn't cancel the startup animation, we won't be getting another onResume
+                // call, so enter VR here.
+                handleDonFlowSuccess();
+
+                // This is extremely unlikely to happen in practice, but it's theoretically possible
+                // for the page to have loaded and registered an activate handler before this point.
+                // Usually the displayActivate is sent from
+                // VrShellDelegate#setListeningForWebVrActivate.
+                if (mAutopresentWebVr && mListeningForWebVrActivate) {
+                    // Dispatch vrdisplayactivate so that the WebVr page can call requestPresent
+                    // to start presentation.
+                    nativeDisplayActivate(mNativeVrShellDelegate);
+                }
+            }
+        }
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java
new file mode 100644
index 0000000..92b3199
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java
@@ -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.
+
+package org.chromium.chrome.browser.webauth;
+
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.services.service_manager.InterfaceFactory;
+import org.chromium.webauth.mojom.Authenticator;
+
+/**
+ * Factory class registered to create Authenticators upon request.
+ */
+public class AuthenticatorFactory implements InterfaceFactory<Authenticator> {
+    private final RenderFrameHost mRenderFrameHost;
+
+    public AuthenticatorFactory(RenderFrameHost renderFrameHost) {
+        mRenderFrameHost = renderFrameHost;
+    }
+
+    @Override
+    public Authenticator createImpl() {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_AUTH)) {
+            return null;
+        }
+
+        if (mRenderFrameHost == null) return null;
+        return new AuthenticatorImpl(mRenderFrameHost);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java
new file mode 100644
index 0000000..fb35480
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java
@@ -0,0 +1,107 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.webauth;
+
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsStatics;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.webauth.mojom.Authenticator;
+import org.chromium.webauth.mojom.AuthenticatorStatus;
+import org.chromium.webauth.mojom.GetAssertionAuthenticatorResponse;
+import org.chromium.webauth.mojom.MakeCredentialAuthenticatorResponse;
+import org.chromium.webauth.mojom.PublicKeyCredentialCreationOptions;
+import org.chromium.webauth.mojom.PublicKeyCredentialRequestOptions;
+
+/**
+ * Android implementation of the authenticator.mojom interface.
+ */
+public class AuthenticatorImpl implements Authenticator, HandlerResponseCallback {
+    private final RenderFrameHost mRenderFrameHost;
+    private final WebContents mWebContents;
+
+    /** Ensures only one request is processed at a time. */
+    boolean mIsOperationPending = false;
+
+    private org.chromium.mojo.bindings.Callbacks
+            .Callback2<Integer, MakeCredentialAuthenticatorResponse> mMakeCredentialCallback;
+    private org.chromium.mojo.bindings.Callbacks
+            .Callback2<Integer, GetAssertionAuthenticatorResponse> mGetAssertionCallback;
+
+    /**
+     * Builds the Authenticator service implementation.
+     *
+     * @param renderFrameHost The host of the frame that has invoked the API.
+     */
+    public AuthenticatorImpl(RenderFrameHost renderFrameHost) {
+        assert renderFrameHost != null;
+        mRenderFrameHost = renderFrameHost;
+        mWebContents = WebContentsStatics.fromRenderFrameHost(renderFrameHost);
+    }
+
+    @Override
+    public void makeCredential(
+            PublicKeyCredentialCreationOptions options, MakeCredentialResponse callback) {
+        mMakeCredentialCallback = callback;
+        if (mIsOperationPending) {
+            onError(AuthenticatorStatus.PENDING_REQUEST);
+        }
+
+        mIsOperationPending = true;
+        onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    public void getAssertion(
+            PublicKeyCredentialRequestOptions options, GetAssertionResponse callback) {
+        mGetAssertionCallback = callback;
+        if (mIsOperationPending) {
+            onError(AuthenticatorStatus.PENDING_REQUEST);
+        }
+
+        mIsOperationPending = true;
+        onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+    }
+
+    /**
+     * Callbacks for receiving responses from the internal handlers.
+     */
+    @Override
+    public void onRegisterResponse(Integer status, MakeCredentialAuthenticatorResponse response) {
+        assert mMakeCredentialCallback != null;
+        mMakeCredentialCallback.call(status, response);
+        close();
+    }
+
+    @Override
+    public void onSignResponse(Integer status, GetAssertionAuthenticatorResponse response) {
+        assert mGetAssertionCallback != null;
+        mGetAssertionCallback.call(status, response);
+        close();
+    }
+
+    @Override
+    public void onError(Integer status) {
+        assert(mMakeCredentialCallback != null || mGetAssertionCallback != null);
+        if (mMakeCredentialCallback != null) {
+            mMakeCredentialCallback.call(status, null);
+        } else if (mGetAssertionCallback != null) {
+            mGetAssertionCallback.call(status, null);
+        }
+        close();
+    }
+
+    @Override
+    public void close() {
+        mIsOperationPending = false;
+        mMakeCredentialCallback = null;
+        mGetAssertionCallback = null;
+    }
+
+    @Override
+    public void onConnectionError(MojoException e) {
+        close();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/HandlerResponseCallback.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/HandlerResponseCallback.java
new file mode 100644
index 0000000..c3b19fb
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webauth/HandlerResponseCallback.java
@@ -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.
+
+package org.chromium.chrome.browser.webauth;
+
+import org.chromium.webauth.mojom.GetAssertionAuthenticatorResponse;
+import org.chromium.webauth.mojom.MakeCredentialAuthenticatorResponse;
+
+/**
+ * Callback for receiving responses from an internal handler.
+ */
+public interface HandlerResponseCallback {
+    /**
+     * Interface that returns the response from a request to register a
+     * credential with an authenticator.
+     */
+    void onRegisterResponse(Integer status, MakeCredentialAuthenticatorResponse response);
+
+    /**
+     * Interface that returns the response from a request to use a credential
+     * to produce a signed assertion.
+     */
+    void onSignResponse(Integer status, GetAssertionAuthenticatorResponse response);
+
+    /**
+     * Interface that returns any errors from either register or sign requests.
+     */
+    void onError(Integer status);
+}
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index d583f32e..acfa8ff 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -143,7 +143,7 @@
       <message name="IDS_SAVE" desc="Label for a button to save a change. Used in multiple contexts. [CHAR-LIMIT=20]">
         Save
       </message>
-      <message name="IDS_DETAILS_LINK" desc="The label of the link to open the detailed information.">
+      <message name="IDS_DETAILS_LINK" desc="In 1) Settings > Clean up computer (desktop), a link to open details of incompatible applications. In 2) Settings > Data Saver (mobile), static title for data usage breakdown." meaning="Short for 'view details'. Link; static title.">
         Details
       </message>
       <message name="IDS_DONE" desc="Label for a button to save a change or finish editing data. Used in multiple contexts. [CHAR-LIMIT=20]">
@@ -1105,17 +1105,14 @@
         <ph name="PERCENT">%1$s<ex>49%</ex></ph> data savings
       </message>
       <message name="IDS_DATA_REDUCTION_SAVINGS_LABEL" desc="Data Reduction statistics label that states the amount of mobile data that was saved by Data Saver. Data Saver allows users to to reduce their mobile data usage by compressing network traffic.">
-        Data saved:
+        data saved
       </message>
       <message name="IDS_DATA_REDUCTION_USAGE_LABEL" desc="Data Reduction statistics label that states the amount of data that was used by Chrome when Data Saver is enabled.">
-        Data used:
+        data used
       </message>
       <message name="IDS_DATA_REDUCTION_PROXY_UNREACHABLE_WARN" desc="Warning message shown when Google data reduction proxy servers are not reachable.">
         Chrome is unable to reach Google servers for data compression. Your data savings may be limited.
       </message>
-      <message name="IDS_DATA_REDUCTION_DATA_USAGE_BREAKDOWN_TITLE" desc="Title for the data usage breakdown on the Data Reduction statistics page. The breakdown lists the top ten sites with the greatest amount of data usage or mobile data that was saved.">
-        Data usage breakdown
-      </message>
       <message name="IDS_DATA_REDUCTION_BREAKDOWN_SITE_TITLE" desc="Title for the sites column on the Data Reduction statistics page. The breakdown lists the top ten sites with the greatest amount of data usage or mobile data that was saved.">
         Site
       </message>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index cac445705..2d301d0 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -783,6 +783,7 @@
   "java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java",
   "java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java",
   "java/src/org/chromium/chrome/browser/offlinepages/OfflinePagesDownloadManagerBridge.java",
+  "java/src/org/chromium/chrome/browser/offlinepages/PublishPageCallback.java",
   "java/src/org/chromium/chrome/browser/offlinepages/SavePageRequest.java",
   "java/src/org/chromium/chrome/browser/offlinepages/TaskExtrasPacker.java",
   "java/src/org/chromium/chrome/browser/offlinepages/TriggerConditions.java",
@@ -1214,6 +1215,7 @@
   "java/src/org/chromium/chrome/browser/tab/SadTabView.java",
   "java/src/org/chromium/chrome/browser/tab/SadTabViewFactory.java",
   "java/src/org/chromium/chrome/browser/tab/Tab.java",
+  "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java",
   "java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java",
   "java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java",
   "java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java",
@@ -1377,6 +1379,9 @@
   "java/src/org/chromium/chrome/browser/webapps/WebappScopePolicy.java",
   "java/src/org/chromium/chrome/browser/webapps/WebappSplashScreenController.java",
   "java/src/org/chromium/chrome/browser/webapps/WebappTabDelegate.java",
+  "java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java",
+  "java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java",
+  "java/src/org/chromium/chrome/browser/webauth/HandlerResponseCallback.java",
   "java/src/org/chromium/chrome/browser/webshare/ShareServiceImpl.java",
   "java/src/org/chromium/chrome/browser/webshare/ShareServiceImplementationFactory.java",
   "java/src/org/chromium/chrome/browser/widget/AlertDialogEditText.java",
@@ -1901,6 +1906,7 @@
   "javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappVisibilityTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java",
+  "javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java",
   "javatests/src/org/chromium/chrome/browser/widget/DualControlLayoutTest.java",
   "javatests/src/org/chromium/chrome/browser/widget/ImageViewTinterTest.java",
   "javatests/src/org/chromium/chrome/browser/widget/OverviewListLayoutTest.java",
diff --git a/chrome/android/javatests/DEPS b/chrome/android/javatests/DEPS
index 6f99387..fbc157f 100644
--- a/chrome/android/javatests/DEPS
+++ b/chrome/android/javatests/DEPS
@@ -43,4 +43,7 @@
   "WebApkIntegrationTest\.java": [
     "+content/public/android/java/src/org/chromium/content/common/ContentSwitches.java",
   ],
+  "AuthenticatorTest\.java": [
+    "+content/public/android/java/src/org/chromium/content/common/ContentSwitches.java",
+  ],
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
index 726cbe3..4b53a97 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
@@ -77,6 +77,9 @@
         mTestObserver = new TestObserver();
         mActivity.getToolbarManager().getToolbarLayout().getLocationBar().addUrlFocusChangeListener(
                 mTestObserver);
+        TabModalPresenter presenter =
+                (TabModalPresenter) mManager.getPresenterForTest(ModalDialogManager.TAB_MODAL);
+        presenter.disableAnimationForTest();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java
new file mode 100644
index 0000000..76b653c
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java
@@ -0,0 +1,119 @@
+// 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.webauth;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content.common.ContentSwitches;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
+
+/** Test suite for navigator.credentials functionality. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
+        "enable-experimental-web-platform-features", "enable-features=WebAuthentication",
+        "ignore-certificate-errors"})
+public class AuthenticatorTest {
+    @Rule
+    public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
+            new ChromeActivityTestRule<>(ChromeActivity.class);
+
+    private static final String TEST_FILE = "/content/test/data/android/authenticator.html";
+    private EmbeddedTestServer mTestServer;
+    private String mUrl;
+    private Tab mTab;
+    private AuthenticatorUpdateWaiter mUpdateWaiter;
+
+    /** Waits until the JavaScript code supplies a result. */
+    private class AuthenticatorUpdateWaiter extends EmptyTabObserver {
+        private CallbackHelper mCallbackHelper;
+        private String mStatus;
+
+        public AuthenticatorUpdateWaiter() {
+            mCallbackHelper = new CallbackHelper();
+        }
+
+        @Override
+        public void onTitleUpdated(Tab tab) {
+            String title = mActivityTestRule.getActivity().getActivityTab().getTitle();
+
+            // Wait until the title indicates either success or failure.
+            if (!title.startsWith("Success") && !title.startsWith("Fail:")) return;
+            mStatus = title;
+            mCallbackHelper.notifyCalled();
+        }
+
+        public String waitForUpdate() throws Exception {
+            mCallbackHelper.waitForCallback(0);
+            return mStatus;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivityTestRule.startMainActivityOnBlankPage();
+        mTestServer = EmbeddedTestServer.createAndStartHTTPSServer(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                ServerCertificate.CERT_OK);
+        mUrl = mTestServer.getURLWithHostName("subdomain.example.test", TEST_FILE);
+        mTab = mActivityTestRule.getActivity().getActivityTab();
+        mUpdateWaiter = new AuthenticatorUpdateWaiter();
+        mTab.addObserver(mUpdateWaiter);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTab.removeObserver(mUpdateWaiter);
+        mTestServer.stopAndDestroyServer();
+    }
+
+    /**
+     * Verify that the Mojo bridge between Blink and Java is working for
+     * navigator.credentials.create. This test currently expects a
+     * "Not Implemented" response. Testing any real response would require
+     * setting up or mocking a real APK.
+     */
+    @Test
+    @MediumTest
+    @Feature({"WebAuth"})
+    public void testCreatePublicKeyCredential() throws Exception {
+        mActivityTestRule.loadUrl(mUrl);
+        mActivityTestRule.runJavaScriptCodeInCurrentTab("doCreatePublicKeyCredential()");
+        Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
+    }
+
+    /**
+     * Verify that the Mojo bridge between Blink and Java is working for
+     * navigator.credentials.create. This test currently expects a
+     * "Not Implemented" response. Testing any real response would require
+     * setting up or mocking a real APK.
+     */
+    @Test
+    @MediumTest
+    @Feature({"WebAuth"})
+    public void testGetPublicKeyCredential() throws Exception {
+        mActivityTestRule.loadUrl(mUrl);
+        mActivityTestRule.runJavaScriptCodeInCurrentTab("doGetPublicKeyCredential()");
+        Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
+    }
+}
diff --git a/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json b/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
index 83176bbc..49172fc 100644
--- a/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
+++ b/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
@@ -6,7 +6,8 @@
       "VrSkipDon": true,
       "DaydreamSetupComplete": true,
       "VrDeviceParams": "CgZHb29nbGUSCUNhcmRib2FyZB0J-SA9JQHegj0qEAAAcEIAAHBCAABwQgAAcEI1KVwPPToIV_GrPmKxDT9QAFgAYAM",
-      "UseAutomatedController": false
+      "UseAutomatedController": false,
+      "GvrPlatformLibraryPref": false
     }
   }
 ]
diff --git a/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
index 6b7d0f2e..c554ba2c 100644
--- a/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
+++ b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
@@ -8,7 +8,8 @@
       "VrDeviceParams": "CgxHb29nbGUsIEluYy4SDURheWRyZWFtIFZpZXcdCfkgPSUB3oI9KhAAAFxCAABcQgAAXEIAAFxCNd9PDT06CLgexT7Zzhc_WABgAJqRYBoIARIKDQAAAAAV9P3UPBIKDQAAAAAV9P3UvA",
       "UseAutomatedController": true,
       "PairedControllerDriver": "DRIVER_AUTOMATED",
-      "PairedControllerAddress": "FOO"
+      "PairedControllerAddress": "FOO",
+      "GvrPlatformLibraryPref": false
     }
   }
 ]
diff --git a/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete_o2.json b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete_o2.json
new file mode 100644
index 0000000..700e6d5
--- /dev/null
+++ b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete_o2.json
@@ -0,0 +1,15 @@
+[
+  {
+    "package": "com.google.vr.vrcore",
+    "filename": "VrCoreSettings.xml",
+    "set": {
+      "VrSkipDon": true,
+      "DaydreamSetupComplete": true,
+      "VrDeviceParams": "CgxHb29nbGUsIEluYy4SDURheWRyZWFtIFZpZXcdCfkgPSUB3oI9KhAAAFxCAABcQgAAXEIAAFxCNd9PDT06CLgexT7Zzhc_WABgAJqRYBoIARIKDQAAAAAV9P3UPBIKDQAAAAAV9P3UvA",
+      "UseAutomatedController": true,
+      "PairedControllerDriver": "DRIVER_AUTOMATED",
+      "PairedControllerAddress": "FOO",
+      "GvrPlatformLibraryPref": true
+    }
+  }
+]
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 1bd8e671..e6175d9 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -4487,13 +4487,13 @@
     No thanks
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_BUTTON_AGREE" desc="Agree button of the opt-in dialog for Android apps.">
-    Agree
+    I Agree
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_BUTTON_CANCEL" desc="Cancel button of the opt-in dialog for Android apps.">
     Cancel
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_BUTTON_NEXT" desc="Next button of the opt-in dialog for Android apps.">
-    Next
+    More
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_BUTTON_RETRY" desc="Retry button of the opt-in dialog for Android apps.">
     Try again
@@ -4514,42 +4514,59 @@
     Terms of Service
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_ENABLED" desc="Message in the opt-in dialog for Android apps in case metrics are managed on the device and on.">
-    This device currently sends diagnostic and usage data to Google. This <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>setting<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph> is enforced by the owner. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+    Send system data. This device currently automatically sends diagnostic and device and app usage data to Google. This <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>setting<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph> is enforced by the owner. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
-  <message name="IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_DISABLED" desc="Message in the opt-in dialog for Android apps in case metrics are enabled on the device and off.">
-    The owner may choose to send diagnostic and usage data for this device to Google. You may view this <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>setting<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph> here. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+  <message name="IDS_ARC_OPT_IN_DIALOG_METRICS_MANAGED_DISABLED" desc="Message in the opt-in dialog for Android apps in case metrics are managed on the device and off.">
+    Send system data. Automatically send diagnostic and device and app usage data to Google. This setting is enforced by the owner. The owner may choose to send diagnostic and usage data for this device to Google. You may view this in <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>settings<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_METRICS_ENABLED" desc="Message in the opt-in dialog for Android apps in case metrics are already enabled on the device. User has no way to deactivate them using opt-in dialog.">
-    This device currently sends diagnostic and usage data to Google. You can change this at any time in your device <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>settings<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+    Send system data. This device currently automatically sends diagnostic and device and app usage data to Google. You can change this at any time in your device <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>settings<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_METRICS_DISABLED" desc="Message in the opt-in dialog for Android apps in case metrics are disabled on the device. User has an option to active them using opt-in dialog">
-    Automatically send diagnostic and usage data to Google. You can change this at any time in your device <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>settings<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+    Send system data. Automatically send diagnostic and device and app usage data to Google. You can change this at any time in your device <ph name="BEGIN_LINK1">&lt;a href="#" id="settings-link"&gt;</ph>settings<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK2">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK2">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
   <message name="IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE" desc="Message in the opt-in dialog for users to enable Backup and Restore for Android apps.">
-    Automatically back up and restore Play app data to Google Drive. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-backup-restore"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+    Back up to Google Drive. Easily restore your data or switch device at any time. Your backup includes app data. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-backup-restore"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
   <message name="IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_ENABLED" desc="Message in the Arc Terms OOBE dialog in case metrics are managed on the device and on.">
-    This device currently sends diagnostic and usage data to Google. This setting is enforced by the owner. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+    Send system data. This device currently automatically sends diagnostic and device and app usage data to Google. This setting is enforced by the owner. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
-  <message name="IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED" desc="Message in the Arc Terms OOBE dialog in case metrics are enabled on the device and off.">
-    The owner may choose to send diagnostic and usage data for this device to Google. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK1">&lt;a&gt;<ex>&lt;/a&gt;</ex></ph>
+  <message name="IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED" desc="Message in the Arc Terms OOBE dialog in case metrics are managed on the device and off.">
+    Send system data. Automatically send diagnostic and device and app usage data to Google. This setting is enforced by the owner. The owner may choose to send diagnostic and usage data for this device to Google. You may view this in settings. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
   </message>
-  <message name="IDS_ARC_OPT_IN_LOCATION_SETTING" desc="Message in the opt-in dialog for Android apps for the user to turn on Google location services">
-    Let Google’s location service help apps find your location quickly and accurately, which can reduce battery consumption. Anonymous location data will be sent to Google, even when no apps are running. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-location-service"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+  <message name="IDS_ARC_OOBE_TERMS_DIALOG_METRICS_ENABLED" desc="Message in the Arc Terms OOBE dialog in case metrics are already enabled on the device.">
+    Send system data. This device currently automatically sends diagnostic and device and app usage data to Google. You can change this at any time in your device settings. If you turned on additional Web &#38; App Activity, this information will be stored with your account so you can manage it in My Activity. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-metrics"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+  </message>
+  <message name="IDS_ARC_OPT_IN_LOCATION_SETTING" desc="Message in the opt-in dialog for Android apps for the user to turn on Google location services.">
+    Help apps find location. Use Google’s location service to help improve location for apps. Google may collect location data periodically and use this data in an anonymous way to improve location accuracy and location-based services. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-location-service"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+  </message>
+  <message name="IDS_ARC_OPT_IN_PAI" desc="Message in the opt-in dialog for Play auto install.">
+    Install updates &#38; apps. By continuing, you agree that this device may also automatically download and install updates and apps from Google, your carrier, and your device's manufacturer, possibly using cellular data. Some of these apps may offer in-app purchases. You can remove these apps at any time. <ph name="BEGIN_LINK1">&lt;a href="#" id="learn-more-link-pai"&gt;</ph>Learn More<ph name="END_LINK1">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>
+  </message>
+  <message name="IDS_ARC_OPT_IN_GOOGLE_SERVICE_CONFIRMATION" desc="Message in the opt-in dialog for Accepting Google services.">
+    By clicking the "I Agree" button, you agree to the processing described above for these Google services.
   </message>
   <message name="IDS_ARC_OPT_IN_LEARN_MORE_CLOSE" desc="Text for close button in learn more dialog">
     Close
   </message>
   <message name="IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS" desc="Message shown in pop up overlay dialog when user clicks learn more UMA">
-    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>This is general information about your device and how you use it, such as battery level, how often you use your apps, the quality and duration of your network connections (like Wi-Fi and Bluetooth), and crash reports when things aren’t working the way they should. It will be used to improve Google’s products and services for everyone. Some aggregated information will help partners, such as Android developers, make their apps and products better, too.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
-    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>You can turn this on or off a any time in the Android apps Settings. This doesn’t affect your device’s ability to send the information it needs to receive essential services such as system updates and security.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>This is general information about your device and how you use it (such as battery level, app usage, and network connectivity). The data will be used to improve Google's products and services for everyone. Some aggregated information will help partners, such as Android developers, make their apps and products better, too.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>Turning off this feature doesn't affect your device's ability to send the information needed for essential services such as system updates and security.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH3">&lt;p&gt;</ph>You can control this feature from Settings &gt; Google. Select Usage &#38; diagnostics from the menu.<ph name="END_PARAGRAPH3">&lt;/p&gt;</ph>
   </message>
   <message name="IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE" desc="Message shown in pop up overlay dialog when user clicks learn more backup and restore">
-    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>When you turn on automatic backup, device and app data is periodically saved to a private folder in Google Drive. App data can be any data that an app has saved (based on developer settings), including potentially sensitive data such as contacts, messages, and photos.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
-    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>Backup data will not count towards your Drive storage quota. Large files, or files developers have chosen to exclude from the service will not be backed up.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>Back up to Google Drive. Easily restore your data or switch device at any time. Your backup includes app data.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>App data can be any data that an app has saved (based on developer settings), including potentially sensitive data such as contacts, messages, and photos.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH3">&lt;p&gt;</ph>Backup data will not count toward your Drive storage quota.<ph name="END_PARAGRAPH3">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH4">&lt;p&gt;</ph>You can turn this service off in Settings.<ph name="END_PARAGRAPH4">&lt;/p&gt;</ph>
   </message>
   <message name="IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES" desc="Message shown in pop up overlay dialog when user clicks learn more location services">
-    <ph name="BEGIN_PARAGRAPH">&lt;p&gt;</ph>Google’s location service uses sources like Wi-Fi to help estimate your device’s location faster and more accurately. When you turn on Google’s location services, your device enters a mode that uses Wi-Fi to provide location information. You can turn this off in location settings anytime.<ph name="END_PARAGRAPH">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>Help apps find location. Use Google’s location service to help improve location for apps. Google may collect location data periodically and use this data in an anonymous way to improve location accuracy and location-based services.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>Google's location service uses sources like Wi-Fi, mobile networks, and sensors to help estimate your device’s location. This service is active when your device’s Location setting is on.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH3">&lt;p&gt;</ph>You can turn off Location by turning off the main Location setting on your device. You can also turn off the use of Wi-Fi, mobile networks, and sensors for location in location settings.<ph name="END_PARAGRAPH3">&lt;/p&gt;</ph>
+  </message>
+  <message name="IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE" desc="Message shown in pop up overlay dialog when user clicks learn more play auto install services.">
+    To remove apps, go to Settings &gt; Apps or Application manager. Then tap the app you want to uninstall (you may need to swipe right or left to find the app). Then tap Uninstall or Disable.
   </message>
   <message name="IDS_ARC_OPT_IN_PRIVACY_POLICY_LINK" desc="Text of the Link to Goolge Privacy Policy in opt-in dialog">
     Google privacy policy
@@ -4603,7 +4620,7 @@
     Installing the Google Play Store on your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>. This could take a few minutes.
   </message>
   <message name="IDS_ARC_OOBE_TERMS_HEADING" desc="Heading of the Arc Terms OOBE dialog.">
-    Google Play Terms of Service
+    Google Play apps and services
   </message>
   <message name="IDS_ARC_OOBE_TERMS_DESCRIPTION" desc="Description of the Arc Terms OOBE dialog.">
     Use Google Play to install Android apps
@@ -4615,7 +4632,7 @@
     Google Play Terms of Service cannot be loaded. Please retry.
   </message>
   <message name="IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT" desc="Accept button of the Arc Terms OOBE dialog.">
-    Agree
+    I Agree
   </message>
   <message name="IDS_ARC_OOBE_TERMS_BUTTON_SKIP" desc="Skip button of the Arc Terms OOBE dialog.">
     Skip
@@ -4841,6 +4858,11 @@
     Lock screen notes are automatically saved to <ph name="LOCK_SCREEN_APP_NAME">$1<ex>Lock Screen Enabled App</ex></ph>. Your most recent note will remain on the lock screen.
   </message>
 
+  <!-- Launcher Searchable Chrome OS apps -->
+  <message name="IDS_LAUNCHER_SEARCHABLE_APP_KEYBOARD_SHORTCUT_VIEWER" desc="The string can be used to search keyboard shortcut viewer app.">
+    Keyboard Shortcut Helper
+  </message>
+
   <message name="IDS_CROSTINI_INSTALLER_TITLE" desc="Title of the Crostini installer, a dialog for installing the 'Terminal'.">
     <ph name="APP_NAME">$1<ex>Terminal</ex></ph> for <ph name="DEVICE_TYPE">$2<ex>Chromebook</ex></ph>
   </message>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index f048d8a..add4fc40 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5658,6 +5658,9 @@
       <message name="IDS_GENERIC_USER_AVATAR_LABEL" desc="Label shown for the user that is currently active, when there are multiple user accounts.">
         Current user
       </message>
+      <message name="IDS_INCOGNITO_AVATAR_BUTTON_TOOLTIP" desc="Tooltip for the inactive avatar button when the user is in incognito mode.">
+        You're incognito
+      </message>
       <message name="IDS_LEGACY_SUPERVISED_USER_NEW_AVATAR_LABEL" desc="Label shown in the new avatar menu for a supervised user.">
         <ph name="PROFILE_DISPLAY_NAME">$1<ex>Markus</ex></ph> (Supervised)
       </message>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index b85b5a8..066d0d7 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -167,6 +167,12 @@
     <message name="IDS_SETTINGS_OPTIONS_IN_MENU_LABEL" desc="Label for checkbox which enables showing accessibility options in the system menu.">
       Always show accessibility options in the system menu
     </message>
+    <message name="IDS_SETTINGS_MANAGE_TTS_SETTINGS" desc="Link to manage text-to-speech settings">
+      Text-to-Speech voice settings
+    </message>
+    <message name="IDS_SETTINGS_TTS_LINK_DESCRIPTION" desc="Description of link to manage text-to-speech settings">
+      Select and customize text-to-speech voices
+    </message>
     <message name="IDS_SETTINGS_LARGE_MOUSE_CURSOR_LABEL" desc="Label for checkbox which enables showing a larger mouse cursor than normal.">
       Show large mouse cursor
     </message>
@@ -338,6 +344,24 @@
     <message name="IDS_SETTINGS_ACCESSIBILITY_TEXT_TO_SPEECH_HEADING" desc="In the settings tab, the heading for accessibility features that enable the computer to speak text from the computer screen.">
       Text-to-Speech
     </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_VOICES" desc="Heading describing a collection of listboxes on the text-to-speech settings page. Each listbox is associated with a language, and contains all possible voices for that language">
+      Preferred Voices
+    </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_PROPERTIES" desc="Heading describing a collection of input controls for a section of text-to-speech settings on speech properties, including speech pitch and rate">
+      Speech Properties
+    </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_RATE" desc="The rate (speed) of speech in text-to-speech settings.">
+      Rate
+    </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_PITCH" desc="The pitch of speech in text-to-speech settings.">
+      Pitch
+    </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_ENGINES" desc="Heading for a section of text-to-speech settings to do per-engine settings">
+      Speech Engines
+    </message>
+    <message name="IDS_SETTINGS_TEXT_TO_SPEECH_INSTALL_ENGINES" desc="A link to install additional text-to-speech engines">
+      Install additional speech engines
+    </message>
   </if>
 
   <if expr="chromeos">
@@ -507,6 +531,9 @@
   <message name="IDS_SETTINGS_NOT_VALID_WEB_ADDRESS" desc="Text indicating that the Web address entered by the user is invalid." >
     Not a valid web address
   </message>
+  <message name="IDS_SETTINGS_RETRY" desc="The label text of the retry button because there is an error.">
+   Retry
+  </message>
 
   <!-- Passwords and Autofill Page -->
   <message name="IDS_SETTINGS_PASSWORDS_AND_AUTOFILL_PAGE_TITLE" desc="Name of the settings page which allows managing passwords and autofill settings.">
@@ -1347,9 +1374,6 @@
     <message name="IDS_SETTINGS_EASY_UNLOCK_TURN_OFF_ERROR_MESSAGE" desc="The text on the Easy unlock turn off dialog when Easy unlock could not be turned off because there is a server error.">
       Smart Lock is currently unavailable. Please try again later.
     </message>
-    <message name="IDS_SETTINGS_EASY_UNLOCK_TURN_OFF_RETRY" desc="The label text of the retry button on the Easy unlock turn off dialog when Easy unlock could not be turned off because there is a server error.">
-      Retry
-    </message>
     <message name="IDS_SETTINGS_EASY_UNLOCK_ALLOW_SIGN_IN_LABEL" desc="The label text on the Easy unlock settings page for the radio box to allow the EasyUnlock on the sign-in screen (in addition to the lock screen).">
       Use Smart Lock to sign in to your account
     </message>
@@ -2185,6 +2209,12 @@
     <message name="IDS_SETTINGS_LANGUAGES_SPELL_CHECK_DISABLED" desc="Text that is shown when spell checking is disabled by policy.">
       Disabled
     </message>
+    <message name="IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED" desc="Error message when spell dictionary download fails.">
+      Spell check dictionary download failed.
+    </message>
+    <message name="IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED_HELP" desc="Error message when spell dictionary download fails more than once possibly due to a network policy or configuration.">
+      Please check with your network administrator to make sure that the firewall is not blocking downloads from Google servers.
+    </message>
   </if>
 
   <!-- Privacy Page -->
@@ -3673,6 +3703,9 @@
     <message name="IDS_SETTINGS_DISPLAY_ZOOM_TITLE" desc="In Device Settings > Displays, the title for the section for changing the display's zoom.">
       Display Size
     </message>
+    <message name="IDS_SETTINGS_DISPLAY_ZOOM_SUBLABEL" desc="In Device Settings > Displays, the text describing the display's zoom.">
+      Make items on your screen smaller or larger
+    </message>
     <message name="IDS_SETTINGS_DISPLAY_ZOOM_VALUE" desc="The currently selected display zoom percentage.">
       <ph name="DISPLAY_ZOOM">$1<ex>120</ex>%</ph>
     </message>
diff --git a/chrome/app/theme/chrome_unscaled_resources.grd b/chrome/app/theme/chrome_unscaled_resources.grd
index 383d4821..9e9c69c5 100644
--- a/chrome/app/theme/chrome_unscaled_resources.grd
+++ b/chrome/app/theme/chrome_unscaled_resources.grd
@@ -111,6 +111,9 @@
         <include name="IDR_APPS_FOLDER_OVERLAY_128" file="mac/apps_folder_overlay_128.png" type="BINDATA" />
         <include name="IDR_APPS_FOLDER_OVERLAY_512" file="mac/apps_folder_overlay_512.png" type="BINDATA" />
       </if>
+      <if expr="chromeos">
+        <include name="IDR_KEYBOARD_SHORTCUT_VIEWER_LOGO_192" file="cros/keyboard_shortcut_viewer_logo_192.png" type="BINDATA" />
+      </if>
     </includes>
   </release>
 </grit>
diff --git a/chrome/app/theme/cros/keyboard_shortcut_viewer_logo_192.png b/chrome/app/theme/cros/keyboard_shortcut_viewer_logo_192.png
new file mode 100644
index 0000000..d38af0c
--- /dev/null
+++ b/chrome/app/theme/cros/keyboard_shortcut_viewer_logo_192.png
Binary files differ
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 1db2124e..94b6a40 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4343,6 +4343,7 @@
       "../android/java/src/org/chromium/chrome/browser/offlinepages/CctOfflinePageModelObserver.java",
       "../android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java",
       "../android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePagesDownloadManagerBridge.java",
+      "../android/java/src/org/chromium/chrome/browser/offlinepages/PublishPageCallback.java",
       "../android/java/src/org/chromium/chrome/browser/offlinepages/SavePageRequest.java",
       "../android/java/src/org/chromium/chrome/browser/offlinepages/downloads/OfflinePageDownloadBridge.java",
       "../android/java/src/org/chromium/chrome/browser/offlinepages/downloads/OfflinePageNotificationBridge.java",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index c450a8a1..83457af 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -175,6 +175,7 @@
   "+third_party/WebKit/public/platform/modules/payments/payment_request.mojom.h",
   "+third_party/WebKit/public/platform/modules/permissions/permission_status.mojom.h",
   "+third_party/WebKit/public/platform/modules/presentation/presentation.mojom.h",
+  "+third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.h",
   "+third_party/WebKit/public/platform/modules/webshare/webshare.mojom.h",
   "+third_party/WebKit/public/platform/oom_intervention.mojom.h",
   "+third_party/WebKit/public/platform/site_engagement.mojom.h",
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 5fddcfc..d3bd004 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -57,6 +57,7 @@
     &features::kSiteNotificationChannels,
     &features::kSimplifiedFullscreenUI,
     &features::kSoundContentSetting,
+    &features::kWebAuth,
     &features::kWebPayments,
     &feed::kInterestFeedContentSuggestions,
     &kAdjustWebApkInstallationSpace,
diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_android.cc
index c48af47..02fa6de 100644
--- a/chrome/browser/android/tab_android.cc
+++ b/chrome/browser/android/tab_android.cc
@@ -455,6 +455,7 @@
   }
   content_layer_->InsertChild(web_contents_->GetNativeView()->GetLayer(), 0);
 
+  // Shows a warning notification for dangerous flags in about:flags.
   chrome::ShowBadFlagsPrompt(web_contents());
 }
 
@@ -975,6 +976,14 @@
                                      j_publisher_url);
 }
 
+bool TabAndroid::AreRendererInputEventsIgnored(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  content::RenderProcessHost* render_process_host =
+      web_contents()->GetMainFrame()->GetProcess();
+  return render_process_host->IgnoreInputEvents();
+}
+
 void TabAndroid::ShowMediaDownloadInProductHelp(
     const gfx::Rect& rect_in_frame) {
   DCHECK(web_contents_);
diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h
index d854b2e..c53cdbea 100644
--- a/chrome/browser/android/tab_android.h
+++ b/chrome/browser/android/tab_android.h
@@ -303,6 +303,10 @@
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
 
+  bool AreRendererInputEventsIgnored(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+
  private:
   class MediaDownloadInProductHelp;
 
diff --git a/chrome/browser/android/vr/vr_shell_gl.cc b/chrome/browser/android/vr/vr_shell_gl.cc
index 68a727c..4d71789 100644
--- a/chrome/browser/android/vr/vr_shell_gl.cc
+++ b/chrome/browser/android/vr/vr_shell_gl.cc
@@ -418,6 +418,7 @@
     return;
   }
   webvr_frame_processing_ = true;
+  OnNewWebVRFrame();
 
   webvr_time_js_submit_[frame_index % kPoseRingBufferSize] =
       base::TimeTicks::Now();
@@ -569,7 +570,6 @@
   TRACE_EVENT1("gpu", "VrShellGl::OnWebVRFrameAvailable", "frame", frame_index);
   pending_frames_.pop();
 
-  OnNewWebVRFrame();
   DrawFrame(frame_index, base::TimeTicks::Now());
 }
 
@@ -979,9 +979,16 @@
 
 void VrShellGl::DrawFrame(int16_t frame_index, base::TimeTicks current_time) {
   TRACE_EVENT1("gpu", "VrShellGl::DrawFrame", "frame", frame_index);
-  if (frame_index < 0 && !webvr_delayed_gvr_submit_.IsCancelled()) {
-    // We've exited WebVR, but the last submit to GVR didn't complete.
-    // Cancel the scheduled submit and reuse the frame.
+  if (!webvr_delayed_gvr_submit_.IsCancelled()) {
+    // The last submit to GVR didn't complete, we have an acquired frame. This
+    // is normal when exiting WebVR, in that case we just want to reuse the
+    // frame. It's not supposed to happen during WebVR presentation.
+    if (frame_index >= 0) {
+      // This is a WebVR frame from OnWebVRFrameAvailable which isn't supposed
+      // to be delivered while the previous frame is still processing. Drop the
+      // previous work to avoid errors and reuse the acquired frame.
+      DLOG(WARNING) << "Unexpected WebVR DrawFrame during acquired frame";
+    }
     webvr_delayed_gvr_submit_.Cancel();
     DrawIntoAcquiredFrame(frame_index, current_time);
     return;
@@ -1341,12 +1348,17 @@
       base::TimeTicks now = base::TimeTicks::Now();
       webvr_render_time_.AddSample(now - js_submit_time);
     }
-  }
 
-  webvr_frame_processing_ = false;
-  // If we have a waiting submit that arrived while processing this one, handle
-  // it now.
-  WebVrTryDeferredSubmit();
+    // Only mark processing as complete if this was a WebVR frame.
+    // We shouldn't be getting UI frames while ShouldDrawWebVr() is true,
+    // but the logic is a bit complicated.
+    if (frame_index >= 0) {
+      webvr_frame_processing_ = false;
+      // If we have a waiting submit that arrived while processing this one,
+      // handle it now.
+      WebVrTryDeferredSubmit();
+    }
+  }
 
   // After saving the timestamp, fps will be available via GetFPS().
   // TODO(vollick): enable rendering of this framerate in a HUD.
@@ -1415,8 +1427,13 @@
   if (cardboard_)
     browser_->ToggleCardboardGamepad(enabled);
 
-  if (!web_vr_mode_)
+  if (!web_vr_mode_) {
     ClosePresentationBindings();
+    // Ensure that re-entering VR later gets a fresh start by clearing out the
+    // current session's animating and processing frame state.
+    webvr_deferred_mojo_submit_.Reset();
+    webvr_frame_processing_ = false;
+  }
 }
 
 void VrShellGl::ContentBoundsChanged(int width, int height) {
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 46db225..c95b330 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -1054,6 +1054,23 @@
   EXPECT_FALSE(guest->WasRecentlyAudible());
 }
 
+IN_PROC_BROWSER_TEST_P(WebViewTest, WebViewRespectsInsets) {
+  LoadAppWithGuest("web_view/simple");
+
+  content::WebContents* guest = GetGuestWebContents();
+  content::RenderWidgetHostView* guest_host_view =
+      guest->GetRenderWidgetHostView();
+
+  gfx::Insets insets(0, 0, 100, 0);
+  gfx::Rect expected(guest_host_view->GetVisibleViewportSize());
+  expected.Inset(insets);
+
+  guest_host_view->SetInsets(gfx::Insets(0, 0, 100, 0));
+
+  gfx::Size size_after = guest_host_view->GetVisibleViewportSize();
+  EXPECT_EQ(expected.size(), size_after);
+}
+
 IN_PROC_BROWSER_TEST_P(WebViewTest, AudioMutesWhileAttached) {
   LoadAppWithGuest("web_view/simple");
 
diff --git a/chrome/browser/browser_process_platform_part_chromeos.cc b/chrome/browser/browser_process_platform_part_chromeos.cc
index 0ebbaef..715c7f30 100644
--- a/chrome/browser/browser_process_platform_part_chromeos.cc
+++ b/chrome/browser/browser_process_platform_part_chromeos.cc
@@ -56,7 +56,7 @@
   DCHECK(!automatic_reboot_manager_);
 
   automatic_reboot_manager_.reset(new chromeos::system::AutomaticRebootManager(
-      std::unique_ptr<base::TickClock>(new base::DefaultTickClock)));
+      base::DefaultTickClock::GetInstance()));
 }
 
 void BrowserProcessPlatformPart::ShutdownAutomaticRebootManager() {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 45f438e..dd192bf 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -319,6 +319,7 @@
 #include "services/proxy_resolver/proxy_resolver_service.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/WebKit/public/platform/modules/payments/payment_request.mojom.h"
+#include "third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.h"
 #include "ui/base/resource/resource_bundle_android.h"
 #include "ui/base/ui_base_paths.h"
 #elif defined(OS_POSIX)
@@ -3738,6 +3739,8 @@
       &ForwardToJavaFrameRegistry<blink::mojom::InstalledAppProvider>));
   frame_interfaces_parameterized_->AddInterface(
       base::Bind(&ForwardToJavaFrameRegistry<payments::mojom::PaymentRequest>));
+  frame_interfaces_parameterized_->AddInterface(
+      base::Bind(&ForwardToJavaFrameRegistry<webauth::mojom::Authenticator>));
 #else
   if (base::FeatureList::IsEnabled(features::kWebPayments)) {
     frame_interfaces_parameterized_->AddInterface(
diff --git a/chrome/browser/chromeos/arc/arc_support_host.cc b/chrome/browser/chromeos/arc/arc_support_host.cc
index d7996db..3d2ab4b1 100644
--- a/chrome/browser/chromeos/arc/arc_support_host.cc
+++ b/chrome/browser/chromeos/arc/arc_support_host.cc
@@ -516,6 +516,11 @@
   loadtime_data->SetString(
       "textBackupRestore",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE));
+  loadtime_data->SetString("textPaiService",
+                           l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_PAI));
+  loadtime_data->SetString(
+      "textGoogleServiceConfirmation",
+      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_GOOGLE_SERVICE_CONFIRMATION));
   loadtime_data->SetString(
       "textLocationService",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LOCATION_SETTING));
@@ -535,6 +540,9 @@
       "learnMoreLocationServices",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES));
   loadtime_data->SetString(
+      "learnMorePaiService",
+      l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE));
+  loadtime_data->SetString(
       "overlayClose",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_CLOSE));
   loadtime_data->SetString(
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 0585efe..8ce8be12 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -333,7 +333,7 @@
     CrosDBusService::ServiceProviderList service_providers;
     CrosDBusService::ServiceProviderList display_service_providers;
 
-    if (GetAshConfig() == ash::Config::CLASSIC) {
+    if (GetAshConfig() != ash::Config::MASH) {
       // TODO(lannm): This will eventually be served by mus-ws.
       display_service_providers.push_back(
           std::make_unique<DisplayPowerServiceProvider>(
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
index a03922e9..b724a34 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
@@ -120,10 +120,9 @@
   ready_callback_ = ready_callback;
 }
 
-void StateController::SetTickClockForTesting(
-    std::unique_ptr<base::TickClock> clock) {
+void StateController::SetTickClockForTesting(base::TickClock* clock) {
   DCHECK(!tick_clock_);
-  tick_clock_ = std::move(clock);
+  tick_clock_ = clock;
 }
 
 void StateController::SetAppManagerForTesting(
@@ -140,7 +139,7 @@
 
 void StateController::Initialize() {
   if (!tick_clock_)
-    tick_clock_ = std::make_unique<base::DefaultTickClock>();
+    tick_clock_ = base::DefaultTickClock::GetInstance();
 
   // The tray action ptr might be set previously if the client was being created
   // for testing.
@@ -228,14 +227,13 @@
   // Lock screen profile creator might have been set by a test.
   if (!lock_screen_profile_creator_) {
     lock_screen_profile_creator_ =
-        std::make_unique<LockScreenProfileCreatorImpl>(profile,
-                                                       tick_clock_.get());
+        std::make_unique<LockScreenProfileCreatorImpl>(profile, tick_clock_);
   }
   lock_screen_profile_creator_->Initialize();
 
   // App manager might have been set previously by a test.
   if (!app_manager_)
-    app_manager_ = std::make_unique<AppManagerImpl>(tick_clock_.get());
+    app_manager_ = std::make_unique<AppManagerImpl>(tick_clock_);
   app_manager_->Initialize(profile, lock_screen_profile_creator_.get());
 
   first_app_run_toast_manager_ =
@@ -358,7 +356,7 @@
       base::Bind(&StateController::OnNoteTakingAvailabilityChanged,
                  base::Unretained(this)));
   note_app_window_metrics_ =
-      std::make_unique<AppWindowMetricsTracker>(tick_clock_.get());
+      std::make_unique<AppWindowMetricsTracker>(tick_clock_);
   lock_screen_data_->SetSessionLocked(true);
   OnNoteTakingAvailabilityChanged();
 }
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.h b/chrome/browser/chromeos/lock_screen_apps/state_controller.h
index e210eea..8938cbc 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller.h
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.h
@@ -100,7 +100,7 @@
   // initialized and ready for action.
   void SetReadyCallbackForTesting(const base::Closure& ready_callback);
   // Sets the tick clock to be used in tests.
-  void SetTickClockForTesting(std::unique_ptr<base::TickClock> clock);
+  void SetTickClockForTesting(base::TickClock* clock);
   // Sets test AppManager implementation. Should be called before
   // |SetPrimaryProfile|
   void SetAppManagerForTesting(std::unique_ptr<AppManager> app_manager);
@@ -294,7 +294,7 @@
 
   // The clock used to keep track of time, for example to report app window
   // lifetime metrics.
-  std::unique_ptr<base::TickClock> tick_clock_;
+  base::TickClock* tick_clock_ = nullptr;
 
   base::WeakPtrFactory<StateController> weak_ptr_factory_;
 
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
index 9931e50..e64e6b4 100644
--- a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
+++ b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
@@ -436,15 +436,13 @@
 
     focus_cycler_delegate_ = std::make_unique<TestFocusCyclerDelegate>();
 
-    auto tick_clock = std::make_unique<base::SimpleTestTickClock>();
     // Advance the clock to have non-null value.
-    tick_clock->Advance(base::TimeDelta::FromMilliseconds(1));
-    tick_clock_ = tick_clock.get();
+    tick_clock_.Advance(base::TimeDelta::FromMilliseconds(1));
 
     state_controller_ = std::make_unique<lock_screen_apps::StateController>();
     state_controller_->SetTrayActionPtrForTesting(
         tray_action_.CreateInterfacePtrAndBind());
-    state_controller_->SetTickClockForTesting(std::move(tick_clock));
+    state_controller_->SetTickClockForTesting(&tick_clock_);
     state_controller_->SetLockScreenLockScreenProfileCreatorForTesting(
         std::move(profile_creator));
     state_controller_->SetAppManagerForTesting(std::move(app_manager));
@@ -676,7 +674,7 @@
     return lock_screen_profile_creator_;
   }
 
-  base::SimpleTestTickClock* tick_clock() { return tick_clock_; }
+  base::SimpleTestTickClock* tick_clock() { return &tick_clock_; }
 
  protected:
   // Should be set by tests that excercise the logic for the first lock screen
@@ -719,7 +717,7 @@
   std::unique_ptr<TestAppWindow> app_window_;
   scoped_refptr<const extensions::Extension> app_;
 
-  base::SimpleTestTickClock* tick_clock_;
+  base::SimpleTestTickClock tick_clock_;
 
   DISALLOW_COPY_AND_ASSIGN(LockScreenAppStateTest);
 };
diff --git a/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.cc b/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.cc
index 82a3418..86422596 100644
--- a/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.cc
+++ b/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.cc
@@ -11,6 +11,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/time/clock.h"
+#include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/login/reauth_stats.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -84,7 +85,8 @@
 
 SAMLOfflineSigninLimiter::SAMLOfflineSigninLimiter(Profile* profile,
                                                    base::Clock* clock)
-    : profile_(profile), clock_(clock ? clock : &default_clock_) {}
+    : profile_(profile),
+      clock_(clock ? clock : base::DefaultClock::GetInstance()) {}
 
 SAMLOfflineSigninLimiter::~SAMLOfflineSigninLimiter() {}
 
diff --git a/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h b/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h
index 6bbca30..a464384 100644
--- a/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h
+++ b/chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chromeos/login/auth/user_context.h"
@@ -47,7 +46,7 @@
   friend class SAMLOfflineSigninLimiterTest;
 
   // |profile| and |clock| must remain valid until Shutdown() is called. If
-  // |clock| is NULL, the |default_clock_| will be used.
+  // |clock| is NULL, the shared base::DefaultClock instance will be used.
   SAMLOfflineSigninLimiter(Profile* profile, base::Clock* clock);
   ~SAMLOfflineSigninLimiter() override;
 
@@ -60,8 +59,6 @@
   // to use online authentication against GAIA.
   void ForceOnlineLogin();
 
-  base::DefaultClock default_clock_;
-
   Profile* profile_;
   base::Clock* clock_;
 
diff --git a/chrome/browser/chromeos/power/ml/idle_event_notifier.cc b/chrome/browser/chromeos/power/ml/idle_event_notifier.cc
index 848aaaee8..9be00ce1 100644
--- a/chrome/browser/chromeos/power/ml/idle_event_notifier.cc
+++ b/chrome/browser/chromeos/power/ml/idle_event_notifier.cc
@@ -61,7 +61,7 @@
     PowerManagerClient* power_manager_client,
     ui::UserActivityDetector* detector,
     viz::mojom::VideoDetectorObserverRequest request)
-    : clock_(std::make_unique<base::DefaultClock>()),
+    : clock_(base::DefaultClock::GetInstance()),
       boot_clock_(std::make_unique<RealBootClock>()),
       power_manager_client_observer_(this),
       user_activity_observer_(this),
@@ -86,10 +86,10 @@
 
 void IdleEventNotifier::SetClockForTesting(
     scoped_refptr<base::SequencedTaskRunner> task_runner,
-    std::unique_ptr<base::Clock> test_clock,
+    base::Clock* test_clock,
     std::unique_ptr<BootClock> test_boot_clock) {
   idle_delay_timer_.SetTaskRunner(task_runner);
-  clock_ = std::move(test_clock);
+  clock_ = test_clock;
   boot_clock_ = std::move(test_boot_clock);
 }
 
diff --git a/chrome/browser/chromeos/power/ml/idle_event_notifier.h b/chrome/browser/chromeos/power/ml/idle_event_notifier.h
index 97786ee..c1e99e6 100644
--- a/chrome/browser/chromeos/power/ml/idle_event_notifier.h
+++ b/chrome/browser/chromeos/power/ml/idle_event_notifier.h
@@ -109,7 +109,7 @@
 
   // Set test clock so that we can check activity time.
   void SetClockForTesting(scoped_refptr<base::SequencedTaskRunner> task_runner,
-                          std::unique_ptr<base::Clock> test_clock,
+                          base::Clock* test_clock,
                           std::unique_ptr<BootClock> test_boot_clock);
 
   // Adds or removes an observer.
@@ -162,7 +162,7 @@
   void ResetTimestampsPerIdleEvent();
 
   // It is base::DefaultClock, but will be set to a mock clock for tests.
-  std::unique_ptr<base::Clock> clock_;
+  base::Clock* clock_;
 
   // It is RealBootClock, but will be set to FakeBootClock for tests.
   std::unique_ptr<BootClock> boot_clock_;
diff --git a/chrome/browser/chromeos/power/ml/idle_event_notifier_unittest.cc b/chrome/browser/chromeos/power/ml/idle_event_notifier_unittest.cc
index 1f7407cb..c3f8530 100644
--- a/chrome/browser/chromeos/power/ml/idle_event_notifier_unittest.cc
+++ b/chrome/browser/chromeos/power/ml/idle_event_notifier_unittest.cc
@@ -87,7 +87,7 @@
     idle_event_notifier_ = std::make_unique<IdleEventNotifier>(
         &power_client_, &user_activity_detector_, mojo::MakeRequest(&observer));
     idle_event_notifier_->SetClockForTesting(
-        task_runner_, task_runner_->DeprecatedGetMockClock(),
+        task_runner_, task_runner_->GetMockClock(),
         std::make_unique<FakeBootClock>(task_runner_,
                                         base::TimeDelta::FromSeconds(10)));
     idle_event_notifier_->AddObserver(&test_observer_);
diff --git a/chrome/browser/chromeos/system/automatic_reboot_manager.cc b/chrome/browser/chromeos/system/automatic_reboot_manager.cc
index 6dcc50b5..8a3d6ee 100644
--- a/chrome/browser/chromeos/system/automatic_reboot_manager.cc
+++ b/chrome/browser/chromeos/system/automatic_reboot_manager.cc
@@ -141,11 +141,10 @@
   has_update_reboot_needed_time = true;
 }
 
-AutomaticRebootManager::AutomaticRebootManager(
-    std::unique_ptr<base::TickClock> clock)
+AutomaticRebootManager::AutomaticRebootManager(base::TickClock* clock)
     : initialized_(base::WaitableEvent::ResetPolicy::MANUAL,
                    base::WaitableEvent::InitialState::NOT_SIGNALED),
-      clock_(std::move(clock)),
+      clock_(clock),
       have_boot_time_(false),
       have_update_reboot_needed_time_(false),
       reboot_reason_(AutomaticRebootManagerObserver::REBOOT_REASON_UNKNOWN),
diff --git a/chrome/browser/chromeos/system/automatic_reboot_manager.h b/chrome/browser/chromeos/system/automatic_reboot_manager.h
index e9ad3015a..7316a198 100644
--- a/chrome/browser/chromeos/system/automatic_reboot_manager.h
+++ b/chrome/browser/chromeos/system/automatic_reboot_manager.h
@@ -91,7 +91,7 @@
     base::TimeTicks update_reboot_needed_time;
   };
 
-  explicit AutomaticRebootManager(std::unique_ptr<base::TickClock> clock);
+  explicit AutomaticRebootManager(base::TickClock* clock);
   ~AutomaticRebootManager() override;
 
   AutomaticRebootManagerObserver::Reason reboot_reason() const {
@@ -149,7 +149,7 @@
   base::WaitableEvent initialized_;
 
   // A clock that can be mocked in tests to fast-forward time.
-  std::unique_ptr<base::TickClock> clock_;
+  base::TickClock* const clock_;
 
   PrefChangeRegistrar local_state_registrar_;
 
diff --git a/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc b/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
index 986142d..59bde8d 100644
--- a/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
+++ b/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
@@ -456,7 +456,7 @@
 void AutomaticRebootManagerBasicTest::CreateAutomaticRebootManager(
     bool expect_reboot) {
   automatic_reboot_manager_.reset(
-      new AutomaticRebootManager(task_runner_->DeprecatedGetMockTickClock()));
+      new AutomaticRebootManager(task_runner_->GetMockTickClock()));
   automatic_reboot_manager_observer_.Init(automatic_reboot_manager_.get());
   task_runner_->RunUntilIdle();
   EXPECT_EQ(expect_reboot ? 1 : 0,
diff --git a/chrome/browser/intranet_redirect_detector.cc b/chrome/browser/intranet_redirect_detector.cc
index f5ec4265..31a7f7f 100644
--- a/chrome/browser/intranet_redirect_detector.cc
+++ b/chrome/browser/intranet_redirect_detector.cc
@@ -15,17 +15,20 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "net/url_request/url_request_status.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
 
 IntranetRedirectDetector::IntranetRedirectDetector()
     : redirect_origin_(g_browser_process->local_state()->GetString(
@@ -69,14 +72,14 @@
   in_sleep_ = false;
 
   // If another fetch operation is still running, cancel it.
-  fetchers_.clear();
+  simple_loaders_.clear();
   resulting_origins_.clear();
 
   const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
   if (cmd_line->HasSwitch(switches::kDisableBackgroundNetworking))
     return;
 
-  DCHECK(fetchers_.empty() && resulting_origins_.empty());
+  DCHECK(simple_loaders_.empty() && resulting_origins_.empty());
 
   // Create traffic annotation tag.
   net::NetworkTrafficAnnotationTag traffic_annotation =
@@ -101,7 +104,7 @@
               "Not implemented, considered not useful."
         })");
 
-  // Start three fetchers on random hostnames.
+  // Start three loaders on random hostnames.
   for (size_t i = 0; i < 3; ++i) {
     std::string url_string("http://");
     // We generate a random hostname with between 7 and 15 characters.
@@ -109,41 +112,45 @@
     for (int j = 0; j < num_chars; ++j)
       url_string += ('a' + base::RandInt(0, 'z' - 'a'));
     GURL random_url(url_string + '/');
-    std::unique_ptr<net::URLFetcher> fetcher = net::URLFetcher::Create(
-        random_url, net::URLFetcher::HEAD, this, traffic_annotation);
+
+    auto resource_request = std::make_unique<network::ResourceRequest>();
+    resource_request->url = random_url;
+    resource_request->method = "HEAD";
     // We don't want these fetches to affect existing state in the profile.
-    fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE |
-                          net::LOAD_DO_NOT_SAVE_COOKIES |
-                          net::LOAD_DO_NOT_SEND_COOKIES |
-                          net::LOAD_DO_NOT_SEND_AUTH_DATA);
-    fetcher->SetRequestContext(g_browser_process->system_request_context());
-    fetcher->Start();
-    net::URLFetcher* fetcher_ptr = fetcher.get();
-    fetchers_[fetcher_ptr] = std::move(fetcher);
+    resource_request->load_flags =
+        net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES |
+        net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA;
+    network::mojom::URLLoaderFactory* loader_factory =
+        g_browser_process->system_network_context_manager()
+            ->GetURLLoaderFactory();
+    std::unique_ptr<network::SimpleURLLoader> simple_loader =
+        network::SimpleURLLoader::Create(std::move(resource_request),
+                                         traffic_annotation);
+    network::SimpleURLLoader* simple_loader_ptr = simple_loader.get();
+    simple_loader->DownloadToString(
+        loader_factory,
+        base::BindOnce(&IntranetRedirectDetector::OnSimpleLoaderComplete,
+                       base::Unretained(this), simple_loader_ptr),
+        /*max_body_size=*/1);
+    simple_loaders_[simple_loader_ptr] = std::move(simple_loader);
   }
 }
 
-void IntranetRedirectDetector::OnURLFetchComplete(
-    const net::URLFetcher* source) {
-  // Delete the fetcher on this function's exit.
-  auto it = fetchers_.find(const_cast<net::URLFetcher*>(source));
-  DCHECK(it != fetchers_.end());
-  std::unique_ptr<net::URLFetcher> fetcher = std::move(it->second);
-  fetchers_.erase(it);
+void IntranetRedirectDetector::OnSimpleLoaderComplete(
+    network::SimpleURLLoader* source,
+    std::unique_ptr<std::string> response_body) {
+  // Delete the loader on this function's exit.
+  auto it = simple_loaders_.find(source);
+  DCHECK(it != simple_loaders_.end());
+  std::unique_ptr<network::SimpleURLLoader> simple_loader =
+      std::move(it->second);
+  simple_loaders_.erase(it);
 
-  // If any two fetches result in the same domain/host, we set the redirect
+  // If any two loaders result in the same domain/host, we set the redirect
   // origin to that; otherwise we set it to nothing.
-  if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
-    if ((resulting_origins_.empty()) ||
-        ((resulting_origins_.size() == 1) &&
-         resulting_origins_.front().is_valid())) {
-      resulting_origins_.push_back(GURL());
-      return;
-    }
-    redirect_origin_ = GURL();
-  } else {
-    DCHECK(source->GetURL().is_valid());
-    GURL origin(source->GetURL().GetOrigin());
+  if (response_body) {
+    DCHECK(source->GetFinalURL().is_valid());
+    GURL origin(source->GetFinalURL().GetOrigin());
     if (resulting_origins_.empty()) {
       resulting_origins_.push_back(origin);
       return;
@@ -153,10 +160,10 @@
         origin,
         net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) {
       redirect_origin_ = origin;
-      if (!fetchers_.empty()) {
-        // Cancel remaining fetch, we don't need it.
-        DCHECK(fetchers_.size() == 1);
-        fetchers_.clear();
+      if (!simple_loaders_.empty()) {
+        // Cancel remaining loader, we don't need it.
+        DCHECK(simple_loaders_.size() == 1);
+        simple_loaders_.clear();
       }
     }
     if (resulting_origins_.size() == 1) {
@@ -170,6 +177,13 @@
             origin,
             net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
     redirect_origin_ = same_domain_or_host ? origin : GURL();
+  } else {
+    if (resulting_origins_.empty() || (resulting_origins_.size() == 1 &&
+                                       resulting_origins_.front().is_valid())) {
+      resulting_origins_.push_back(GURL());
+      return;
+    }
+    redirect_origin_ = GURL();
   }
 
   g_browser_process->local_state()->SetString(
diff --git a/chrome/browser/intranet_redirect_detector.h b/chrome/browser/intranet_redirect_detector.h
index a6dfcd3..34941726 100644
--- a/chrome/browser/intranet_redirect_detector.h
+++ b/chrome/browser/intranet_redirect_detector.h
@@ -16,9 +16,12 @@
 #include "content/public/browser/notification_registrar.h"
 #include "net/base/network_change_notifier.h"
 #include "net/dns/host_resolver_proc.h"
-#include "net/url_request/url_fetcher_delegate.h"
 #include "url/gurl.h"
 
+namespace network {
+class SimpleURLLoader;
+}
+
 class PrefRegistrySimple;
 
 // This object is responsible for determining whether the user is on a network
@@ -38,8 +41,7 @@
 // return a value at all times (even during startup or in unittest mode).  If no
 // redirection is in place, the returned GURL will be empty.
 class IntranetRedirectDetector
-    : public net::URLFetcherDelegate,
-      public net::NetworkChangeNotifier::NetworkChangeObserver {
+    : public net::NetworkChangeNotifier::NetworkChangeObserver {
  public:
   // Only the main browser process loop should call this, when setting up
   // g_browser_process->intranet_redirect_detector_.  No code other than the
@@ -61,15 +63,17 @@
   // switch sleep has finished.  Runs any pending fetch.
   void FinishSleep();
 
-  // net::URLFetcherDelegate
-  void OnURLFetchComplete(const net::URLFetcher* source) override;
+  // Invoked from SimpleURLLoader after download is complete.
+  void OnSimpleLoaderComplete(network::SimpleURLLoader* source,
+                              std::unique_ptr<std::string> response_body);
 
   // NetworkChangeNotifier::NetworkChangeObserver
   void OnNetworkChanged(
       net::NetworkChangeNotifier::ConnectionType type) override;
 
   GURL redirect_origin_;
-  std::map<net::URLFetcher*, std::unique_ptr<net::URLFetcher>> fetchers_;
+  std::map<network::SimpleURLLoader*, std::unique_ptr<network::SimpleURLLoader>>
+      simple_loaders_;
   std::vector<GURL> resulting_origins_;
   bool in_sleep_;  // True if we're in the seven-second "no fetching" period
                    // that begins at browser start, or the one-second "no
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 5e40833a..0502fb9 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -164,6 +164,13 @@
 // third_party/crashpad/crashpad/handler/handler_main.cc.
 const char kCrashpadHistogramAllocatorName[] = "CrashpadMetrics";
 
+#if defined(OS_WIN)
+// Must be kept in sync with the allocator name in
+// notification_helper/notification_helper.cc.
+constexpr char kNotificationHelperHistogramAllocatorName[] =
+    "NotificationHelperMetrics";
+#endif
+
 #if defined(OS_WIN) || defined(OS_MACOSX)
 // The stream type assigned to the minidump stream that holds the serialized
 // system profile proto.
@@ -187,6 +194,9 @@
 #if defined(OS_WIN)
   metrics::FileMetricsProvider::RegisterPrefs(
       registry, installer::kSetupHistogramAllocatorName);
+
+  metrics::FileMetricsProvider::RegisterPrefs(
+      registry, kNotificationHelperHistogramAllocatorName);
 #endif
 }
 
@@ -285,6 +295,34 @@
       metrics::FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
       metrics::FileMetricsProvider::ASSOCIATE_CURRENT_RUN,
       installer::kSetupHistogramAllocatorName));
+
+  // When metrics reporting is enabled, register the notification_helper metrics
+  // files; otherwise delete any existing files in order to preserve user
+  // privacy.
+  // TODO(chengx): Investigate if there is a need to update
+  // RegisterOrRemovePreviousRunMetricsFile and apply it here to remove
+  // potential duplicate code.
+  if (!user_data_dir.empty()) {
+    base::FilePath notification_helper_metrics_upload_dir =
+        user_data_dir.AppendASCII(kNotificationHelperHistogramAllocatorName);
+
+    if (metrics_reporting_enabled) {
+      file_metrics_provider->RegisterSource(
+          metrics::FileMetricsProvider::Params(
+              notification_helper_metrics_upload_dir,
+              metrics::FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+              metrics::FileMetricsProvider::ASSOCIATE_CURRENT_RUN,
+              kNotificationHelperHistogramAllocatorName));
+    } else {
+      base::PostTaskWithTraits(
+          FROM_HERE,
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+          base::BindOnce(base::IgnoreResult(&base::DeleteFile),
+                         std::move(notification_helper_metrics_upload_dir),
+                         /*recursive=*/true));
+    }
+  }
 #endif
 
   return file_metrics_provider;
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 756c746c..4ac9c8c 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -292,6 +292,26 @@
   base::android::RunCallbackAndroid(j_callback_obj, static_cast<int>(value));
 }
 
+void PublishPageDone(
+    const ScopedJavaGlobalRef<jobject>& j_published_callback_obj,
+    const OfflinePageItem& offline_page) {
+  // Create a java side OfflinePageItem for this offline_page.
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_page =
+      Java_OfflinePageBridge_createOfflinePageItem(
+          env, ConvertUTF8ToJavaString(env, offline_page.url.spec()),
+          offline_page.offline_id,
+          ConvertUTF8ToJavaString(env, offline_page.client_id.name_space),
+          ConvertUTF8ToJavaString(env, offline_page.client_id.id),
+          ConvertUTF16ToJavaString(env, offline_page.title),
+          ConvertUTF8ToJavaString(env, offline_page.file_path.value()),
+          offline_page.file_size, offline_page.creation_time.ToJavaTime(),
+          offline_page.access_count, offline_page.last_access_time.ToJavaTime(),
+          ConvertUTF8ToJavaString(env, offline_page.request_origin));
+
+  base::android::RunCallbackAndroid(j_published_callback_obj, j_page);
+}
+
 }  // namespace
 
 static jboolean JNI_OfflinePageBridge_IsOfflineBookmarksEnabled(
@@ -596,7 +616,7 @@
       content::WebContents::FromJavaWebContents(j_web_contents);
   if (web_contents) {
     save_page_params.url = web_contents->GetLastCommittedURL();
-    archiver.reset(new OfflinePageMHTMLArchiver(web_contents));
+    archiver.reset(new OfflinePageMHTMLArchiver());
   }
 
   save_page_params.client_id.name_space =
@@ -606,7 +626,7 @@
   save_page_params.request_origin = ConvertJavaStringToUTF8(env, j_origin);
 
   offline_page_model_->SavePage(
-      save_page_params, std::move(archiver),
+      save_page_params, std::move(archiver), web_contents,
       base::Bind(&SavePageCallback, j_callback_ref, save_page_params.url));
 }
 
@@ -643,6 +663,38 @@
       params, base::Bind(&SavePageLaterCallback, j_callback_ref));
 }
 
+void OfflinePageBridge::PublishInternalPage(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& j_profile,
+    const jlong j_offline_id,
+    const base::android::JavaParamRef<jstring>& j_title,
+    const base::android::JavaParamRef<jstring>& j_url,
+    const base::android::JavaParamRef<jstring>& j_file_path,
+    const jlong j_file_size,
+    const base::android::JavaParamRef<jobject>& j_published_callback) {
+  ScopedJavaGlobalRef<jobject> j_published_callback_ref;
+  j_published_callback_ref.Reset(env, j_published_callback);
+
+  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+  OfflinePageModel* offline_page_model =
+      OfflinePageModelFactory::GetForBrowserContext(profile);
+  DCHECK(offline_page_model != nullptr);
+  // Since we only need base functionaity of the OfflinePageArchiver, we don't
+  // need to pass a web_contents to the Archiver constructor.
+  std::unique_ptr<OfflinePageArchiver> archiver(new OfflinePageMHTMLArchiver());
+  GURL url = GURL(ConvertJavaStringToUTF8(env, j_url));
+  base::FilePath file_path(ConvertJavaStringToUTF8(env, j_file_path));
+  ClientId client_id;  // Use an empty client id.
+  OfflinePageItem offline_page(url, (int64_t)j_offline_id, client_id, file_path,
+                               j_file_size);
+  offline_page.title = ConvertJavaStringToUTF16(env, j_title);
+
+  offline_page_model->PublishInternalArchive(
+      offline_page, std::move(archiver),
+      base::BindOnce(&PublishPageDone, std::move(j_published_callback_ref)));
+}
+
 ScopedJavaLocalRef<jstring> OfflinePageBridge::GetOfflinePageHeaderForReload(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.h b/chrome/browser/offline_pages/android/offline_page_bridge.h
index fcc9d71..003527f 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.h
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.h
@@ -128,6 +128,17 @@
                      const base::android::JavaParamRef<jstring>& j_origin,
                      jboolean user_requested);
 
+  void PublishInternalPage(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jobject>& j_profile,
+      const jlong j_offline_id,
+      const base::android::JavaParamRef<jstring>& j_title,
+      const base::android::JavaParamRef<jstring>& j_url,
+      const base::android::JavaParamRef<jstring>& j_file_path,
+      const jlong j_size,
+      const base::android::JavaParamRef<jobject>& j_published_callback);
+
   jboolean IsShowingOfflinePreview(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
@@ -254,4 +265,3 @@
 }  // namespace offline_pages
 
 #endif  // CHROME_BROWSER_OFFLINE_PAGES_ANDROID_OFFLINE_PAGE_BRIDGE_H_
-
diff --git a/chrome/browser/offline_pages/background_loader_offliner.cc b/chrome/browser/offline_pages/background_loader_offliner.cc
index 1a1d5ade..4f9691e 100644
--- a/chrome/browser/offline_pages/background_loader_offliner.cc
+++ b/chrome/browser/offline_pages/background_loader_offliner.cc
@@ -495,8 +495,7 @@
     }
   }
 
-  std::unique_ptr<OfflinePageArchiver> archiver(
-      new OfflinePageMHTMLArchiver(web_contents));
+  std::unique_ptr<OfflinePageArchiver> archiver(new OfflinePageMHTMLArchiver());
 
   OfflinePageModel::SavePageParams params;
   params.url = web_contents->GetLastCommittedURL();
@@ -514,7 +513,7 @@
     params.original_url = request.url();
 
   offline_page_model_->SavePage(
-      params, std::move(archiver),
+      params, std::move(archiver), web_contents,
       base::Bind(&BackgroundLoaderOffliner::OnPageSaved,
                  weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/offline_pages/background_loader_offliner_unittest.cc b/chrome/browser/offline_pages/background_loader_offliner_unittest.cc
index 9b55980..d518b7b 100644
--- a/chrome/browser/offline_pages/background_loader_offliner_unittest.cc
+++ b/chrome/browser/offline_pages/background_loader_offliner_unittest.cc
@@ -66,6 +66,7 @@
 
   void SavePage(const SavePageParams& save_page_params,
                 std::unique_ptr<OfflinePageArchiver> archiver,
+                content::WebContents* web_contents,
                 const SavePageCallback& callback) override {
     mock_saving_ = true;
     save_page_callback_ = callback;
diff --git a/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc b/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
index d21eb8d..41aa0d0 100644
--- a/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
+++ b/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
@@ -48,17 +48,8 @@
 }  // namespace
 
 // static
-OfflinePageMHTMLArchiver::OfflinePageMHTMLArchiver(
-    content::WebContents* web_contents)
-    : web_contents_(web_contents),
-      weak_ptr_factory_(this) {
-  DCHECK(web_contents_);
-}
-
 OfflinePageMHTMLArchiver::OfflinePageMHTMLArchiver()
-    : web_contents_(nullptr),
-      weak_ptr_factory_(this) {
-}
+    : weak_ptr_factory_(this) {}
 
 OfflinePageMHTMLArchiver::~OfflinePageMHTMLArchiver() {
 }
@@ -66,6 +57,7 @@
 void OfflinePageMHTMLArchiver::CreateArchive(
     const base::FilePath& archives_dir,
     const CreateArchiveParams& create_archive_params,
+    content::WebContents* web_contents,
     const CreateArchiveCallback& callback) {
   DCHECK(callback_.is_null());
   DCHECK(!callback.is_null());
@@ -73,29 +65,30 @@
 
   // TODO(chili): crbug/710248 These checks should probably be done inside
   // the offliner.
-  if (HasConnectionSecurityError()) {
+  if (HasConnectionSecurityError(web_contents)) {
     ReportFailure(ArchiverResult::ERROR_SECURITY_CERTIFICATE);
     return;
   }
 
   // Don't save chrome error pages.
-  if (GetPageType() == content::PageType::PAGE_TYPE_ERROR) {
+  if (GetPageType(web_contents) == content::PageType::PAGE_TYPE_ERROR) {
     ReportFailure(ArchiverResult::ERROR_ERROR_PAGE);
     return;
   }
 
   // Don't save chrome-injected interstitial info pages
   // i.e. "This site may be dangerous. Are you sure you want to continue?"
-  if (GetPageType() == content::PageType::PAGE_TYPE_INTERSTITIAL) {
+  if (GetPageType(web_contents) == content::PageType::PAGE_TYPE_INTERSTITIAL) {
     ReportFailure(ArchiverResult::ERROR_INTERSTITIAL_PAGE);
     return;
   }
 
-  GenerateMHTML(archives_dir, create_archive_params);
+  GenerateMHTML(archives_dir, web_contents, create_archive_params);
 }
 
 void OfflinePageMHTMLArchiver::GenerateMHTML(
     const base::FilePath& archives_dir,
+    content::WebContents* web_contents,
     const CreateArchiveParams& create_archive_params) {
   if (archives_dir.empty()) {
     DVLOG(1) << "Archive path was empty. Can't create archive.";
@@ -103,20 +96,20 @@
     return;
   }
 
-  if (!web_contents_) {
+  if (!web_contents) {
     DVLOG(1) << "WebContents is missing. Can't create archive.";
     ReportFailure(ArchiverResult::ERROR_CONTENT_UNAVAILABLE);
     return;
   }
 
-  if (!web_contents_->GetRenderViewHost()) {
+  if (!web_contents->GetRenderViewHost()) {
     DVLOG(1) << "RenderViewHost is not created yet. Can't create archive.";
     ReportFailure(ArchiverResult::ERROR_CONTENT_UNAVAILABLE);
     return;
   }
 
-  GURL url(web_contents_->GetLastCommittedURL());
-  base::string16 title(web_contents_->GetTitle());
+  GURL url(web_contents->GetLastCommittedURL());
+  base::string16 title(web_contents->GetTitle());
   base::FilePath file_path(
       archives_dir.Append(base::GenerateGUID())
           .AddExtension(OfflinePageUtils::kMHTMLExtension));
@@ -126,7 +119,7 @@
   params.use_page_problem_detectors =
       create_archive_params.use_page_problem_detectors;
 
-  web_contents_->GenerateMHTML(
+  web_contents->GenerateMHTML(
       params,
       base::BindOnce(&OfflinePageMHTMLArchiver::OnGenerateMHTMLDone,
                      weak_ptr_factory_.GetWeakPtr(), url, file_path, title));
@@ -166,10 +159,11 @@
                  file_path, title, file_size, digest));
 }
 
-bool OfflinePageMHTMLArchiver::HasConnectionSecurityError() {
-  SecurityStateTabHelper::CreateForWebContents(web_contents_);
+bool OfflinePageMHTMLArchiver::HasConnectionSecurityError(
+    content::WebContents* web_contents) {
+  SecurityStateTabHelper::CreateForWebContents(web_contents);
   SecurityStateTabHelper* helper =
-      SecurityStateTabHelper::FromWebContents(web_contents_);
+      SecurityStateTabHelper::FromWebContents(web_contents);
   DCHECK(helper);
   security_state::SecurityInfo security_info;
   helper->GetSecurityInfo(&security_info);
@@ -177,8 +171,9 @@
          security_info.security_level;
 }
 
-content::PageType OfflinePageMHTMLArchiver::GetPageType() {
-  return web_contents_->GetController().GetVisibleEntry()->GetPageType();
+content::PageType OfflinePageMHTMLArchiver::GetPageType(
+    content::WebContents* web_contents) {
+  return web_contents->GetController().GetVisibleEntry()->GetPageType();
 }
 
 void OfflinePageMHTMLArchiver::DeleteFileAndReportFailure(
diff --git a/chrome/browser/offline_pages/offline_page_mhtml_archiver.h b/chrome/browser/offline_pages/offline_page_mhtml_archiver.h
index c76dcb6..632fc9df 100644
--- a/chrome/browser/offline_pages/offline_page_mhtml_archiver.h
+++ b/chrome/browser/offline_pages/offline_page_mhtml_archiver.h
@@ -43,22 +43,20 @@
 //   }
 class OfflinePageMHTMLArchiver : public OfflinePageArchiver {
  public:
-
-  explicit OfflinePageMHTMLArchiver(content::WebContents* web_contents);
+  OfflinePageMHTMLArchiver();
   ~OfflinePageMHTMLArchiver() override;
 
   // OfflinePageArchiver implementation:
   void CreateArchive(const base::FilePath& archives_dir,
                      const CreateArchiveParams& create_archive_params,
+                     content::WebContents* web_contents,
                      const CreateArchiveCallback& callback) override;
 
  protected:
-  // Allows to overload the archiver for testing.
-  OfflinePageMHTMLArchiver();
-
   // Try to generate MHTML.
   // Might be overridden for testing purpose.
   virtual void GenerateMHTML(const base::FilePath& archives_dir,
+                             content::WebContents* web_contents,
                              const CreateArchiveParams& create_archive_params);
 
   // Callback for Generating MHTML.
@@ -75,10 +73,10 @@
   // Checks whether the page to be saved has security error when loaded over
   // HTTPS. Saving a page will fail if that is the case. HTTP connections are
   // not affected.
-  virtual bool HasConnectionSecurityError();
+  virtual bool HasConnectionSecurityError(content::WebContents* web_contents);
 
   // Returns the page type of the page being saved.
-  virtual content::PageType GetPageType();
+  virtual content::PageType GetPageType(content::WebContents* web_contents);
 
   // Reports failure to create archive a page to the client that requested it.
   void ReportFailure(ArchiverResult result);
@@ -87,9 +85,6 @@
   void DeleteFileAndReportFailure(const base::FilePath& file_path,
                                   ArchiverResult result);
 
-  // Contents of the web page to be serialized. Not owned.
-  content::WebContents* web_contents_;
-
   CreateArchiveCallback callback_;
 
   base::WeakPtrFactory<OfflinePageMHTMLArchiver> weak_ptr_factory_;
diff --git a/chrome/browser/offline_pages/offline_page_mhtml_archiver_unittest.cc b/chrome/browser/offline_pages/offline_page_mhtml_archiver_unittest.cc
index 48576df..7943f554 100644
--- a/chrome/browser/offline_pages/offline_page_mhtml_archiver_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_mhtml_archiver_unittest.cc
@@ -53,9 +53,10 @@
 
  private:
   void GenerateMHTML(const base::FilePath& archives_dir,
+                     content::WebContents* web_contents,
                      const CreateArchiveParams& create_archive_params) override;
-  bool HasConnectionSecurityError() override;
-  content::PageType GetPageType() override;
+  bool HasConnectionSecurityError(content::WebContents* web_contents) override;
+  content::PageType GetPageType(content::WebContents* web_contents) override;
 
   const GURL url_;
   const TestScenario test_scenario_;
@@ -74,6 +75,7 @@
 
 void TestMHTMLArchiver::GenerateMHTML(
     const base::FilePath& archives_dir,
+    content::WebContents* web_contents,
     const CreateArchiveParams& create_archive_params) {
   if (test_scenario_ == TestScenario::WEB_CONTENTS_MISSING) {
     ReportFailure(ArchiverResult::ERROR_CONTENT_UNAVAILABLE);
@@ -93,11 +95,13 @@
                                 kTestTitle, kTestFileSize));
 }
 
-bool TestMHTMLArchiver::HasConnectionSecurityError() {
+bool TestMHTMLArchiver::HasConnectionSecurityError(
+    content::WebContents* web_contents) {
   return test_scenario_ == TestScenario::CONNECTION_SECURITY_ERROR;
 }
 
-content::PageType TestMHTMLArchiver::GetPageType() {
+content::PageType TestMHTMLArchiver::GetPageType(
+    content::WebContents* web_contents) {
   if (test_scenario_ == TestScenario::ERROR_PAGE)
     return content::PageType::PAGE_TYPE_ERROR;
   if (test_scenario_ == TestScenario::INTERSTITIAL_PAGE)
@@ -185,7 +189,7 @@
   std::unique_ptr<TestMHTMLArchiver> archiver(
       new TestMHTMLArchiver(url, scenario));
   archiver->CreateArchive(archive_dir_path_,
-                          OfflinePageArchiver::CreateArchiveParams(),
+                          OfflinePageArchiver::CreateArchiveParams(), nullptr,
                           callback());
   PumpLoop();
   return archiver;
diff --git a/chrome/browser/offline_pages/offline_page_request_job_unittest.cc b/chrome/browser/offline_pages/offline_page_request_job_unittest.cc
index 093d195..aa54c67c 100644
--- a/chrome/browser/offline_pages/offline_page_request_job_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_request_job_unittest.cc
@@ -294,6 +294,7 @@
 
   void CreateArchive(const base::FilePath& archives_dir,
                      const CreateArchiveParams& create_archive_params,
+                     content::WebContents* web_contents,
                      const CreateArchiveCallback& callback) override {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
@@ -954,7 +955,7 @@
       ClientId(kDownloadNamespace, base::IntToString(item_counter));
   save_page_params.original_url = original_url;
   OfflinePageModelFactory::GetForBrowserContext(profile())->SavePage(
-      save_page_params, std::move(archiver),
+      save_page_params, std::move(archiver), nullptr,
       base::Bind(&OfflinePageRequestJobTest::OnSavePageDone,
                  base::Unretained(this)));
   WaitForAsyncOperation();
diff --git a/chrome/browser/offline_pages/offline_page_utils_unittest.cc b/chrome/browser/offline_pages/offline_page_utils_unittest.cc
index 6f76802..2037030 100644
--- a/chrome/browser/offline_pages/offline_page_utils_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_utils_unittest.cc
@@ -182,8 +182,7 @@
   save_page_params.url = url;
   save_page_params.client_id = client_id;
   OfflinePageModelFactory::GetForBrowserContext(profile())->SavePage(
-      save_page_params,
-      std::move(archiver),
+      save_page_params, std::move(archiver), web_contents_.get(),
       base::Bind(&OfflinePageUtilsTest::OnSavePageDone, AsWeakPtr()));
   RunUntilIdle();
 }
diff --git a/chrome/browser/offline_pages/recent_tab_helper.cc b/chrome/browser/offline_pages/recent_tab_helper.cc
index 9deb7dd..fd3bc64 100644
--- a/chrome/browser/offline_pages/recent_tab_helper.cc
+++ b/chrome/browser/offline_pages/recent_tab_helper.cc
@@ -42,8 +42,7 @@
   // offline_pages::RecentTabHelper::Delegate
   std::unique_ptr<offline_pages::OfflinePageArchiver> CreatePageArchiver(
       content::WebContents* web_contents) override {
-    return std::make_unique<offline_pages::OfflinePageMHTMLArchiver>(
-        web_contents);
+    return std::make_unique<offline_pages::OfflinePageMHTMLArchiver>();
   }
   bool GetTabId(content::WebContents* web_contents, int* tab_id) override {
     return offline_pages::OfflinePageUtils::GetTabId(web_contents, tab_id);
@@ -459,6 +458,7 @@
   save_page_params.request_origin = snapshot_info->origin;
   page_model_->SavePage(
       save_page_params, delegate_->CreatePageArchiver(web_contents()),
+      web_contents(),
       base::Bind(&RecentTabHelper::SavePageCallback,
                  weak_ptr_factory_.GetWeakPtr(), snapshot_info));
 }
diff --git a/chrome/browser/printing/print_preview_message_handler.cc b/chrome/browser/printing/print_preview_message_handler.cc
index b1c5b4b..3e88490b 100644
--- a/chrome/browser/printing/print_preview_message_handler.cc
+++ b/chrome/browser/printing/print_preview_message_handler.cc
@@ -67,9 +67,8 @@
     return nullptr;
   }
 
-  unsigned char* data_begin = static_cast<unsigned char*>(shared_buf->memory());
-  std::vector<unsigned char> data(data_begin, data_begin + data_size);
-  return base::RefCountedBytes::TakeVector(&data);
+  return base::MakeRefCounted<base::RefCountedSharedMemory>(
+      std::move(shared_buf), data_size);
 }
 
 }  // namespace
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 0f1d2a67..bb389a6 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -334,11 +334,13 @@
 
   std::unique_ptr<base::SharedMemory> shared_buf =
       GetShmFromMojoHandle(std::move(handle));
-  scoped_refptr<base::RefCountedBytes> bytes =
-      base::MakeRefCounted<base::RefCountedBytes>(
-          reinterpret_cast<const unsigned char*>(shared_buf->memory()),
-          shared_buf->mapped_size());
-  PrintDocument(document, bytes, params.page_size, params.content_area,
+  if (!shared_buf)
+    return;
+
+  size_t size = shared_buf->mapped_size();
+  auto data = base::MakeRefCounted<base::RefCountedSharedMemory>(
+      std::move(shared_buf), size);
+  PrintDocument(document, data, params.page_size, params.content_area,
                 params.physical_offsets);
 }
 
@@ -372,11 +374,10 @@
     web_contents()->Stop();
     return;
   }
-  scoped_refptr<base::RefCountedBytes> bytes =
-      base::MakeRefCounted<base::RefCountedBytes>(
-          reinterpret_cast<const unsigned char*>(shared_buf->memory()),
-          content.data_size);
-  PrintDocument(document, bytes, params.page_size, params.content_area,
+
+  auto data = base::MakeRefCounted<base::RefCountedSharedMemory>(
+      std::move(shared_buf), content.data_size);
+  PrintDocument(document, data, params.page_size, params.content_area,
                 params.physical_offsets);
 }
 
diff --git a/chrome/browser/resource_coordinator/lifecycle_unit_source_base.cc b/chrome/browser/resource_coordinator/lifecycle_unit_source_base.cc
index e6c317a..8480a67 100644
--- a/chrome/browser/resource_coordinator/lifecycle_unit_source_base.cc
+++ b/chrome/browser/resource_coordinator/lifecycle_unit_source_base.cc
@@ -27,10 +27,4 @@
     observer.OnLifecycleUnitCreated(lifecycle_unit);
 }
 
-void LifecycleUnitSourceBase::NotifyLifecycleUnitDestroyed(
-    LifecycleUnit* lifecycle_unit) {
-  for (LifecycleUnitSourceObserver& observer : observers_)
-    observer.OnLifecycleUnitDestroyed(lifecycle_unit);
-}
-
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/lifecycle_unit_source_base.h b/chrome/browser/resource_coordinator/lifecycle_unit_source_base.h
index 829ceec..a16485f 100644
--- a/chrome/browser/resource_coordinator/lifecycle_unit_source_base.h
+++ b/chrome/browser/resource_coordinator/lifecycle_unit_source_base.h
@@ -24,12 +24,11 @@
   void RemoveObserver(LifecycleUnitSourceObserver* observer) override;
 
  protected:
-  // Notifies observers that a LifecycleUnit was created / destroyed.
+  // Notifies observers that a LifecycleUnit was created.
   void NotifyLifecycleUnitCreated(LifecycleUnit* lifecycle_unit);
-  void NotifyLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit);
 
  private:
-  // Observers notified when a LifecycleUnit is created or destroyed.
+  // Observers notified when a LifecycleUnit is created.
   base::ObserverList<LifecycleUnitSourceObserver> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(LifecycleUnitSourceBase);
diff --git a/chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h b/chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h
index 9bdc375..56a3980 100644
--- a/chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h
+++ b/chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h
@@ -9,20 +9,17 @@
 
 class LifecycleUnit;
 
-// Interface to be notified when LifecycleUnits are created and destroyed.
-//
-// An observer doesn't own a LifecycleUnit* received via this interface. An
-// observer can safely use a LifecycleUnit* received via this interface until it
-// is notified that it will be destroyed (OnLifecycleUnitDestroyed()).
+// Interface to be notified when LifecycleUnits are created.
 class LifecycleUnitSourceObserver {
  public:
   virtual ~LifecycleUnitSourceObserver() = default;
 
   // Invoked immediately after a LifecycleUnit is created.
+  //
+  // The observer doesn't own |lifecycle_unit|. To use |lifecycle_unit| beyond
+  // this method invocation, register a LifecycleUnitObserver to be notified of
+  // its destruction.
   virtual void OnLifecycleUnitCreated(LifecycleUnit* lifecycle_unit) = 0;
-
-  // Invoked just before a LifecycleUnit is destroyed.
-  virtual void OnLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit) = 0;
 };
 
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
index e2cb729..41e54f1 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
@@ -128,7 +128,6 @@
   TabLifecycleUnit* lifecycle_unit = it->second.get();
   if (focused_tab_lifecycle_unit_ == lifecycle_unit)
     UpdateFocusedTabTo(nullptr);
-  NotifyLifecycleUnitDestroyed(lifecycle_unit);
   tabs_.erase(contents);
 }
 
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
index 1674741b..9ded1ea 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "build/build_config.h"
+#include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
 #include "chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h"
 #include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h"
 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit.h"
@@ -46,7 +47,6 @@
   MockLifecycleUnitSourceObserver() = default;
 
   MOCK_METHOD1(OnLifecycleUnitCreated, void(LifecycleUnit*));
-  MOCK_METHOD1(OnLifecycleUnitDestroyed, void(LifecycleUnit*));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockLifecycleUnitSourceObserver);
@@ -65,6 +65,21 @@
   DISALLOW_COPY_AND_ASSIGN(MockTabLifecycleObserver);
 };
 
+class MockLifecycleUnitObserver : public LifecycleUnitObserver {
+ public:
+  MockLifecycleUnitObserver() = default;
+
+  MOCK_METHOD1(OnLifecycleUnitStateChanged,
+               void(LifecycleUnit* lifecycle_unit));
+  MOCK_METHOD2(OnLifecycleUnitVisibilityChanged,
+               void(LifecycleUnit* lifecycle_unit,
+                    content::Visibility visibility));
+  MOCK_METHOD1(OnLifecycleUnitDestroyed, void(LifecycleUnit* lifecycle_unit));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockLifecycleUnitObserver);
+};
+
 bool IsFocused(LifecycleUnit* lifecycle_unit) {
   return lifecycle_unit->GetSortKey().last_focused_time ==
          base::TimeTicks::Max();
@@ -86,11 +101,7 @@
   }
 
   void TearDown() override {
-    // Expect notifications when tabs are closed.
-    EXPECT_CALL(source_observer_, OnLifecycleUnitDestroyed(testing::_))
-        .Times(tab_strip_model_->count());
     tab_strip_model_->CloseAllTabs();
-
     tab_strip_model_.reset();
     ChromeRenderViewHostTestHarness::TearDown();
   }
@@ -188,13 +199,24 @@
     EXPECT_TRUE(source_.GetTabLifecycleUnitExternal(third_web_contents));
 
     // Expect notifications when tabs are closed.
-    EXPECT_CALL(source_observer_,
-                OnLifecycleUnitDestroyed(first_lifecycle_unit));
-    EXPECT_CALL(source_observer_,
-                OnLifecycleUnitDestroyed(second_lifecycle_unit));
-    EXPECT_CALL(source_observer_,
-                OnLifecycleUnitDestroyed(third_lifecycle_unit));
-    tab_strip_model_->CloseAllTabs();
+    CloseTabsAndExpectNotifications(
+        tab_strip_model_.get(),
+        {first_lifecycle_unit, second_lifecycle_unit, third_lifecycle_unit});
+  }
+
+  void CloseTabsAndExpectNotifications(
+      TabStripModel* tab_strip_model,
+      std::vector<LifecycleUnit*> lifecycle_units) {
+    std::vector<std::unique_ptr<testing::StrictMock<MockLifecycleUnitObserver>>>
+        observers;
+    for (LifecycleUnit* lifecycle_unit : lifecycle_units) {
+      observers.emplace_back(
+          std::make_unique<testing::StrictMock<MockLifecycleUnitObserver>>());
+      lifecycle_unit->AddObserver(observers.back().get());
+      EXPECT_CALL(*observers.back().get(),
+                  OnLifecycleUnitDestroyed(lifecycle_unit));
+    }
+    tab_strip_model->CloseAllTabs();
   }
 
   TabLifecycleUnitSource source_;
@@ -250,10 +272,8 @@
             second_lifecycle_unit->GetSortKey().last_focused_time);
 
   // Expect notifications when tabs are closed.
-  EXPECT_CALL(source_observer_, OnLifecycleUnitDestroyed(first_lifecycle_unit));
-  EXPECT_CALL(source_observer_,
-              OnLifecycleUnitDestroyed(second_lifecycle_unit));
-  tab_strip_model_->CloseAllTabs();
+  CloseTabsAndExpectNotifications(
+      tab_strip_model_.get(), {first_lifecycle_unit, second_lifecycle_unit});
 }
 
 TEST_F(TabLifecycleUnitSourceTest, CloseTabInFocusedTabStrip) {
@@ -264,15 +284,16 @@
 
   // Close the second tab. The first tab should be focused.
   test_clock_.Advance(kShortDelay);
-  EXPECT_CALL(source_observer_,
-              OnLifecycleUnitDestroyed(second_lifecycle_unit));
+  testing::StrictMock<MockLifecycleUnitObserver> second_observer;
+  second_lifecycle_unit->AddObserver(&second_observer);
+  EXPECT_CALL(second_observer, OnLifecycleUnitDestroyed(second_lifecycle_unit));
   tab_strip_model_->CloseWebContentsAt(1, 0);
   testing::Mock::VerifyAndClear(&source_observer_);
   EXPECT_TRUE(IsFocused(first_lifecycle_unit));
 
   // Expect notifications when tabs are closed.
-  EXPECT_CALL(source_observer_, OnLifecycleUnitDestroyed(first_lifecycle_unit));
-  tab_strip_model_->CloseAllTabs();
+  CloseTabsAndExpectNotifications(tab_strip_model_.get(),
+                                  {first_lifecycle_unit});
 }
 
 TEST_F(TabLifecycleUnitSourceTest, ReplaceWebContents) {
@@ -298,10 +319,8 @@
   delete original_web_contents;
 
   // Expect notifications when tabs are closed.
-  EXPECT_CALL(source_observer_, OnLifecycleUnitDestroyed(first_lifecycle_unit));
-  EXPECT_CALL(source_observer_,
-              OnLifecycleUnitDestroyed(second_lifecycle_unit));
-  tab_strip_model_->CloseAllTabs();
+  CloseTabsAndExpectNotifications(
+      tab_strip_model_.get(), {first_lifecycle_unit, second_lifecycle_unit});
 }
 
 TEST_F(TabLifecycleUnitSourceTest, DetachWebContents) {
@@ -334,9 +353,8 @@
   EXPECT_EQ(LifecycleUnit::State::DISCARDED, first_lifecycle_unit->GetState());
 
   // Expect a notification when the tab is closed.
-  EXPECT_CALL(source_observer_, OnLifecycleUnitDestroyed(testing::_))
-      .Times(other_tab_strip_model.count());
-  other_tab_strip_model.CloseAllTabs();
+  CloseTabsAndExpectNotifications(&other_tab_strip_model,
+                                  {first_lifecycle_unit});
 }
 
 // Tab discarding is tested here rather than in TabLifecycleUnitTest because
diff --git a/chrome/browser/resources/chromeos/arc_support/background.js b/chrome/browser/resources/chromeos/arc_support/background.js
index 66a73de..d2941fa 100644
--- a/chrome/browser/resources/chromeos/arc_support/background.js
+++ b/chrome/browser/resources/chromeos/arc_support/background.js
@@ -279,10 +279,12 @@
    *     backup-restore preference.
    * @param {PreferenceCheckbox} locationServiceCheckbox The checkbox for the
    *     location service.
+   * @param {string} learnMorePaiService. Contents of learn more link of Play
+   *     auto install service.
    */
   constructor(
       container, isManaged, countryCode, metricsCheckbox, backupRestoreCheckbox,
-      locationServiceCheckbox) {
+      locationServiceCheckbox, learnMorePaiService) {
     this.loadingContainer_ =
         container.querySelector('#terms-of-service-loading');
     this.contentContainer_ =
@@ -337,14 +339,33 @@
     });
     this.state_ = LoadState.UNLOADED;
 
+    this.serviceContainer_ = container.querySelector('#service-container');
+    this.locationService_ =
+        container.querySelector('#location-service-preference');
+    this.paiService_ = container.querySelector('#pai-service-descirption');
+    this.googleServiceConfirmation_ =
+        container.querySelector('#google-service-confirmation');
+    this.agreeButton_ = container.querySelector('#button-agree');
+    this.nextButton_ = container.querySelector('#button-next');
+
     // On managed case, do not show TermsOfService section. Note that the
     // checkbox for the prefereces are still visible.
     var visibility = isManaged ? 'hidden' : 'visible';
     container.querySelector('#terms-container').style.visibility = visibility;
 
+    // PAI service.
+    var paiLabel = this.paiService_.querySelector('.content-text');
+    var paiLearnMoreLink = paiLabel.querySelector('#learn-more-link-pai');
+    if (paiLearnMoreLink) {
+      paiLearnMoreLink.onclick = function(event) {
+        event.stopPropagation();
+        showTextOverlay(learnMorePaiService);
+      };
+    }
+
     // Set event handler for buttons.
-    container.querySelector('#button-agree')
-        .addEventListener('click', () => this.onAgree());
+    this.agreeButton_.addEventListener('click', () => this.onAgree());
+    this.nextButton_.addEventListener('click', () => this.onNext_());
     container.querySelector('#button-cancel')
         .addEventListener('click', () => this.onCancel_());
   }
@@ -364,12 +385,30 @@
   showContent_() {
     this.loadingContainer_.hidden = true;
     this.contentContainer_.hidden = false;
+    this.locationService_.hidden = true;
+    this.paiService_.hidden = true;
+    this.googleServiceConfirmation_.hidden = true;
+    this.serviceContainer_.style.overflow = 'hidden';
+    this.agreeButton_.hidden = true;
+    this.nextButton_.hidden = false;
     this.updateTermsHeight_();
-    this.contentContainer_.querySelector('#button-agree').focus();
+    this.nextButton_.focus();
+  }
+
+  onNext_() {
+    this.locationService_.hidden = false;
+    this.paiService_.hidden = false;
+    this.googleServiceConfirmation_.hidden = false;
+    this.serviceContainer_.style.overflowY = 'auto';
+    this.serviceContainer_.scrollTop = this.serviceContainer_.scrollHeight;
+    this.agreeButton_.hidden = false;
+    this.nextButton_.hidden = true;
+    this.agreeButton_.focus();
   }
 
   /**
-   * Updates terms view height manually because webview is not automatically
+   * Updates terms view height manually because webview is not automati
+   * cally
    * resized in case parent div element gets resized.
    */
   updateTermsHeight_() {
@@ -681,7 +720,8 @@
       new PreferenceCheckbox(
           doc.getElementById('location-service-preference'),
           data.learnMoreLocationServices, '#learn-more-link-location-service',
-          data.controlledByPolicy));
+          data.controlledByPolicy),
+      data.learnMorePaiService);
 
   // Initialize the Active Directory SAML authentication page.
   activeDirectoryAuthPage =
diff --git a/chrome/browser/resources/chromeos/arc_support/main.css b/chrome/browser/resources/chromeos/arc_support/main.css
index 75acca6..5869f35b 100644
--- a/chrome/browser/resources/chromeos/arc_support/main.css
+++ b/chrome/browser/resources/chromeos/arc_support/main.css
@@ -53,6 +53,16 @@
   width: 100%;
 }
 
+#service-container {
+  box-shadow: 0 -2px 2px -2px rgba(0, 0, 0, 0.14);
+  flex: auto;
+  margin: 40px 0 0 0;
+}
+
+#service-container::-webkit-scrollbar {
+  display: none;
+}
+
 #terms-view {
   display: block;
   margin: auto;
@@ -73,7 +83,8 @@
 }
 
 #button-retry,
-#button-agree {
+#button-agree,
+#button-next {
   -webkit-margin-end: 18px;
 }
 
@@ -95,17 +106,17 @@
   margin: 0;
 }
 
-.content {
-  /* height plus padding equals 464px, which is app height (640px) minus icon
-   * header (96px) and bottom (80px).
-   */
-  height: 452px;
-  margin: 0;
-  padding: 0 64px 12px 64px;
+#google-service-confirmation .content-text {
+  font-weight: 600;
 }
 
-.subcontentcontainer {
-  padding: 0 20px;
+.content {
+  /* height equals 464px, which is app height (640px) minus icon
+   * header (96px) and bottom (80px).
+   */
+  height: 464px;
+  margin: 0;
+  padding: 0 64px;
 }
 
 .controlled-setting-indicator {
@@ -213,15 +224,15 @@
 
 .section-checkbox-container {
   margin: auto;
-  padding-top: 16px;
-  width: 100%;
+  max-width: 590px;
+  padding: 0 20px 16px 20px;
 }
 
 .section-terms {
   border: 1px solid #d9d9d9;
   box-sizing: border-box;
   flex: auto;
-  margin: 40px 0 14px 0;
+  margin: 0 0 16px 0;
   padding: 0;
 }
 
diff --git a/chrome/browser/resources/chromeos/arc_support/main.html b/chrome/browser/resources/chromeos/arc_support/main.html
index bb91f12..ee3b3bb 100644
--- a/chrome/browser/resources/chromeos/arc_support/main.html
+++ b/chrome/browser/resources/chromeos/arc_support/main.html
@@ -96,13 +96,15 @@
             aria-live="polite"></p>
         <p class="subtitle" i18n-content="greetingDescription">
         </p>
-        <div class="section-terms" id="terms-container">
-          <webview id="terms-view"></webview>
-        </div>
-        <div class="subcontentcontainer">
+        <div class ="section-flex" id="service-container">
+          <div class="section-terms" id="terms-container">
+            <webview id="terms-view"></webview>
+          </div>
           <div>
-            <a class="checkbox-text" id="privacy-policy-link"
-                href="#" i18n-content="privacyPolicyLink"></a>
+            <label class="layout horizontal section-checkbox-container">
+              <a class="checkbox-text" id="privacy-policy-link"
+                  href="#" i18n-content="privacyPolicyLink"></a>
+            </label>
           </div>
           <div id="metrics-preference">
             <label class="layout horizontal section-checkbox-container">
@@ -128,6 +130,19 @@
               </paper-checkbox>
             </label>
           </div>
+          <div id="pai-service-descirption">
+            <label class="layout horizontal section-checkbox-container">
+                <p class="content-text"
+                    i18n-values=".innerHTML:textPaiService"></p>
+            </label>
+          </div>
+          <div id="google-service-confirmation">
+            <label class="layout horizontal section-checkbox-container">
+              <p class="content-text"
+                    i18n-values=".innerHTML:textGoogleServiceConfirmation">
+              </p>
+            </label>
+          </div>
         </div>
       </div>
       <div class="layout horizontal center end-justified section-buttons">
@@ -136,6 +151,10 @@
           <div class="container flex" i18n-content="buttonCancel">
           </div>
         </paper-button>
+        <paper-button class="blue" id="button-next">
+          <div class="container flex" i18n-content="buttonNext">
+          </div>
+        </paper-button>
         <paper-button class="blue" id="button-agree">
           <div class="container flex" i18n-content="buttonAgree">
           </div>
diff --git a/chrome/browser/resources/chromeos/arc_support/playstore.css b/chrome/browser/resources/chromeos/arc_support/playstore.css
index 8c342c10..d8d3a5c 100644
--- a/chrome/browser/resources/chromeos/arc_support/playstore.css
+++ b/chrome/browser/resources/chromeos/arc_support/playstore.css
@@ -38,12 +38,19 @@
   line-height: 20px;
 }
 
+.large-view .play-contained h1 {
+  color: rgba(0, 0, 0, 0.87);
+  font-size: 14px;
+  line-height: 20px;
+}
+
 .large-view .play-contained h2,
 .large-view .play-contained strong {
   color: rgba(0, 0, 0, 0.87);
   font-size: 13px;
   line-height: 20px;
 }
+
 .large-view .play-contained select {
   color: rgba(0, 0, 0, 0.87);
   font-size: 13px;
@@ -62,6 +69,15 @@
   width: 100%;
 }
 
+.play-contained h1 {
+  color: rgba(0, 0, 0, 0.54);
+  font-family: 'Roboto';
+  font-size: 11px;
+  font-style: normal;
+  font-weight: 600;
+  line-height: 16px;
+}
+
 .play-contained h2,
 .play-contained strong {
   color: rgba(0, 0, 0, 0.54);
@@ -86,7 +102,6 @@
   padding: 0;
 }
 
-.play-contained h1,
 #play-footer ul {
   display: none;
 }
diff --git a/chrome/browser/resources/chromeos/login/arc_terms_of_service.css b/chrome/browser/resources/chromeos/login/arc_terms_of_service.css
index 838c4de3..12ba390 100644
--- a/chrome/browser/resources/chromeos/login/arc_terms_of_service.css
+++ b/chrome/browser/resources/chromeos/login/arc_terms_of_service.css
@@ -26,13 +26,15 @@
 .arc-tos-loading .arc-tos-content,
 .arc-tos-loading .arc-tos-error,
 .arc-tos-loading #arc-tos-retry-button-md,
+.arc-tos-loading #arc-tos-accept-button-md,
 .error .arc-tos-content,
 .error .arc-tos-loading,
-.error #arc-tos-accept-button-md {
+.error #arc-tos-accept-button-md,
+.error #arc-tos-next-button-md {
   display: none;
 }
 
-.arc-tos-loading #arc-tos-accept-button-md,
+.arc-tos-loading #arc-tos-next-button-md,
 .arc-tos-loading #arc-tos-skip-button-md,
 .arc-tos-loading #arc-tos-retry-button-md {
   pointer-events: none;
@@ -69,6 +71,24 @@
   color: rgba(0, 0, 0, 0.54);
 }
 
+
+#google-service-confirmation-text {
+  font-weight: 600;
+}
+
+#arc-tos-dialog-md {
+  height: 640px;
+}
+
+#arc-tos-container-md {
+  box-shadow: 0 -2px 2px -2px rgba(0, 0, 0, 0.14);
+  padding: -4px 0 0 0;
+}
+
+#arc-tos-container-md::-webkit-scrollbar {
+  display: none;
+}
+
 #arc-tos-view-md {
   display: block;
   height: 10px;
@@ -81,7 +101,7 @@
   border: 1px solid #d9d9d9;
   box-sizing: border-box;
   flex: auto;
-  margin: -4px 0 0 0;
+  margin: 0;
   padding: 0;
   width: 100%;
 }
@@ -91,6 +111,7 @@
 }
 
 #arc-tos-accept-button-md,
+#arc-tos-next-button-md,
 #arc-tos-retry-button-md {
   -webkit-padding-end: 18px;
 }
diff --git a/chrome/browser/resources/chromeos/login/arc_terms_of_service.html b/chrome/browser/resources/chromeos/login/arc_terms_of_service.html
index 979c227..7046ae4 100644
--- a/chrome/browser/resources/chromeos/login/arc_terms_of_service.html
+++ b/chrome/browser/resources/chromeos/login/arc_terms_of_service.html
@@ -20,7 +20,7 @@
         <div class="subtitle" i18n-content="arcTermsOfServiceScreenDescription">
         </div>
       </div>
-      <div class="footer flex layout vertical">
+      <div id="arc-tos-container-md" class="footer flex layout vertical">
         <div id="arc-tos-view-container-md" class="arc-tos-content">
           <webview id="arc-tos-view-md"></webview>
         </div>
@@ -35,11 +35,20 @@
             <p i18n-values=".innerHTML:arcTextBackupRestore"></p>
           </paper-checkbox>
         </div>
-        <div class="parameter-section arc-tos-content">
+        <div id= "arc-location-service-md"
+            class="parameter-section arc-tos-content">
           <paper-checkbox id="arc-enable-location-service-md">
             <p i18n-values=".innerHTML:arcTextLocationService"></p>
           </paper-checkbox>
         </div>
+        <div id="arc-pai-service-md" class="parameter-section arc-tos-content">
+          <p i18n-values=".innerHTML:arcTextPaiService"></p>
+        </div>
+        <div id="arc-google-service-confirmation-md"
+            class="parameter-section arc-tos-content">
+          <p id="google-service-confirmation-text"
+              i18n-values=".innerHTML:arcTextGoogleServiceConfirmation"></p>
+        </div>
         <div class="arc-tos-loading">
           <p i18n-content="arcTermsOfServiceLoading"></p>
         </div>
@@ -59,6 +68,11 @@
             disabled="[[arcTosButtonsDisabled]]">
           <div i18n-content="arcTermsOfServiceRetryButton"></div>
         </oobe-text-button>
+        <oobe-text-button id="arc-tos-next-button-md"
+            inverse on-tap="onNext_"
+            disabled="[[arcTosButtonsDisabled]]">
+          <div i18n-content="arcTermsOfServiceNextButton"></div>
+        </oobe-text-button>
         <oobe-text-button id="arc-tos-accept-button-md"
             inverse on-tap="onAccept_"
             disabled="[[arcTosButtonsDisabled]]">
diff --git a/chrome/browser/resources/chromeos/login/arc_terms_of_service.js b/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
index 7535582..fed5702 100644
--- a/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
+++ b/chrome/browser/resources/chromeos/login/arc_terms_of_service.js
@@ -45,6 +45,15 @@
   },
 
   /**
+   * On-tap event handler for Next button.
+   *
+   * @private
+   */
+  onNext_: function() {
+    this.screen.onNext();
+  },
+
+  /**
    * On-tap event handler for Retry button.
    *
    * @private
diff --git a/chrome/browser/resources/chromeos/login/oobe_a11y_option.html b/chrome/browser/resources/chromeos/login/oobe_a11y_option.html
index 925b576..b48fcde8 100644
--- a/chrome/browser/resources/chromeos/login/oobe_a11y_option.html
+++ b/chrome/browser/resources/chromeos/login/oobe_a11y_option.html
@@ -3,33 +3,7 @@
 
 <dom-module id="oobe-a11y-option">
   <template>
-    <style>
-      :root {
-          --oobe-toggle-button-size: {
-            box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
-            height: 16px;
-            top: -2px;
-            width: 16px;
-          };
-          --oobe-toggle-bar-size: {
-            height: 12px;
-            left: 4px;
-            width: 24px;
-          };
-          --paper-toggle-button-checked-bar-color: var(--google-blue-500);
-          --paper-toggle-button-checked-bar: var(--oobe-toggle-bar-size);
-          --paper-toggle-button-checked-button: {
-            @apply(--oobe-toggle-button-size);
-            transform: translate(14px, 0);
-          };
-          --paper-toggle-button-checked-button-color: var(--google-blue-500);
-          --paper-toggle-button-checked-ink-color: var(--google-blue-500);
-          --paper-toggle-button-label-spacing: 0;
-          --paper-toggle-button-unchecked-bar: var(--oobe-toggle-bar-size);
-          --paper-toggle-button-unchecked-button:
-              var(--oobe-toggle-button-size);
-      }
-    </style>
+    <link rel="stylesheet" href="oobe_toggle_button.css">
     <link rel="stylesheet" href="oobe_flex_layout.css">
     <link rel="stylesheet" href="oobe_a11y_option.css">
     <div id="elementBox" class="layout horizontal">
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.css b/chrome/browser/resources/chromeos/login/oobe_eula.css
index fe80252f..a385b27 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.css
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.css
@@ -35,15 +35,11 @@
 #logging {
   -webkit-margin-start: 20px;
   color: rgba(0, 0, 0, 0.54);
-  margin-bottom: 20px;
   margin-top: 23px; /* = 36 - font size */
 }
 
-#usageStats {
-  --paper-checkbox-size: 16px;
-  --paper-checkbox-checked-color: var(--google-blue-500);
-  --paper-checkbox-ink-size: 44px;
-  size: 16px;
+paper-toggle-button {
+  width: 36px;
 }
 
 #usageStatsLabelContainer {
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.html b/chrome/browser/resources/chromeos/login/oobe_eula.html
index 533c8b8..45a1d63 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.html
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.html
@@ -39,6 +39,7 @@
 
 <dom-module id="oobe-eula-md">
   <template>
+    <link rel="stylesheet" href="oobe_toggle_button.css">
     <link rel="stylesheet" href="chrome://resources/css/throbber.css">
     <link rel="stylesheet" href="oobe_eula.css">
     <link rel="stylesheet" href="oobe_dialog_parameters.css">
@@ -71,9 +72,9 @@
             i18n-content="eulaSystemInstallationSettings">
         </a>
         <div id="logging" class="layout horizontal">
-          <paper-checkbox id="usageStats" checked="{{usageStatsChecked}}"
+          <paper-toggle-button id="usageStats" checked="{{usageStatsChecked}}"
               on-change="onUsageChanged_" aria-labelledby="usageStatsLabel">
-          </paper-checkbox>
+          </paper-toggle-button>
           <div id="usageStatsLabelContainer">
             <span id="usageStatsLabel" i18n-content="checkboxLogging"></span>
             <a id="" href="#" i18n-content="learnMore"
diff --git a/chrome/browser/resources/chromeos/login/oobe_toggle_button.css b/chrome/browser/resources/chromeos/login/oobe_toggle_button.css
new file mode 100644
index 0000000..cedacb00
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/oobe_toggle_button.css
@@ -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. */
+
+
+/* This is common style file for all OOBE paper-toggle buttons. */
+
+:root {
+    --oobe-toggle-button-size: {
+      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
+      height: 16px;
+      top: -2px;
+      width: 16px;
+    };
+    --oobe-toggle-bar-size: {
+      height: 12px;
+      left: 4px;
+      width: 24px;
+    };
+    --paper-toggle-button-checked-bar-color: var(--google-blue-500);
+    --paper-toggle-button-checked-bar: var(--oobe-toggle-bar-size);
+    --paper-toggle-button-checked-button: {
+      @apply(--oobe-toggle-button-size);
+      transform: translate(14px, 0);
+    };
+    --paper-toggle-button-checked-button-color: var(--google-blue-500);
+    --paper-toggle-button-checked-ink-color: var(--google-blue-500);
+    --paper-toggle-button-label-spacing: 0;
+    --paper-toggle-button-unchecked-bar: var(--oobe-toggle-bar-size);
+    --paper-toggle-button-unchecked-button:
+        var(--oobe-toggle-button-size);
+}
diff --git a/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js b/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
index 504dc02..2aa15db 100644
--- a/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
+++ b/chrome/browser/resources/chromeos/login/screen_arc_terms_of_service.js
@@ -135,6 +135,9 @@
       metrics.querySelector('#learn-more-link-metrics').onclick = function() {
         self.showLearnMoreOverlay(leanMoreStatisticsText);
       };
+      // For device owner set up, this may come after the page is loaded.
+      // Recaculate the ToS webview height.
+      this.updateTermViewHight_();
     },
 
     /**
@@ -248,6 +251,15 @@
       retryButton.addEventListener('click', this.reloadPlayStoreToS.bind(this));
       buttons.push(retryButton);
 
+      var nextButton = this.ownerDocument.createElement('button');
+      nextButton.id = 'arc-tos-next-button';
+      nextButton.disabled = this.classList.contains('arc-tos-loading');
+      nextButton.classList.add('preserve-disabled-state');
+      nextButton.textContent =
+          loadTimeData.getString('arcTermsOfServiceNextButton');
+      nextButton.addEventListener('click', this.onNext.bind(this));
+      buttons.push(nextButton);
+
       var acceptButton = this.ownerDocument.createElement('button');
       acceptButton.id = 'arc-tos-accept-button';
       acceptButton.disabled = this.classList.contains('arc-tos-loading');
@@ -261,6 +273,21 @@
     },
 
     /**
+     * Handles Next button click.
+     */
+    onNext: function() {
+      this.getElement_('arc-location-service').hidden = false;
+      this.getElement_('arc-pai-service').hidden = false;
+      this.getElement_('arc-google-service-confirmation').hidden = false;
+      this.getElement_('arc-tos-container').style.overflowY = 'auto';
+      this.getElement_('arc-tos-container').scrollTop =
+          this.getElement_('arc-tos-container').scrollHeight;
+      this.getElement_('arc-tos-next-button').hidden = true;
+      this.getElement_('arc-tos-accept-button').hidden = false;
+      this.getElement_('arc-tos-accept-button').focus();
+    },
+
+    /**
      * Handles Accept button click.
      */
     onAccept: function() {
@@ -408,8 +435,23 @@
       this.addClass_('arc-tos-loaded');
 
       this.enableButtons_(true);
-      this.getElement_('arc-tos-accept-button').focus();
+      this.getElement_('arc-location-service').hidden = true;
+      this.getElement_('arc-pai-service').hidden = true;
+      this.getElement_('arc-google-service-confirmation').hidden = true;
+      this.getElement_('arc-tos-container').style.overflow = 'hidden';
+      this.getElement_('arc-tos-accept-button').hidden = true;
+      this.getElement_('arc-tos-next-button').hidden = false;
+      this.getElement_('arc-tos-next-button').focus();
 
+      this.updateTermViewHight_();
+    },
+
+    /**
+     * Updates ToS webview height.
+     *
+     * @private
+     */
+    updateTermViewHight_() {
       var termsView = this.getElement_('arc-tos-view');
       var termsViewContainer = this.getElement_('arc-tos-view-container');
       var setTermsHeight = function() {
@@ -459,6 +501,10 @@
           'https://play.google.com/about/images/play_logo.png';
 
       this.hideOverlay();
+      // ToS content may be loaded before the page is shown. In that case,
+      // height of ToS webview is not correctly caculated. Recaculate the
+      // height here.
+      this.updateTermViewHight_();
     },
 
     /**
@@ -497,24 +543,33 @@
     setLearnMoreHandlers_: function() {
       var self = this;
 
-      var leanMoreBackupAndRestoreText =
+      var learnMoreBackupAndRestoreText =
           loadTimeData.getString('arcLearnMoreBackupAndRestore');
       var backupAndRestore = this.getElement_('arc-enable-backup-restore');
       backupAndRestore.parentElement
           .querySelector('#learn-more-link-backup-restore')
           .onclick = function(event) {
         event.stopPropagation();
-        self.showLearnMoreOverlay(leanMoreBackupAndRestoreText);
+        self.showLearnMoreOverlay(learnMoreBackupAndRestoreText);
       };
 
-      var leanMoreLocationServiceText =
+      var learnMoreLocationServiceText =
           loadTimeData.getString('arcLearnMoreLocationService');
       var locationService = this.getElement_('arc-enable-location-service');
       locationService.parentElement
           .querySelector('#learn-more-link-location-service')
           .onclick = function(event) {
         event.stopPropagation();
-        self.showLearnMoreOverlay(leanMoreLocationServiceText);
+        self.showLearnMoreOverlay(learnMoreLocationServiceText);
+      };
+
+      var learnMorePaiServiceText =
+          loadTimeData.getString('arcLearnMorePaiService');
+      var paiService = this.getElement_('arc-pai-service');
+      paiService.querySelector('#learn-more-link-pai').onclick = function(
+          event) {
+        event.stopPropagation();
+        self.showLearnMoreOverlay(learnMorePaiServiceText);
       };
     }
   };
diff --git a/chrome/browser/resources/print_preview/new/app.html b/chrome/browser/resources/print_preview/new/app.html
index d157bda..44405bf 100644
--- a/chrome/browser/resources/print_preview/new/app.html
+++ b/chrome/browser/resources/print_preview/new/app.html
@@ -25,7 +25,9 @@
 <link rel="import" href="scaling_settings.html">
 <link rel="import" href="other_options_settings.html">
 <link rel="import" href="advanced_options_settings.html">
-
+<if expr="not chromeos">
+  <link rel="import" href="link_container.html">
+</if>
 <dom-module id="print-preview-app">
   <template>
     <style>
@@ -119,6 +121,16 @@
             destination="[[destination_]]" disabled="[[controlsDisabled_]]"
             hidden$="[[!settings.vendorItems.available]]">
         </print-preview-advanced-options-settings>
+<if expr="not chromeos">
+        <print-preview-link-container destination="[[destination_]]"
+            app-kiosk-mode="[[isInAppKioskMode_]]"
+            disabled="[[controlsDisabled_]]"
+<if expr="is_macosx">
+            on-open-pdf-in-preview="onOpenPdfInPreview_"
+</if>
+            on-print-with-system-dialog="onPrintWithSystemDialog_">
+        </print-preview-link-container>
+</if>
       </div>
     </div>
     <div id="preview-area-container">
diff --git a/chrome/browser/resources/print_preview/new/app.js b/chrome/browser/resources/print_preview/new/app.js
index 7fb935e5..742d156 100644
--- a/chrome/browser/resources/print_preview/new/app.js
+++ b/chrome/browser/resources/print_preview/new/app.js
@@ -75,6 +75,13 @@
       notify: true,
       value: null,
     },
+
+    /** @private {boolean} */
+    isInAppKioskMode_: {
+      type: Boolean,
+      notify: true,
+      value: false,
+    },
   },
 
   /** @private {?WebUIListenerTracker} */
@@ -93,7 +100,10 @@
   cancelled_: false,
 
   /** @private {boolean} */
-  isInAppKioskMode_: false,
+  showSystemDialogBeforePrint_: false,
+
+  /** @private {boolean} */
+  openPdfInPreview_: false,
 
   /** @override */
   attached: function() {
@@ -153,6 +163,7 @@
         settings.isInAppKioskMode, settings.printerName,
         settings.serializedDefaultDestinationSelectionRulesStr,
         this.recentDestinations_);
+    this.isInAppKioskMode_ = settings.isInAppKioskMode;
   },
 
   /**
@@ -222,7 +233,9 @@
     } else if (this.state == print_preview_new.State.PRINTING) {
       const destination = assert(this.destinationStore_.selectedDestination);
       const whenPrintDone =
-          this.nativeLayer_.print(this.$.model.createPrintTicket(destination));
+          this.nativeLayer_.print(this.$.model.createPrintTicket(
+              destination, this.openPdfInPreview_,
+              this.showSystemDialogBeforePrint_));
       if (destination.isLocal) {
         const onError = destination.id ==
                 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF ?
@@ -289,6 +302,29 @@
         assert(this.documentInfo_), data);
   },
 
+  // <if expr="not chromeos">
+  /** @private */
+  onPrintWithSystemDialog_: function() {
+    assert(!cr.isChromeOS);
+    if (cr.isWindows) {
+      this.showSystemDialogBeforePrint_ = true;
+      this.onPrintRequested_();
+      return;
+    }
+    this.nativeLayer_.showSystemDialog();
+    this.$.state.transitTo(print_preview_new.State.SYSTEM_DIALOG);
+  },
+  // </if>
+
+  // <if expr="is_macosx">
+  /** @private */
+  onOpenPdfInPreview_: function() {
+    this.openPdfInPreview_ = true;
+    this.$.previewArea.setOpeningPdfInPreview();
+    this.onPrintRequested_();
+  },
+  // </if>
+
   /**
    * Called when printing to a privet, cloud, or extension printer fails.
    * @param {*} httpError The HTTP error code, or -1 or a string describing
diff --git a/chrome/browser/resources/print_preview/new/compiled_resources2.gyp b/chrome/browser/resources/print_preview/new/compiled_resources2.gyp
index 7082f1a4..c37a0ad2 100644
--- a/chrome/browser/resources/print_preview/new/compiled_resources2.gyp
+++ b/chrome/browser/resources/print_preview/new/compiled_resources2.gyp
@@ -18,6 +18,7 @@
         'scaling_settings',
         'other_options_settings',
         'advanced_options_settings',
+        'link_container',
         'preview_area',
         'model',
         'state',
@@ -171,6 +172,15 @@
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
     {
+      'target_name': 'link_container',
+      'dependencies': [
+        '../data/compiled_resources2.gyp:destination',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+      ],
+      'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
+    },
+    {
       'target_name': 'preview_area',
       'dependencies': [
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
diff --git a/chrome/browser/resources/print_preview/new/link_container.html b/chrome/browser/resources/print_preview/new/link_container.html
new file mode 100644
index 0000000..2fa93003
--- /dev/null
+++ b/chrome/browser/resources/print_preview/new/link_container.html
@@ -0,0 +1,50 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
+<link rel="import" href="chrome://resources/html/action_link_css.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="../data/destination.html">
+<link rel="import" href="print_preview_shared_css.html">
+<link rel="import" href="throbber_css.html">
+
+<dom-module id="print-preview-link-container">
+  <template>
+    <style include="print-preview-shared throbber action-link cr-hidden-style">
+      :host {
+        margin: 7px 0;
+      }
+
+      :host > div {
+        display: flex;
+      }
+
+      :host > div > .throbber {
+        margin: 8px;
+        min-height: 16px;
+        min-width: 16px;
+      }
+
+      :host > div > a {
+        margin: 8px 20px;
+      }
+    </style>
+    <div>
+      <a is="action-link" id="systemDialogLink" disabled="[[disabled]]"
+           hidden$="[[!shouldShowSystemDialogLink_]]"
+           on-click="onSystemDialogClick_">
+        $i18n{systemDialogOption}
+      </a>
+      <div id="systemDialogThrobber" hidden class="throbber"></div>
+    </div>
+<if expr="is_macosx">
+    <div>
+      <a is="action-link" id="openPdfInPreviewLink" disabled="[[disabled]]"
+          on-click="onOpenInPreviewClick_">
+        $i18n{openPdfInPreviewOption}
+      </a>
+      <div id="openPdfInPreviewThrobber" hidden class="throbber"></div>
+    </div>
+</if>
+  </template>
+  <script src="link_container.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/print_preview/new/link_container.js b/chrome/browser/resources/print_preview/new/link_container.js
new file mode 100644
index 0000000..235de40a
--- /dev/null
+++ b/chrome/browser/resources/print_preview/new/link_container.js
@@ -0,0 +1,55 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Polymer({
+  is: 'print-preview-link-container',
+
+  properties: {
+    appKioskMode: Boolean,
+
+    /** @type {?print_preview.Destination} */
+    destination: Object,
+
+    disabled: Boolean,
+
+    /** @private {boolean} */
+    shouldShowSystemDialogLink_: {
+      type: Boolean,
+      computed: 'computeShouldShowSystemDialogLink_(appKioskMode, destination)',
+    },
+  },
+
+  /**
+   * @return {boolean} Whether the system dialog link should be visible.
+   * @private
+   */
+  computeShouldShowSystemDialogLink_: function() {
+    if (this.appKioskMode)
+      return false;
+    if (!cr.isWindows)
+      return true;
+    return !!this.destination &&
+        this.destination.origin == print_preview.DestinationOrigin.LOCAL &&
+        this.destination.id !=
+        print_preview.Destination.GooglePromotedId.SAVE_AS_PDF;
+  },
+
+  /** @private */
+  onSystemDialogClick_: function() {
+    if (!this.shouldShowSystemDialogLink_)
+      return;
+    // <if expr="not is_win">
+    this.$.systemDialogThrobber.removeAttribute('hidden');
+    // </if>
+    this.fire('print-with-system-dialog');
+  },
+
+  // <if expr="is_macosx">
+  /** @private */
+  onOpenInPreviewClick_: function() {
+    this.$.openPdfInPreviewThrobber.removeAttribute('hidden');
+    this.fire('open-pdf-in-preview');
+  },
+  // </if>
+});
diff --git a/chrome/browser/resources/print_preview/new/model.js b/chrome/browser/resources/print_preview/new/model.js
index dec76c3..b0426a19 100644
--- a/chrome/browser/resources/print_preview/new/model.js
+++ b/chrome/browser/resources/print_preview/new/model.js
@@ -499,9 +499,13 @@
   /**
    * Creates a string that represents a print ticket.
    * @param {!print_preview.Destination} destination Destination to print to.
+   * @param {boolean} openPdfInPreview Whether this print request is to open
+   *     the PDF in Preview app (Mac only).
+   * @param {boolean} showSystemDialog Whether this print request is to show
+   *     the system dialog.
    * @return {string} Serialized print ticket.
    */
-  createPrintTicket: function(destination) {
+  createPrintTicket: function(destination, openPdfInPreview, showSystemDialog) {
     const dpi = /** @type {{horizontal_dpi: (number | undefined),
                             vertical_dpi: (number | undefined),
                             vendor_id: (number | undefined)}} */ (
@@ -536,7 +540,7 @@
       fitToPageEnabled: this.getSettingValue('fitToPage'),
       pageWidth: this.documentInfo.pageSize.width,
       pageHeight: this.documentInfo.pageSize.height,
-      showSystemDialog: false,
+      showSystemDialog: showSystemDialog,
     };
 
     // Set 'cloudPrintID' only if the destination is not local.
@@ -561,6 +565,10 @@
       ticket.ticket = this.createCloudJobTicket(destination);
       ticket.capabilities = JSON.stringify(destination.capabilities);
     }
+
+    if (openPdfInPreview)
+      ticket.OpenPDFInPreview = true;
+
     return JSON.stringify(ticket);
   },
 
diff --git a/chrome/browser/resources/print_preview/new/preview_area.js b/chrome/browser/resources/print_preview/new/preview_area.js
index 40147603b..eabcf12 100644
--- a/chrome/browser/resources/print_preview/new/preview_area.js
+++ b/chrome/browser/resources/print_preview/new/preview_area.js
@@ -67,6 +67,18 @@
     /** @type {?print_preview.MeasurementSystem} */
     measurementSystem: Object,
 
+    /** @private {boolean} Whether the plugin is loaded */
+    pluginLoaded_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /** @private {boolean} Whether the document is ready */
+    documentReady_: {
+      type: Boolean,
+      value: false,
+    },
+
     /** @private {string} */
     previewState_: {
       type: String,
@@ -78,7 +90,8 @@
     previewLoaded_: {
       type: Boolean,
       notify: true,
-      computed: 'computePreviewLoaded_(previewState_)',
+      computed: 'computePreviewLoaded_(' +
+          'previewState_, pluginLoaded_, documentReady_)',
     },
   },
 
@@ -108,12 +121,6 @@
   /** @private {HTMLEmbedElement|print_preview_new.PDFPlugin} */
   plugin_: null,
 
-  /** @private {boolean} Whether the plugin is loaded */
-  pluginLoaded_: false,
-
-  /** @private {boolean} Whether the document is ready */
-  documentReady_: false,
-
   /** @override */
   attached: function() {
     this.nativeLayer_ = print_preview.NativeLayer.getInstance();
@@ -139,7 +146,9 @@
    * @private
    */
   computePreviewLoaded_: function() {
-    return this.previewState_ == PreviewAreaState_.DISPLAY_PREVIEW;
+    return this.previewState_ == PreviewAreaState_.DISPLAY_PREVIEW ||
+        (this.documentReady_ && this.pluginLoaded_ &&
+         this.previewState_ == PreviewAreaState_.OPEN_IN_PREVIEW);
   },
 
   /** @return {boolean} Whether the preview is loaded. */
@@ -237,7 +246,7 @@
     if (this.previewState_ == PreviewAreaState_.LOADING)
       return this.i18n('loading');
     if (this.previewState_ == PreviewAreaState_.OPEN_IN_PREVIEW)
-      return this.i18n('openPdfInPreview');
+      return this.i18n('openingPDFInPreview');
     if (this.previewState_ == PreviewAreaState_.INVALID_SETTINGS)
       return this.i18n('invalidSettings');
     if (this.previewState_ == PreviewAreaState_.PREVIEW_FAILED)
@@ -255,7 +264,8 @@
             this.onPreviewStart_(previewUid, -1);
           this.documentReady_ = true;
           if (this.pluginLoaded_) {
-            this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW;
+            if (this.previewState_ != PreviewAreaState_.OPEN_IN_PREVIEW)
+              this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW;
             this.fire('preview-loaded');
           }
         },
@@ -291,6 +301,14 @@
     }
   },
 
+  // <if expr="macosx">
+  /** Set the preview state to display the "opening in preview" message. */
+  setOpeningPdfInPreview: function() {
+    assert(cr.isMac);
+    this.previewState_ = PreviewAreaState_.OPEN_IN_PREVIEW;
+  },
+  // </if>
+
   /**
    * @param {number} previewUid The unique identifier of the preview.
    * @param {number} index The index of the page to preview.
@@ -375,7 +393,8 @@
   onPluginLoad_: function() {
     this.pluginLoaded_ = true;
     if (this.documentReady_) {
-      this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW;
+      if (this.previewState_ != PreviewAreaState_.OPEN_IN_PREVIEW)
+        this.previewState_ = PreviewAreaState_.DISPLAY_PREVIEW;
       this.fire('preview-loaded');
     }
   },
diff --git a/chrome/browser/resources/print_preview/new/state.js b/chrome/browser/resources/print_preview/new/state.js
index 26d1a63..c2baae3e 100644
--- a/chrome/browser/resources/print_preview/new/state.js
+++ b/chrome/browser/resources/print_preview/new/state.js
@@ -10,10 +10,11 @@
   READY: 1,
   HIDDEN: 2,
   PRINTING: 3,
-  INVALID_TICKET: 4,
-  INVALID_PRINTER: 5,
-  FATAL_ERROR: 6,
-  CLOSING: 7,
+  SYSTEM_DIALOG: 4,
+  INVALID_TICKET: 5,
+  INVALID_PRINTER: 6,
+  FATAL_ERROR: 7,
+  CLOSING: 8,
 };
 
 Polymer({
@@ -51,6 +52,9 @@
             this.state == print_preview_new.State.READY ||
             this.state == print_preview_new.State.HIDDEN);
         break;
+      case (print_preview_new.State.SYSTEM_DIALOG):
+        assert(this.state == print_preview_new.State.READY);
+        break;
       case (print_preview_new.State.INVALID_TICKET):
         assert(this.state == print_preview_new.State.READY);
         break;
diff --git a/chrome/browser/resources/print_preview/print_preview_resources.grd b/chrome/browser/resources/print_preview/print_preview_resources.grd
index af0175ff..e2d2d5c 100644
--- a/chrome/browser/resources/print_preview/print_preview_resources.grd
+++ b/chrome/browser/resources/print_preview/print_preview_resources.grd
@@ -19,7 +19,8 @@
                  type="chrome_html" />
       <structure name="IDR_PRINT_PREVIEW_NEW_APP_HTML"
                  file="new/app.html"
-                 type="chrome_html" />
+                 type="chrome_html"
+                 preprocess="true" />
       <structure name="IDR_PRINT_PREVIEW_NEW_APP_JS"
                  file="new/app.js"
                  type="chrome_html" />
@@ -269,6 +270,16 @@
       <structure name="IDR_PRINT_PREVIEW_NEW_NUMBER_SETTINGS_SECTION_JS"
                  file="new/number_settings_section.js"
                  type="chrome_html" />
+      <if expr="not chromeos">
+        <structure name="IDR_PRINT_PREVIEW_NEW_LINK_CONTAINER_HTML"
+                   file="new/link_container.html"
+                   type="chrome_html"
+                   preprocess="true" />
+        <structure name="IDR_PRINT_PREVIEW_NEW_LINK_CONTAINER_JS"
+                   file="new/link_container.js"
+                   type="chrome_html"
+                   preprocess="true" />
+      </if>
       <structure name="IDR_PRINT_PREVIEW_NEW_DESTINATION_DIALOG_HTML"
                  file="new/destination_dialog.html"
                  type="chrome_html" />
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chrome/browser/resources/settings/a11y_page/a11y_page.html
index 3d78f07..6b4fd80 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.html
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.html
@@ -10,6 +10,7 @@
 <link rel="import" href="../controls/settings_toggle_button.html">
 <link rel="import" href="../settings_page/settings_animated_pages.html">
 <link rel="import" href="../settings_page/settings_subpage.html">
+<link rel="import" href="tts_subpage.html">
 </if>
 
 <dom-module id="settings-a11y-page">
@@ -46,6 +47,16 @@
           </settings-manage-a11y-page>
         </settings-subpage>
       </template>
+      <template is="dom-if" if="[[showExperimentalFeatures_]]">
+        <template is="dom-if" route-path="/manageAccessibility/tts">
+          <settings-subpage
+              associated-control="[[$$('#subpage-trigger')]]"
+              page-title="$i18n{manageTtsSettings}">
+            <settings-tts-subpage>
+            </settings-tts-subpage>
+          </settings-subpage>
+        </template>
+      </template>
     </settings-animated-pages>
 </if>
 
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.js b/chrome/browser/resources/settings/a11y_page/a11y_page.js
index 062fad8..9b137d99 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.js
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.js
@@ -43,6 +43,20 @@
         return map;
       },
     },
+
+    // <if expr="chromeos">
+    /**
+     * Whether to show experimental accessibility features.
+     * Only used in Chrome OS.
+     * @private {boolean}
+     */
+    showExperimentalFeatures_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('showExperimentalA11yFeatures');
+      },
+    },
+    // </if>
   },
 
   // <if expr="chromeos">
diff --git a/chrome/browser/resources/settings/a11y_page/compiled_resources2.gyp b/chrome/browser/resources/settings/a11y_page/compiled_resources2.gyp
index bbc7a563..8a99d30d 100644
--- a/chrome/browser/resources/settings/a11y_page/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/a11y_page/compiled_resources2.gyp
@@ -6,6 +6,7 @@
     {
       'target_name': 'a11y_page',
       'dependencies': [
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
         '../compiled_resources2.gyp:route',
         '../settings_page/compiled_resources2.gyp:settings_animated_pages',
       ],
@@ -20,5 +21,14 @@
       ],
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
+    {
+      'target_name': 'tts_subpage',
+      'dependencies': [
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
+        '../settings_page/compiled_resources2.gyp:settings_animated_pages',
+        '../compiled_resources2.gyp:route',
+      ],
+       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
+    },
   ],
 }
diff --git a/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html b/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html
index 8c44d74..d4551ef 100644
--- a/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html
+++ b/chrome/browser/resources/settings/a11y_page/manage_a11y_page.html
@@ -8,6 +8,7 @@
 <link rel="import" href="../route.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="../settings_vars_css.html">
+<link rel="import" href="tts_subpage.html">
 
 <dom-module id="settings-manage-a11y-page">
   <template>
@@ -67,6 +68,21 @@
         </paper-icon-button-light>
       </div>
     </iron-collapse>
+    <template is="dom-if" if="[[showExperimentalFeatures_]]">
+      <div class="settings-box two-line" on-click="onManageTtsSettingsTap_"
+          actionable>
+        <div class="start">
+          $i18n{manageTtsSettings}
+          <div class="secondary" id="appearanceSettingsSecondary">
+            $i18n{ttsSettingsLinkDescription}
+          </div>
+        </div>
+        <paper-icon-button-light class="subpage-arrow">
+          <button aria-label="$i18n{manageTtsSettings}"
+              aria-describedby="appearanceSettingsSecondary"></button>
+        </paper-icon-button-light>
+      </div>
+    </template>
 
     <h2>$i18n{displayHeading}</h2>
     <settings-toggle-button class="first"
diff --git a/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js
index 090a8a7..88fb1bb 100644
--- a/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/a11y_page/manage_a11y_page.js
@@ -99,6 +99,11 @@
   },
 
   /** @private */
+  onManageTtsSettingsTap_: function() {
+    settings.navigateTo(settings.routes.MANAGE_TTS_SETTINGS);
+  },
+
+  /** @private */
   onChromeVoxSettingsTap_: function() {
     chrome.send('showChromeVoxSettings');
   },
diff --git a/chrome/browser/resources/settings/a11y_page/tts_subpage.html b/chrome/browser/resources/settings/a11y_page/tts_subpage.html
new file mode 100644
index 0000000..5ade21f2
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/tts_subpage.html
@@ -0,0 +1,48 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="../controls/settings_slider.html">
+<link rel="import" href="../i18n_setup.html">
+<link rel="import" href="../settings_shared_css.html">
+<link rel="import" href="../settings_vars_css.html">
+
+<dom-module id="settings-tts-subpage">
+  <template>
+    <style include="settings-shared">
+      h2 {
+        -webkit-padding-start: var(--settings-box-row-padding);
+      }
+
+      .settings-box {
+        -webkit-margin-end: var(--settings-box-row-padding);
+        -webkit-margin-start: var(--settings-box-row-indent);
+        -webkit-padding-end: 0;
+        -webkit-padding-start: 0;
+      }
+    </style>
+    <h2>$i18n{textToSpeechVoices}</h2>
+    <div class="settings-box block first">
+    </div>
+
+    <h2>$i18n{textToSpeechProperties}</h2>
+    <div class="settings-box first">
+      <div class="start">$i18n{textToSpeechRate}</div>
+      <settings-slider
+          pref="{{prefs.settings.tts.speech_rate}}"
+          min="0.2" max="8.0">
+      </settings-slider>
+    </div>
+    <div class="settings-box continuation">
+      <div class="start">$i18n{textToSpeechPitch}</div>
+      <settings-slider
+          pref="{{prefs.settings.tts.speech_pitch}}"
+          min="0.2" max="3.0">
+      </settings-slider>
+    </div>
+
+    <h2>$i18n{textToSpeechEngines}</h2>
+    <div class="settings-box block first">
+      <a href="">$i18n{textToSpeechInstallEngines}</a>
+    </div>
+  </template>
+  <script src="tts_subpage.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/a11y_page/tts_subpage.js b/chrome/browser/resources/settings/a11y_page/tts_subpage.js
new file mode 100644
index 0000000..22144bc
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/tts_subpage.js
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview 'tts-subpage' is the collapsible section containing
+ * text-to-speech settings.
+ */
+
+Polymer({
+  is: 'settings-tts-subpage',
+
+  properties: {
+    /**
+     * Preferences state.
+     */
+    prefs: {
+      type: Object,
+      notify: true,
+    },
+  },
+
+});
diff --git a/chrome/browser/resources/settings/device_page/compiled_resources2.gyp b/chrome/browser/resources/settings/device_page/compiled_resources2.gyp
index 9162ea9..0962808 100644
--- a/chrome/browser/resources/settings/device_page/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/device_page/compiled_resources2.gyp
@@ -65,7 +65,8 @@
         '<(EXTERNS_GYP):settings_private',
         '<(EXTERNS_GYP):system_display',
         '<(INTERFACES_GYP):system_display_interface',
-        'display_layout'
+        'display_layout',
+        'display_size_slider'
       ],
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
@@ -118,6 +119,19 @@
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
     },
     {
+      'target_name': 'display_size_slider',
+      'dependencies': [
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-progress/compiled_resources2.gyp:paper-progress-extracted',
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior/compiled_resources2.gyp:iron-a11y-keys-behavior-extracted',
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-resizable-behavior/compiled_resources2.gyp:iron-resizable-behavior-extracted',
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-range-behavior/compiled_resources2.gyp:iron-range-behavior-extracted',
+        '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-behaviors/compiled_resources2.gyp:paper-inky-focus-behavior-extracted',
+        '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_pref_behavior',
+        '../prefs/compiled_resources2.gyp:prefs_behavior',
+      ],
+      'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
+    },
+    {
       'target_name': 'power',
       'dependencies': [
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior',
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html
index 58b9a664a..acd9ad6a 100644
--- a/chrome/browser/resources/settings/device_page/display.html
+++ b/chrome/browser/resources/settings/device_page/display.html
@@ -12,6 +12,7 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-tabs/paper-tabs.html">
 <link rel="import" href="display_layout.html">
 <link rel="import" href="display_overscan_dialog.html">
+<link rel="import" href="display_size_slider.html">
 <link rel="import" href="night_light_slider.html">
 <link rel="import" href="../controls/settings_dropdown_menu.html">
 <link rel="import" href="../controls/settings_slider.html">
@@ -161,16 +162,13 @@
         <div class="settings-box indented two-line">
           <div class="start text-area layout vertical">
             <div>$i18n{displayZoomTitle}</div>
-            <div class="secondary self-start">
-              [[getDisplayZoomText_(selectedDisplay, selectedZoomPref_.value)]]
-            </div>
+            <div class="secondary self-start">$i18n{displayZoomSublabel}</div>
           </div>
-          <settings-slider tick-values="[[zoomValues_]]"
-              disabled="[[!enableDisplayZoomSlider_(selectedDisplay)]]"
-              pref="{{selectedZoomPref_}}" on-change="onSelectedZoomChange_"
-              label-min="$i18n{displaySizeSliderMinLabel}"
-              label-max="$i18n{displaySizeSliderMaxLabel}">
-          </settings-slider>
+          <display-size-slider id="displaySizeSlider"
+              ticks="[[zoomValues_]]" pref="{{selectedZoomPref_}}"
+              min-label="$i18n{displaySizeSliderMinLabel}"
+              max-label="$i18n{displaySizeSliderMaxLabel}">
+          </display-size-slider>
         </div>
 
         <!-- Drop down select menu for resolution -->
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index a95cba2..649b5b6 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -102,7 +102,7 @@
     /** @private {!Array<number>} Mode index values for slider. */
     modeValues_: Array,
 
-    /** @private {!Array<number>} Display zoom percentage values for slider */
+    /** @private {SliderTicks} Display zoom slider tick values. */
     zoomValues_: Array,
 
     /** @private {!DropdownMenuOptionList} */
@@ -183,6 +183,7 @@
     'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' +
         ' prefs.ash.night_light.enabled.*)',
     'onSelectedModeChange_(selectedModePref_.value)',
+    'onSelectedZoomChange_(selectedZoomPref_.value)',
   ],
 
   /** @private {number} Selected mode index received from chrome. */
@@ -315,32 +316,35 @@
    */
   getSelectedDisplayZoom_: function(selectedDisplay) {
     const selectedZoom = selectedDisplay.displayZoomFactor * 100;
-    let closestMatch = this.zoomValues_[0];
+    let closestMatch = this.zoomValues_[0].value;
     let minimumDiff = Math.abs(closestMatch - selectedZoom);
 
     for (let i = 0; i < this.zoomValues_.length; i++) {
-      const currentDiff = Math.abs(this.zoomValues_[i] - selectedZoom);
+      const currentDiff = Math.abs(this.zoomValues_[i].value - selectedZoom);
       if (currentDiff < minimumDiff) {
-        closestMatch = this.zoomValues_[i];
+        closestMatch = this.zoomValues_[i].value;
         minimumDiff = currentDiff;
       }
     }
 
-    return closestMatch;
+    return /** @type {number} */ (closestMatch);
   },
 
   /**
    * Given the display with the current display mode, this function lists all
-   * the display zoom values in percentage.
+   * the display zoom values and their labels to be used by the slider.
    * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {!Array<number>}
+   * @return {SliderTicks}
    */
   getZoomValues_: function(selectedDisplay) {
+    /** @type {SliderTicks} */
     let zoomValues = [];
     for (let i = 0; i < selectedDisplay.availableDisplayZoomFactors.length;
          i++) {
-      zoomValues.push(
-          Math.round(selectedDisplay.availableDisplayZoomFactors[i] * 100));
+      const value =
+          Math.round(selectedDisplay.availableDisplayZoomFactors[i] * 100);
+      const label = this.i18n('displayZoomValue', value.toString());
+      zoomValues.push({value: value, label: label});
     }
     return zoomValues;
   },
@@ -565,16 +569,6 @@
   },
 
   /**
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {string}
-   * @private
-   */
-  getDisplayZoomText_: function(selectedDisplay) {
-    return this.i18n(
-        'displayZoomValue', this.selectedZoomPref_.value.toString());
-  },
-
-  /**
    * @param {!{detail: string}} e |e.detail| is the id of the selected display.
    * @private
    */
@@ -663,10 +657,14 @@
    * @private
    */
   onSelectedZoomChange_: function() {
+    if (this.currentSelectedModeIndex_ == -1 || !this.selectedDisplay)
+      return;
+
     /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
       displayZoomFactor:
           /** @type {number} */ (this.selectedZoomPref_.value) / 100.0
     };
+
     settings.display.systemDisplayApi.setDisplayProperties(
         this.selectedDisplay.id, properties,
         this.setPropertiesCallback_.bind(this));
diff --git a/chrome/browser/resources/settings/device_page/display_size_slider.html b/chrome/browser/resources/settings/device_page/display_size_slider.html
new file mode 100644
index 0000000..d38f6dd
--- /dev/null
+++ b/chrome/browser/resources/settings/device_page/display_size_slider.html
@@ -0,0 +1,230 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-inky-focus-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-range-behavior/iron-range-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
+
+<dom-module id="display-size-slider">
+  <template>
+    <style>
+      :host {
+        /* Counteract the margin on #sliderContainer and match the margin from
+           settings-slider.html */
+        -webkit-margin-end: -16px;
+
+        cursor: default;
+        display: inline-flex;
+        font-weight: 500;
+        min-width: 200px;
+        text-align: center;
+        user-select: none;
+      }
+
+      /* focus shows the ripple */
+      :host(:focus) {
+        outline: none;
+      }
+
+      :host-context([dir='rtl']) #sliderContainer {
+        transform: scaleX(-1);
+      }
+
+      /* We dont want the text to be flipped in rtl */
+      :host-context([dir='rtl']) #labelText,
+      :host-context([dir='rtl']) #subLabelContainer {
+        transform: scaleX(-1);
+      }
+
+      #sliderContainer {
+        display: inline-table;
+        height: 32px;
+        margin-left: 16px;
+        margin-right: 16px;
+        position: relative;
+        width: 100%;
+      }
+
+      #sliderContainer:focus {
+        outline: 0;
+      }
+
+      #labelContainer {
+        bottom: 36px;
+        display: none;
+        height: 1.75em;
+        position: absolute;
+        width: inherit;
+        z-index: 10;
+      }
+
+      /* Show the label on keyboard focus and mouse hover */
+      .dragging #labelContainer,
+      :host(:focus) #labelContainer,
+      #sliderContainer:hover #labelContainer,
+      .hover #labelContainer {
+        display: block;
+      }
+
+      .label {
+        background: var(--google-blue-700);
+        border-radius: 14px;
+        color: white;
+        font-size: 12px;
+        left: 0;
+        line-height: 1.5em;
+        padding: 0 8px;
+        position: absolute;
+        text-align: center;
+        transform: translateX(-50%);
+        transition: margin-top 200ms cubic-bezier(0, 0, 0.2, 1);
+        vertical-align: middle;
+        width: auto;
+      }
+
+      .bar-container {
+        bottom: 0;
+        left: 0;
+        overflow: hidden;
+        position: absolute;
+        right: 0;
+        top: 0;
+      }
+
+      .slider-markers {
+        box-sizing: border-box;
+        height: 2px;
+        left: 0;
+        pointer-events: none;
+        position: absolute;
+        right: -1px;
+        top: 15px;
+        @apply --layout-horizontal;
+      }
+
+      .slider-marker {
+        @apply --layout-flex;
+      }
+      .slider-markers::after,
+      .slider-marker::after {
+        background-color: #000;
+        border-radius: 50%;
+        content: '';
+        display: block;
+        height: 2px;
+        margin-left: -1px;
+        width: 2px;
+      }
+
+      #sliderBar {
+        --paper-progress-container-color: var(--paper-grey-400);
+        --paper-progress-height: 2px;
+        background-color: transparent;
+        padding: 15px 0;
+        width: 100%;
+      }
+
+      .slider-knob {
+        height: 32px;
+        left: 0;
+        margin-left: -16px;
+        position: absolute;
+        top: 0;
+        width: 32px;
+      }
+
+      .slider-knob:focus {
+        outline: none;
+      }
+
+      .slider-knob-inner {
+        background-color: var(--google-blue-700);
+        border: 2px solid var(--google-blue-700);
+        border-radius: 50%;
+        box-sizing: border-box;
+        height: calc(100% - 20px);
+        margin: 10px;
+        transition-duration: 180ms;
+        transition-property: transform, background-color, border;
+        transition-timing-function: ease;
+        width: calc(100% - 20px);
+      }
+
+      .expand > .slider-knob > .slider-knob-inner {
+        transform: scale(1.5);
+      }
+
+      paper-ripple {
+        color: var(--google-blue-700);
+      }
+
+      #subLabelContainer {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        padding-top: 24px;
+        pointer-events: none;
+      }
+
+      #subLabelContainer > div {
+        font-size: 12px;
+        font-weight: normal;
+      }
+
+      paper-progress {
+        --paper-progress-active-color: var(--google-blue-700);
+        --paper-progress-disabled-active-color: var(--paper-grey-400);
+      }
+
+      /* Styles for disabled state */
+      #sliderContainer.disabled {
+        pointer-events: none;
+      }
+
+      .disabled > .slider-knob > .slider-knob-inner {
+        transform: scale3d(0.75, 0.75, 1);
+      }
+
+      #subLabelContainer[disabled] {
+        color: var(--paper-grey-400);
+      }
+    </style>
+    <div id="sliderContainer"
+        class$="[[getClassNames_(expand, disabled, dragging)]]"
+        on-mouseout="onMouseOut_">
+      <div id="labelContainer" hidden$="[[shouldShowLabel_(ticks, index)]]"
+          aria-label="[[getLabelForIndex_(ticks, index)]]">
+        <div id="label" class="label">
+          <div id="labelText">
+            [[getLabelForIndex_(ticks, index)]]
+          </div>
+        </div>
+      </div>
+      <div class="bar-container">
+        <paper-progress id="sliderBar" disabled$="[[disabled]]"
+            on-down="onBarDown_" on-up="onBarUp_" on-track="knobTrack_"
+            value="[[index]]" min="[[min]]" max="[[max]]">
+        </paper-progress>
+      </div>
+
+      <template is="dom-if" if="[[!disabled]]">
+        <div class="slider-markers">
+          <template is="dom-repeat" items="[[markers]]">
+            <div class="slider-marker"></div>
+          </template>
+        </div>
+      </template>
+
+      <div id="sliderKnob" class="slider-knob" on-down="knobDown_"
+          on-up="resetKnob_" on-track="knobTrack_">
+        <div class="slider-knob-inner"></div>
+      </div>
+      <div id="subLabelContainer" disabled$="[[disabled]]">
+        <div id="subLabelMin">[[minLabel]]</div>
+        <div id="subLabelMax">[[maxLabel]]</div>
+      </div>
+    </div>
+  </template>
+  <script src="display_size_slider.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/device_page/display_size_slider.js b/chrome/browser/resources/settings/device_page/display_size_slider.js
new file mode 100644
index 0000000..bba3debe
--- /dev/null
+++ b/chrome/browser/resources/settings/device_page/display_size_slider.js
@@ -0,0 +1,473 @@
+// 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
+ * display-size-slider is used to change the value of a pref via a slider
+ * control. This specific slider is used instead of the settings-slider due to
+ * its implementation of the tool tip that displays the current slider value.
+ */
+
+/**
+ * The |value| is the corresponding value that the current slider tick is
+ * assocated with. The string |label| is shown in the ui as the label for the
+ * current slider value.
+ * @typedef {{
+ *   value: (number|string|boolean),
+ *   label: string
+ * }}
+ */
+let SliderTick;
+
+/**
+ * @typedef {!Array<SliderTick>}
+ */
+let SliderTicks;
+
+(function() {
+
+Polymer({
+  is: 'display-size-slider',
+
+  behaviors: [
+    CrPolicyPrefBehavior,
+    Polymer.IronA11yKeysBehavior,
+    Polymer.IronRangeBehavior,
+    Polymer.IronResizableBehavior,
+    Polymer.PaperInkyFocusBehavior,
+    PrefsBehavior,
+  ],
+
+  properties: {
+    /** The slider is disabled if true. */
+    disabled: {type: Boolean, value: false, readOnly: true},
+
+    /** True when the user is dragging the slider knob. */
+    dragging: {type: Boolean, value: false, readOnly: true},
+
+    /** If true, the knob is expanded. */
+    expand: {type: Boolean, value: false, readOnly: true},
+
+    /** @type {number} The index for the current slider value in |ticks|. */
+    index: {type: Number, value: 0, readOnly: true},
+
+    /** @type {string} The label for the minimum slider value */
+    minLabel: {type: String, value: ''},
+
+    /** @type {string} The label for the maximum slider value */
+    maxLabel: {type: String, value: ''},
+
+    /**
+     * Each item in the array represents a UI element corresponding to a tick
+     * value.
+     * @type {!Array<number>}
+     */
+    markers: {
+      type: Array,
+      readOnly: true,
+      value: function() {
+        return [];
+      }
+    },
+
+    /** @type {!chrome.settingsPrivate.PrefObject} */
+    pref: Object,
+
+    /**
+     * The data associated with each tick on the slider. Each element in the
+     * array contains a value and the label corresponding to that value.
+     * @type {SliderTicks}
+     */
+    ticks: {type: Array, value: []},
+  },
+
+  listeners: {},
+
+  observers: [
+    'updateIndex_(pref.value)', 'updateKnobAndLabel_(index, disabled)',
+    'updateSliderParams_(ticks.*)', 'updateMarkers_(min, max)',
+    'updateDisabled_(ticks.*, pref.*)'
+  ],
+
+  hostAttributes: {role: 'slider', tabindex: 0},
+
+  keyBindings: {
+    'left': 'leftKeyPress_',
+    'right': 'rightKeyPress_',
+    'up': 'increment_',
+    'down': 'decrement_',
+    'pagedown home': 'resetToMinIndex_',
+    'pageup end': 'resetToMaxIndex_'
+  },
+
+  get _isRTL() {
+    if (this.__isRTL === undefined) {
+      this.__isRTL = window.getComputedStyle(this)['direction'] === 'rtl';
+    }
+    return this.__isRTL;
+  },
+
+  /**
+   * Clamps the value of |newIndex| to IronRangeBehavior's max/min bounds and
+   * updates the value for |index|.
+   * @param {number} newIndex The new value for index that needs to be set.
+   * @private
+   */
+  clampAndSetIndex_: function(newIndex) {
+    newIndex = this.clampToRange_(newIndex, this.min, this.max);
+    if (newIndex != this.index)
+      this._setIndex(newIndex);
+  },
+
+  /**
+   * Clamps the value of |newIndex| to IronRangeBehavior's max/min bounds and
+   * updates the value of |pref| to this clamped value.
+   * @param {number} newIndex The new value for index that needs to be set.
+   * @private
+   */
+  clampIndexAndUpdatePref_: function(newIndex) {
+    newIndex = this.clampToRange_(newIndex, this.min, this.max);
+    if (this.ticks[newIndex].value != this.pref.value)
+      this.set('pref.value', this.ticks[newIndex].value);
+  },
+
+  /**
+   * Clamps and returns the given |value| within the |min| and |max| range.
+   * @param {number} value The number to clamp.
+   * @param {number} min The minimum value in range. Any value below this will
+   *     be clamped to |min|.
+   * @param {number} max The maximum value in range. Any value above this will
+   *     be clamped to |max|.
+   * @return {number}
+   * @private
+   */
+  clampToRange_: function(value, min, max) {
+    return Math.max(Math.min(value, max), min);
+  },
+
+  /**
+   * Overrides _createRipple from PaperInkyFocusBehavior to set the ripple
+   * container as the slider knob before creating the ripple animation. Without
+   * this the PaperInkyFocusBehavior does not know where to create the ripple
+   * animation.
+   * @private
+   */
+  _createRipple: function() {
+    this._rippleContainer = this.$.sliderKnob;
+    return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this);
+  },
+
+  /** @private Safely decrements the slider index value by 1 and updates pref */
+  decrement_: function() {
+    this.clampIndexAndUpdatePref_(this.index - 1);
+  },
+
+  /**
+   * Overrides _focusChanged from PaperInkyFocusBehavior to create a ripple only
+   * on a focus received via a keyboard. Hide the ripple when the focus was not
+   * triggered via a keyboard event.
+   * @private
+   */
+  _focusedChanged: function(receivedFocusFromKeyboard) {
+    if (receivedFocusFromKeyboard) {
+      this.ensureRipple();
+    }
+    if (this.hasRipple()) {
+      // note, ripple must be un-hidden prior to setting `holdDown`
+      if (receivedFocusFromKeyboard) {
+        this._ripple.style.display = '';
+      } else {
+        this._ripple.style.display = 'none';
+      }
+      this._ripple.holdDown = receivedFocusFromKeyboard;
+    }
+  },
+
+  /**
+   * Returns a string concatenated list of class names based on whether the
+   * corresponding properties are set.
+   * @return {string}
+   */
+  getClassNames_: function() {
+    return this.mergeClasses_({
+      disabled: this.disabled,
+      expand: this.expand,
+      dragging: this.dragging,
+    });
+  },
+
+  /**
+   * Returns the current label for the selected slider value.
+   * @param {SliderTicks} ticks Slider label and corresponding value for each
+   *    tick.
+   * @param {number} index Index of the slider tick with the desired label.
+   * @return {string}
+   */
+  getLabelForIndex_: function(ticks, index) {
+    if (!ticks || ticks.length == 0 || index >= ticks.length)
+      return '';
+    return ticks[index].label;
+  },
+
+  /** @private Safely increments the slider index value by 1 and updates pref */
+  increment_: function() {
+    this.clampIndexAndUpdatePref_(this.index + 1);
+  },
+
+  /**
+   * Handles the mouse down event for the slider knob.
+   * @private
+   */
+  knobDown_: function(event) {
+    this._setExpand(true);
+
+    // cancel selection
+    event.preventDefault();
+
+    // Without this the paper ripple is displayed on click.
+    this.focus();
+  },
+
+  /**
+   * Handles the mouse drag and drop event for slider knob from start to end.
+   * @param {Event} event
+   * @private
+   */
+  knobTrack_: function(event) {
+    event.stopPropagation();
+    switch (event.detail.state) {
+      case 'start':
+        this.knobTrackStart_();
+        break;
+      case 'track':
+        this.knobTrackDrag_(event);
+        break;
+      case 'end':
+        this.knobTrackEnd_(event);
+        break;
+    }
+  },
+
+  /**
+   * Handles the drag event for the slider knob.
+   * @param {!Event} event
+   * @private
+   */
+  knobTrackDrag_: function(event) {
+    // Distance travelled during mouse drag.
+    const dx = event.detail.dx;
+
+    // Total width of the progress bar in CSS pixels.
+    const totalWidth = this.$.sliderBar.offsetWidth;
+
+    // Distance between 2 consecutive tick markers.
+    const tickWidth = totalWidth / (this.ticks.length - 1);
+
+    // Number of ticks covered by |dx|.
+    let dxInTicks = Math.round(dx / tickWidth);
+
+    if (this._isRTL)
+      dxInTicks *= -1;
+
+    let nextIndex = this.startIndex_ + dxInTicks;
+
+    this.clampAndSetIndex_(nextIndex);
+  },
+
+  /**
+   * Handles the end of slider knob drag event.
+   * @param {!Event} event
+   * @private
+   */
+  knobTrackEnd_: function(event) {
+    this._setDragging(false);
+
+    // Update the pref once user stops dragging and releases mouse.
+    if (this.index != this.startIndex_)
+      this.clampIndexAndUpdatePref_(this.index);
+  },
+
+  /**
+   * Handles the start of the slider knob drag event.
+   * @private
+   */
+  knobTrackStart_: function() {
+    this.startIndex_ = this.index;
+    this._setDragging(true);
+  },
+
+  /** @private Handles the 'left' key press. */
+  leftKeyPress_: function() {
+    this._isRTL ? this.increment_() : this.decrement_();
+  },
+
+  /**
+   * Returns a concatenated string of classnames based on the boolean table
+   * |classes|.
+   * @param {!Object<string, boolean>} classes An object mapping between
+   *     classnames and boolean. The boolean for the corresponding classname is
+   *     true if that classname needs to be present in the returned string.
+   * @return {string}
+   * @private
+   */
+  mergeClasses_: function(classes) {
+    return Object.keys(classes)
+        .filter(function(className) {
+          return classes[className];
+        })
+        .join(' ');
+  },
+
+  /**
+   * Handles the event where the user clicks or taps on the slider bar directly.
+   * @param {!Event} event
+   * @private
+   */
+  onBarDown_: function(event) {
+    const barWidth = this.$.sliderBar.offsetWidth;
+    const barOriginX = this.$.sliderBar.getBoundingClientRect().left;
+    let eventOffsetFromOriginX = event.detail.x - barOriginX;
+    if (this._isRTL)
+      eventOffsetFromOriginX = barWidth - eventOffsetFromOriginX;
+    const tickWidth = barWidth / (this.ticks.length - 1);
+    let newTickIndex = Math.round(eventOffsetFromOriginX / tickWidth);
+    this.startIndex_ = this.index;
+
+    // Update the index but dont update the pref until mouse is released.
+    this.clampAndSetIndex_(newTickIndex);
+  },
+
+  /**
+   * Handles the event of mouse up from the slider bar.
+   * @private
+   */
+  onBarUp_: function() {
+    if (this.startIndex_ != this.index)
+      this.clampIndexAndUpdatePref_(this.index);
+  },
+
+  /**
+   * Handles the event of the mouse moving out of the slider container.
+   * @private
+   */
+  onMouseOut_: function() {
+    // This is needed to hide the label after the user has clicked on the slider
+    this.blur();
+  },
+
+  /**
+   * Resets the knob back to its default state.
+   * @private
+   */
+  resetKnob_: function() {
+    this._setExpand(false);
+  },
+
+  /** @private Handles the 'right' key press. */
+  rightKeyPress_: function() {
+    this._isRTL ? this.decrement_() : this.increment_();
+  },
+
+  /** @private Handles the 'end' and 'page up' key press. */
+  resetToMaxIndex_: function() {
+    this.clampIndexAndUpdatePref_(this.max);
+  },
+
+  /** @private Handles the 'home' and 'page down' key press. */
+  resetToMinIndex_: function() {
+    this.clampIndexAndUpdatePref_(this.min);
+  },
+
+  /**
+   * Returs true if the label needs to be displayed to the user. If the |label|
+   * field for the current selected slider tick represented by |index| is not
+   * set, then this returns false.
+   * @param {SliderTicks} ticks Info related to the slider ticks.
+   * @param {number} index Index of the current selected slider value.
+   * @return {boolean}
+   */
+  shouldShowLabel_: function(ticks, index) {
+    if (!ticks || ticks.length == 0)
+      return false;
+    if (index < 0 || index >= ticks.length)
+      return false;
+    return (!!ticks[index].label && ticks[index].label.length != 0);
+  },
+
+  /**
+   * Sets the disabled property if there are no tick values or if the pref
+   * cannot be modified by the user.
+   * @private
+   */
+  updateDisabled_: function() {
+    let disabled = false;
+
+    // Disabled if no tick values are set for the slider.
+    if (!this.ticks || this.ticks.length <= 1)
+      disabled |= true;
+
+
+    // Disabled if the pref cannot be modified.
+    disabled |= this.isPrefEnforced();
+
+    this._setDisabled(!!disabled);
+  },
+
+  /**
+   * Updates the value for |index| based on the current set value of |pref|.
+   * @private
+   */
+  updateIndex_: function() {
+    if (!this.ticks || this.ticks.length == 0)
+      return;
+    if (!this.pref)
+      return;
+    for (let i = 0; i < this.ticks.length; i++)
+      if (this.ticks[i].value == this.pref.value)
+        this._setIndex(i);
+  },
+
+  /**
+   * Updates the knob position based on the the value of progress indicator.
+   * @private
+   */
+  updateKnobAndLabel_: function() {
+    if (this.disabled) {
+      this.$.sliderKnob.style.left = '0%';
+      this.$.label.style.left = '0%';
+      return;
+    }
+    this._setRatio(this._calcRatio(this.index) * 100);
+
+    this.$.sliderKnob.style.left = this.ratio + '%';
+    this.$.label.style.left = this.ratio + '%';
+  },
+
+  /**
+   * Initializes the |markers| array based on the number of ticks.
+   * @private
+   */
+  updateMarkers_: function(min, max) {
+    let steps = Math.round((max - min) / this.step);
+    if (steps < 0 || !isFinite(steps))
+      steps = 0;
+    this._setMarkers(new Array(steps));
+  },
+
+  /**
+   * Updates the min and max possible values for the slider.
+   * @private
+   */
+  updateSliderParams_: function() {
+    this.min = 0;
+    if (this.ticks.length == 0) {
+      this.max = 0;
+      return;
+    }
+    this.max = this.ticks.length - 1;
+    this.updateIndex_();
+  },
+});
+
+})();
diff --git a/chrome/browser/resources/settings/languages_page/languages.js b/chrome/browser/resources/settings/languages_page/languages.js
index c9f2606..e0d77b5 100644
--- a/chrome/browser/resources/settings/languages_page/languages.js
+++ b/chrome/browser/resources/settings/languages_page/languages.js
@@ -156,6 +156,11 @@
   /** @private {?Function} */
   boundOnInputMethodChanged_: null,
 
+  // <if expr="not is_macosx">
+  /** @private {?Function} */
+  boundOnSpellcheckDictionariesChanged_: null,
+  // </if>
+
   /** @private {?settings.LanguagesBrowserProxy} */
   browserProxy_: null,
 
@@ -217,14 +222,24 @@
 
     Promise.all(promises).then(results => {
       if (!this.isConnected) {
-        // Return early if this element was detached from the DOM before this
-        // async callback executes (can happen during testing).
+        // Return early if this element was detached from the DOM before
+        // this async callback executes (can happen during testing).
         return;
       }
 
       // TODO(dpapad): Cleanup this code. It uses results[3] and results[4]
       // which only exist for ChromeOS.
       this.createModel_(results[1], results[2], results[3], results[4]);
+
+      // <if expr="not is_macosx">
+      this.boundOnSpellcheckDictionariesChanged_ =
+          this.onSpellcheckDictionariesChanged_.bind(this);
+      this.languageSettingsPrivate_.onSpellcheckDictionariesChanged.addListener(
+          this.boundOnSpellcheckDictionariesChanged_);
+      this.languageSettingsPrivate_.getSpellcheckDictionaryStatuses(
+          this.boundOnSpellcheckDictionariesChanged_);
+      // </if>
+
       this.resolver_.resolve();
     });
 
@@ -242,6 +257,14 @@
           assert(this.boundOnInputMethodChanged_));
       this.boundOnInputMethodChanged_ = null;
     }
+
+    // <if expr="not is_macosx">
+    if (this.boundOnSpellcheckDictionariesChanged_) {
+      this.languageSettingsPrivate_.onSpellcheckDictionariesChanged
+          .removeListener(this.boundOnSpellcheckDictionariesChanged_);
+      this.boundOnSpellcheckDictionariesChanged_ = null;
+    }
+    // </if>
   },
 
   /**
@@ -269,6 +292,13 @@
       this.enabledLanguageSet_.add(enabledLanguageStates[i].language.code);
 
     this.set('languages.enabled', enabledLanguageStates);
+
+    // <if expr="not is_macosx">
+    if (this.boundOnSpellcheckDictionariesChanged_) {
+      this.languageSettingsPrivate_.getSpellcheckDictionaryStatuses(
+          this.boundOnSpellcheckDictionariesChanged_);
+    }
+    // </if>
   },
 
   /**
@@ -314,7 +344,11 @@
     for (let i = 0; i < spellCheckForcedDictionaries.length; i++) {
       const code = spellCheckForcedDictionaries[i];
       if (!enabledSet.has(code) && this.supportedLanguageMap_.has(code)) {
-        forcedLanguages.push(this.supportedLanguageMap_.get(code));
+        forcedLanguages.push({
+          language: this.supportedLanguageMap_.get(code),
+          isManaged: true,
+          downloadDictionaryFailureCount: 0,
+        });
       }
     }
     return forcedLanguages;
@@ -470,11 +504,52 @@
           translateCode != translateTarget &&
           (!prospectiveUILanguage || code != prospectiveUILanguage);
       languageState.isManaged = !!spellCheckForcedSet.has(code);
+      languageState.downloadDictionaryFailureCount = 0;
       enabledLanguageStates.push(languageState);
     }
     return enabledLanguageStates;
   },
 
+  // <if expr="not is_macosx">
+  /**
+   * Updates the dictionary download status for languages in
+   * |this.languages.enabled| and |this.languages.forcedSpellCheckLanguages| in
+   * order to track the number of times a spell check dictionary download has
+   * failed.
+   * @param {!Array<!chrome.languageSettingsPrivate.SpellcheckDictionaryStatus>}
+   *     statuses
+   * @private
+   */
+  onSpellcheckDictionariesChanged_: function(statuses) {
+    const statusMap = new Map();
+    statuses.forEach(status => {
+      statusMap.set(status.languageCode, status);
+    });
+
+    ['enabled', 'forcedSpellCheckLanguages'].forEach(collectionName => {
+      this.languages[collectionName].forEach((languageState, index) => {
+        const status = statusMap.get(languageState.language.code);
+        if (!status)
+          return;
+
+        const previousStatus = languageState.downloadDictionaryStatus;
+        const keyPrefix = `languages.${collectionName}.${index}`;
+        this.set(`${keyPrefix}.downloadDictionaryStatus`, status);
+
+        const failureCountKey = `${keyPrefix}.downloadDictionaryFailureCount`;
+        if (status.downloadFailed &&
+            !(previousStatus && previousStatus.downloadFailed)) {
+          const failureCount = languageState.downloadDictionaryFailureCount + 1;
+          this.set(failureCountKey, failureCount);
+        } else if (
+            status.isReady && !(previousStatus && previousStatus.isReady)) {
+          this.set(failureCountKey, 0);
+        }
+      });
+    });
+  },
+  // </if>
+
   /**
    * Returns a list of enabled input methods.
    * @return {!Array<!chrome.languageSettingsPrivate.InputMethod>}
@@ -770,6 +845,14 @@
     return this.supportedLanguageMap_.get(languageCode);
   },
 
+  /**
+   * Retries downloading the dictionary for |languageCode|.
+   * @param {string} languageCode
+   */
+  retryDownloadDictionary: function(languageCode) {
+    this.languageSettingsPrivate_.retryDownloadDictionary(languageCode);
+  },
+
   // <if expr="chromeos">
   /** @param {string} id */
   addInputMethod: function(id) {
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.html b/chrome/browser/resources/settings/languages_page/languages_page.html
index 85f0d93..b071927 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.html
+++ b/chrome/browser/resources/settings/languages_page/languages_page.html
@@ -6,6 +6,8 @@
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/iron-icons.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
@@ -76,6 +78,25 @@
       #offerTranslations:focus {
         background-color: transparent;
       }
+
+      .name-with-error-list {
+        padding: 18px 0;
+      }
+
+      .name-with-error-list div {
+        color: var(--google-red-500);
+        margin-top: 8px;
+      }
+
+      .error-icon {
+        @apply --cr-icon-height-width;
+        --iron-icon-fill-color: var(--google-red-700);
+        -webkit-margin-end: 8px;
+      }
+
+      .name-with-error-list[disabled] {
+        pointer-events: none;
+      }
     </style>
     <settings-languages languages="{{languages}}" prefs="{{prefs}}"
         language-helper="{{languageHelper}}">
@@ -231,24 +252,42 @@
         <iron-collapse id="spellCheckCollapse" opened="[[spellCheckOpened_]]">
           <div class="list-frame vertical-list">
             <template is="dom-repeat" items="[[spellCheckLanguages_]]">
-              <div class="list-item">
-                <template is="dom-if" if="[[!item.isManaged]]">
-                  <div class="start" on-click="onSpellCheckChange_"
-                      actionable$="[[item.language.supportsSpellcheck]]">
-                    [[item.language.displayName]]
+              <div class="list-item two-line">
+                <div class="start name-with-error-list"
+                    on-click="onSpellCheckNameClick_" actionable
+                    disabled$="[[isSpellCheckNameClickDisabled_(item,
+                        item.*)]]">
+                  [[item.language.displayName]]
+                  <div hidden="[[!errorsGreaterThan_(
+                      item.downloadDictionaryFailureCount, 0)]]">
+                    <iron-icon class="error-icon" icon="error">
+                    </iron-icon>
+                    $i18n{languagesDictionaryDownloadError}
                   </div>
+                  <div hidden="[[!errorsGreaterThan_(
+                      item.downloadDictionaryFailureCount, 1)]]">
+                    $i18n{languagesDictionaryDownloadErrorHelp}
+                  </div>
+                </div>
+                <paper-button on-click="onRetryDictionaryDownloadClick_"
+                    hidden="[[!errorsGreaterThan_(
+                        item.downloadDictionaryFailureCount, 0)]]">
+                  $i18n{retry}
+                </paper-button>
+                <template is="dom-if" if="[[!item.isManaged]]">
                   <cr-toggle on-change="onSpellCheckChange_"
                       disabled="[[!item.language.supportsSpellcheck]]"
                       checked="[[item.spellCheckEnabled]]"
-                      aria-label$="[[item.language.displayName]]">
+                      aria-label$="[[item.language.displayName]]"
+                      hidden="[[errorsGreaterThan_(
+                          item.downloadDictionaryFailureCount, 0)]]">
                   </cr-toggle>
                 </template>
                 <template is="dom-if" if="[[item.isManaged]]">
-                  <div class="start">
-                    [[item.language.displayName]]
-                  </div>
                   <cr-policy-pref-indicator
-                      pref="[[prefs.spellcheck.forced_dictionaries]]">
+                      pref="[[prefs.spellcheck.forced_dictionaries]]"
+                      hidden="[[errorsGreaterThan_(
+                          item.downloadDictionaryFailureCount, 0)]]">
                   </cr-policy-pref-indicator>
                 </template>
               </div>
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.js b/chrome/browser/resources/settings/languages_page/languages_page.js
index d9df072..f476e9c 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.js
+++ b/chrome/browser/resources/settings/languages_page/languages_page.js
@@ -122,6 +122,19 @@
         'languages.forcedSpellCheckLanguages.*)',
     'updateSpellcheckEnabled_(prefs.browser.enable_spellchecking.*)',
   ],
+
+  /**
+   * Checks if there are any errors downloading the spell check dictionary. This
+   * is used for showing/hiding error messages, spell check toggle and retry.
+   * button.
+   * @param {number} downloadDictionaryFailureCount
+   * @param {number} threshold
+   * @return {boolean}
+   * @private
+   */
+  errorsGreaterThan_: function(downloadDictionaryFailureCount, threshold) {
+    return downloadDictionaryFailureCount > threshold;
+  },
   // </if>
 
   /**
@@ -482,8 +495,7 @@
    */
   getSpellCheckLanguages_: function() {
     return this.languages.enabled.concat(
-        this.languages.forcedSpellCheckLanguages.map(
-            language => ({'language': language, isManaged: true})));
+        this.languages.forcedSpellCheckLanguages);
   },
 
   /** @private */
@@ -500,6 +512,8 @@
     for (let i = 0; i < this.spellCheckLanguages_.length; i++) {
       this.notifyPath(`spellCheckLanguages_.${i}.isManaged`);
       this.notifyPath(`spellCheckLanguages_.${i}.spellCheckEnabled`);
+      this.notifyPath(
+          `spellCheckLanguages_.${i}.downloadDictionaryFailureCount`);
     }
   },
 
@@ -534,6 +548,39 @@
   },
 
   /**
+   * Handler to initiate another attempt at downloading the spell check
+   * dictionary for a specified language.
+   * @param {!{target: Element, model: !{item: !LanguageState}}} e
+   */
+  onRetryDictionaryDownloadClick_: function(e) {
+    assert(this.errorsGreaterThan_(
+        e.model.item.downloadDictionaryFailureCount, 0));
+    this.languageHelper.retryDownloadDictionary(e.model.item.language.code);
+  },
+
+  /**
+   * Handler for clicking on the name of the language. The action taken must
+   * match the control that is available.
+   * @param {!{target: Element, model: !{item: !LanguageState}}} e
+   */
+  onSpellCheckNameClick_: function(e) {
+    assert(!this.isSpellCheckNameClickDisabled_(e.model.item));
+    this.onSpellCheckChange_(e);
+  },
+
+  /**
+   * Name only supports clicking when language is not managed, supports
+   * spellcheck, and the dictionary has been downloaded with no errors.
+   * @param {!LanguageState|!ForcedLanguageState} item
+   * @return {boolean}
+   * @private
+   */
+  isSpellCheckNameClickDisabled_: function(item) {
+    return item.isManaged || !item.language.supportsSpellcheck ||
+        item.downloadDictionaryFailureCount > 0;
+  },
+
+  /**
    * @return {string}
    * @private
    */
diff --git a/chrome/browser/resources/settings/languages_page/languages_types.js b/chrome/browser/resources/settings/languages_page/languages_types.js
index 9de2810..6dec6417 100644
--- a/chrome/browser/resources/settings/languages_page/languages_types.js
+++ b/chrome/browser/resources/settings/languages_page/languages_types.js
@@ -15,6 +15,9 @@
  *   spellCheckEnabled: boolean,
  *   translateEnabled: boolean,
  *   isManaged: boolean,
+ *   downloadDictionaryFailureCount: number,
+ *   downloadDictionaryStatus:
+ *       ?chrome.languageSettingsPrivate.SpellcheckDictionaryStatus,
  * }}
  */
 let LanguageState;
@@ -24,6 +27,9 @@
  * @typedef {{
  *   language: !chrome.languageSettingsPrivate.Language,
  *   isManaged: boolean,
+ *   downloadDictionaryFailureCount: number,
+ *   downloadDictionaryStatus:
+ *       ?chrome.languageSettingsPrivate.SpellcheckDictionaryStatus,
  * }}
  */
 let ForcedLanguageState;
@@ -61,7 +67,7 @@
  *   translateTarget: string,
  *   prospectiveUILanguage: (string|undefined),
  *   inputMethods: (!InputMethodsModel|undefined),
- *   forcedSpellCheckLanguages: !Array<!chrome.languageSettingsPrivate.Language>
+ *   forcedSpellCheckLanguages: !Array<!ForcedLanguageState>,
  * }}
  */
 let LanguagesModel;
@@ -174,6 +180,9 @@
    */
   getLanguage: assertNotReached,
 
+  /** @param {string} languageCode */
+  retryDownloadDictionary: assertNotReached,
+
   // <if expr="chromeos">
   /** @param {string} id */
   addInputMethod: assertNotReached,
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html b/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html
index d9a7b19..9551b369 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/password_list_item.html
@@ -44,7 +44,7 @@
         </a>
       </div>
       <input id="username" class="username-column text-elide password-field"
-          readonly value="[[item.entry.loginPair.username]]">
+          readonly tabindex="-1" value="[[item.entry.loginPair.username]]">
       <div class="password-column">
         <template is="dom-if" if="[[!item.entry.federationText]]">
           <input id="password" aria-label=$i18n{editPasswordPasswordLabel}
diff --git a/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.js b/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.js
index dff171ac..1e4ad8f 100644
--- a/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.js
+++ b/chrome/browser/resources/settings/people_page/easy_unlock_turn_off_dialog.js
@@ -145,7 +145,7 @@
       case settings.EasyUnlockTurnOffStatus.PENDING:
         return this.i18n('easyUnlockTurnOffButton');
       case settings.EasyUnlockTurnOffStatus.SERVER_ERROR:
-        return this.i18n('easyUnlockTurnOffRetryButton');
+        return this.i18n('retry');
     }
     assertNotReached();
   },
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 64ed4a8b..2e2cfb8 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -47,6 +47,7 @@
  *   MANAGE_ACCESSIBILITY: (undefined|!settings.Route),
  *   MANAGE_PASSWORDS: (undefined|!settings.Route),
  *   MANAGE_PROFILE: (undefined|!settings.Route),
+ *   MANAGE_TTS_SETTINGS: (undefined|!settings.Route),
  *   MULTIDEVICE: (undefined|!settings.Route),
  *   NETWORK_DETAIL: (undefined|!settings.Route),
  *   ON_STARTUP: (undefined|!settings.Route),
@@ -384,6 +385,8 @@
       // <if expr="chromeos">
       r.MANAGE_ACCESSIBILITY =
           r.ACCESSIBILITY.createChild('/manageAccessibility');
+      r.MANAGE_TTS_SETTINGS =
+          r.MANAGE_ACCESSIBILITY.createChild('/manageAccessibility/tts');
       // </if>
 
       r.SYSTEM = r.ADVANCED.createSection('/system', 'system');
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 494b1e45..9126d66 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -28,6 +28,12 @@
         <structure name="IDR_SETTINGS_MANAGE_A11Y_PAGE_HTML"
                    file="a11y_page/manage_a11y_page.html"
                    type="chrome_html" />
+        <structure name="IDR_SETTINGS_TTS_SUBPAGE_JS"
+                   file="a11y_page/tts_subpage.js"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_TTS_SUBPAGE_HTML"
+                   file="a11y_page/tts_subpage.html"
+                   type="chrome_html" />
       </if>
       <structure name="IDR_SETTINGS_ABOUT_PAGE_BROWSER_PROXY_HTML"
                  file="about_page/about_page_browser_proxy.html"
@@ -542,6 +548,12 @@
         <structure name="IDR_SETTINGS_DEVICE_NIGHT_LIGHT_SLIDER_JS"
                    file="device_page/night_light_slider.js"
                    type="chrome_html" />
+        <structure name="IDR_SETTINGS_DEVICE_DISPLAY_SIZE_SLIDER_HTML"
+                   file="device_page/display_size_slider.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_DEVICE_DISPLAY_SIZE_SLIDER_JS"
+                   file="device_page/display_size_slider.js"
+                   type="chrome_html" />
 
       </if>
       <structure name="IDR_SETTINGS_DOWNLOADS_BROWSER_PROXY_HTML"
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.cc
index 0ea55b6..10ff5cd 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.cc
@@ -30,6 +30,16 @@
   SETTINGS_PAGE_ON_REBOOT_REQUIRED_MAX,
 };
 
+// These values are used to record how the user was prompted to reboot the
+// machine. Must be in sync with the SoftwareReporterRebootPromptType enum from
+// enums.xml.
+enum SoftwareReporterRebootPromptTypeHistogramValue {
+  REBOOT_PROMPT_TYPE_SETTINGS_PAGE_OPENED = 1,
+  REBOOT_PROMPT_TYPE_MODAL_DIALOG_SHOWN = 2,
+  REBOOT_PROMPT_TYPE_NON_MODAL_DIALOG_SHOWN = 3,
+  REBOOT_PROMPT_TYPE_MAX,
+};
+
 class PromptDelegateImpl
     : public ChromeCleanerRebootDialogControllerImpl::PromptDelegate {
  public:
@@ -89,14 +99,6 @@
   return controller;
 }
 
-void ChromeCleanerRebootDialogControllerImpl::DialogShown() {
-  DCHECK(base::FeatureList::IsEnabled(kRebootPromptDialogFeature));
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  // TODO(crbug.com/770749) Collect metrics on how many times this dialog is
-  //                        shown.
-}
-
 void ChromeCleanerRebootDialogControllerImpl::Accept() {
   DCHECK(base::FeatureList::IsEnabled(kRebootPromptDialogFeature));
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -181,8 +183,16 @@
 
   if (base::FeatureList::IsEnabled(kRebootPromptDialogFeature)) {
     prompt_delegate_->ShowChromeCleanerRebootPrompt(browser, this);
+    SoftwareReporterRebootPromptTypeHistogramValue prompt_type =
+        IsRebootPromptModal() ? REBOOT_PROMPT_TYPE_MODAL_DIALOG_SHOWN
+                              : REBOOT_PROMPT_TYPE_NON_MODAL_DIALOG_SHOWN;
+    UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.Cleaner.RebootPromptShown",
+                              prompt_type, REBOOT_PROMPT_TYPE_MAX);
   } else {
     prompt_delegate_->OpenSettingsPage(browser);
+    UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.Cleaner.RebootPromptShown",
+                              REBOOT_PROMPT_TYPE_SETTINGS_PAGE_OPENED,
+                              REBOOT_PROMPT_TYPE_MAX);
     OnInteractionDone();
   }
 }
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.h
index 8511ace..51950f1 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.h
@@ -40,7 +40,6 @@
       std::unique_ptr<PromptDelegate> delegate);
 
   // ChromeCleanerRebootDialogController overrides.
-  void DialogShown() override;
   void Accept() override;
   void Cancel() override;
   void Close() override;
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_win.h
index eb7554ef6..aaa010e 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_win.h
@@ -16,9 +16,6 @@
 // the user interacts with it.
 class ChromeCleanerRebootDialogController {
  public:
-  // Called by the reboot dialog when the dialog has been shown. Used for
-  // reporting metrics.
-  virtual void DialogShown() = 0;
   // Called by the reboot dialog when user accepts the reboot prompt. Once
   // |Accept()| has been called, the controller will eventually delete itself
   // and no member functions should be called after that.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0c3b3ce..c71a2ee 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3614,6 +3614,10 @@
         "app_list/crostini/crostini_util.h",
         "app_list/search/arc_app_result.cc",
         "app_list/search/arc_app_result.h",
+        "app_list/search/internal_app_metadata.cc",
+        "app_list/search/internal_app_metadata.h",
+        "app_list/search/internal_app_result.cc",
+        "app_list/search/internal_app_result.h",
         "ash/launcher/arc_app_deferred_launcher_controller.cc",
         "ash/launcher/arc_app_deferred_launcher_controller.h",
         "ash/launcher/arc_app_deferred_launcher_item_controller.cc",
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc
index 48b3603..5f7d9263 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -34,11 +34,14 @@
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/search/arc_app_result.h"
 #include "chrome/browser/ui/app_list/search/extension_app_result.h"
+#include "chrome/browser/ui/app_list/search/internal_app_metadata.h"
+#include "chrome/browser/ui/app_list/search/internal_app_result.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_set.h"
+#include "ui/base/l10n/l10n_util.h"
 
 using extensions::ExtensionRegistry;
 
@@ -297,6 +300,36 @@
   DISALLOW_COPY_AND_ASSIGN(ArcDataSource);
 };
 
+class InternalDataSource : public AppSearchProvider::DataSource {
+ public:
+  InternalDataSource(Profile* profile, AppSearchProvider* owner)
+      : AppSearchProvider::DataSource(profile, owner) {}
+
+  ~InternalDataSource() override = default;
+
+  // AppSearchProvider::DataSource overrides:
+  void AddApps(AppSearchProvider::Apps* apps) override {
+    const base::Time time;
+    for (const auto& internal_app : GetInternalAppList()) {
+      apps->emplace_back(std::make_unique<AppSearchProvider::App>(
+          this, internal_app.app_id,
+          l10n_util::GetStringUTF8(internal_app.name_string_resource_id), time,
+          time));
+    }
+  }
+
+  std::unique_ptr<AppResult> CreateResult(
+      const std::string& app_id,
+      AppListControllerDelegate* list_controller,
+      bool is_recommended) override {
+    return std::make_unique<InternalAppResult>(profile(), app_id,
+                                               list_controller, is_recommended);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InternalDataSource);
+};
+
 }  // namespace
 
 AppSearchProvider::AppSearchProvider(Profile* profile,
@@ -311,6 +344,8 @@
       std::make_unique<ExtensionDataSource>(profile, this));
   if (arc::IsArcAllowedForProfile(profile))
     data_sources_.emplace_back(std::make_unique<ArcDataSource>(profile, this));
+  data_sources_.emplace_back(
+      std::make_unique<InternalDataSource>(profile, this));
 }
 
 AppSearchProvider::~AppSearchProvider() {}
diff --git a/chrome/browser/ui/app_list/search/internal_app_metadata.cc b/chrome/browser/ui/app_list/search/internal_app_metadata.cc
new file mode 100644
index 0000000..247caeb
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/internal_app_metadata.cc
@@ -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.
+
+#include "chrome/browser/ui/app_list/search/internal_app_metadata.h"
+
+#include "base/no_destructor.h"
+#include "chrome/grit/chrome_unscaled_resources.h"
+#include "chrome/grit/generated_resources.h"
+
+namespace app_list {
+
+const std::vector<InternalApp>& GetInternalAppList() {
+  static const base::NoDestructor<std::vector<InternalApp>> internal_app_list(
+      {{kInternalAppIdKeyboardShortcutViewer,
+        IDS_LAUNCHER_SEARCHABLE_APP_KEYBOARD_SHORTCUT_VIEWER,
+        IDR_KEYBOARD_SHORTCUT_VIEWER_LOGO_192}});
+  return *internal_app_list;
+}
+
+int GetIconResourceIdByAppId(const std::string& app_id) {
+  for (const auto& app : GetInternalAppList()) {
+    if (app_id == app.app_id)
+      return app.icon_resource_id;
+  }
+  return 0;
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/internal_app_metadata.h b/chrome/browser/ui/app_list/search/internal_app_metadata.h
new file mode 100644
index 0000000..3df1c3c3a
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/internal_app_metadata.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_INTERNAL_APP_METADATA_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_INTERNAL_APP_METADATA_H_
+
+#include <string>
+#include <vector>
+
+namespace app_list {
+
+constexpr char kInternalAppIdKeyboardShortcutViewer[] =
+    "internal://keyboard_shortcut_viewer";
+
+// Metadata about an internal app.
+// Internal apps are these apps can run in Chrome OS directly, e.g. Keyboard
+// Shortcut Viewer.
+struct InternalApp {
+  const char* app_id;
+  int name_string_resource_id = 0;
+  int icon_resource_id = 0;
+};
+
+// Returns a list of Chrome OS internal apps, which are searchable in launcher.
+const std::vector<InternalApp>& GetInternalAppList();
+
+// Returns the app's icon resource id.
+// Returns 0 if |app_id| is invalid.
+int GetIconResourceIdByAppId(const std::string& app_id);
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_INTERNAL_APP_METADATA_H_
diff --git a/chrome/browser/ui/app_list/search/internal_app_result.cc b/chrome/browser/ui/app_list/search/internal_app_result.cc
new file mode 100644
index 0000000..4959b82
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/internal_app_result.cc
@@ -0,0 +1,76 @@
+// 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/app_list/search/internal_app_result.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
+#include "chrome/browser/ui/app_list/search/internal_app_metadata.h"
+#include "chrome/browser/ui/app_list/search/search_util.h"
+#include "chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h"
+#include "ui/app_list/app_list_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image_skia_operations.h"
+
+namespace app_list {
+
+namespace {
+
+gfx::ImageSkia GetIconForInternalAppId(const std::string& app_id) {
+  const int resource_id = GetIconResourceIdByAppId(app_id);
+  if (resource_id == 0)
+    return gfx::ImageSkia();
+
+  gfx::ImageSkia* source =
+      ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
+  return gfx::ImageSkiaOperations::CreateResizedImage(
+      *source, skia::ImageOperations::RESIZE_BEST,
+      gfx::Size(kTileIconSize, kTileIconSize));
+}
+
+}  // namespace
+
+InternalAppResult::InternalAppResult(Profile* profile,
+                                     const std::string& app_id,
+                                     AppListControllerDelegate* controller,
+                                     bool is_recommendation)
+    : AppResult(profile, app_id, controller, is_recommendation) {
+  set_id(app_id);
+  set_result_type(ResultType::kInternalApp);
+
+  gfx::ImageSkia icon = GetIconForInternalAppId(app_id);
+  if (!icon.isNull())
+    SetIcon(icon);
+}
+
+InternalAppResult::~InternalAppResult() {}
+
+void InternalAppResult::ExecuteLaunchCommand(int event_flags) {
+  Open(event_flags);
+}
+
+void InternalAppResult::Open(int event_flags) {
+  // Record the search metric if the result is not a suggested app.
+  if (display_type() != DisplayType::kRecommendation)
+    RecordHistogram(APP_SEARCH_RESULT);
+
+  if (id() == kInternalAppIdKeyboardShortcutViewer)
+    keyboard_shortcut_viewer_util::ShowKeyboardShortcutViewer();
+}
+
+std::unique_ptr<SearchResult> InternalAppResult::Duplicate() const {
+  auto copy = std::make_unique<InternalAppResult>(
+      profile(), app_id(), controller(),
+      display_type() == DisplayType::kRecommendation);
+  copy->set_title(title());
+  copy->set_title_tags(title_tags());
+  copy->set_relevance(relevance());
+  return copy;
+}
+
+ui::MenuModel* InternalAppResult::GetContextMenuModel() {
+  return nullptr;
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/internal_app_result.h b/chrome/browser/ui/app_list/search/internal_app_result.h
new file mode 100644
index 0000000..ee92edb
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/internal_app_result.h
@@ -0,0 +1,40 @@
+// 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_APP_LIST_SEARCH_INTERNAL_APP_RESULT_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_INTERNAL_APP_RESULT_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/app_list/search/app_result.h"
+
+class AppListControllerDelegate;
+class Profile;
+
+namespace app_list {
+
+class InternalAppResult : public AppResult {
+ public:
+  InternalAppResult(Profile* profile,
+                    const std::string& app_id,
+                    AppListControllerDelegate* controller,
+                    bool is_recommendation);
+  ~InternalAppResult() override;
+
+  // SearchResult overrides:
+  void Open(int event_flags) override;
+  std::unique_ptr<SearchResult> Duplicate() const override;
+  ui::MenuModel* GetContextMenuModel() override;
+
+  // AppContextMenuDelegate overrides:
+  void ExecuteLaunchCommand(int event_flags) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InternalAppResult);
+};
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_INTERNAL_APP_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
index eeca9c2b..d6a17f6 100644
--- a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
@@ -45,6 +45,8 @@
 constexpr char kGmailArcPackage[] = "com.google.android.gm";
 constexpr char kGmailArcActivity[] =
     "com.google.android.gm.ConversationListActivityGmail";
+constexpr char kKeyboardShortcutHelperInternalName[] =
+    "Keyboard Shortcut Helper";
 
 }  // namespace
 
@@ -259,19 +261,22 @@
   prefs->SetLastLaunchTime(kHostedAppId, base::Time::FromInternalValue(20));
   prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(10));
   prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(5));
-  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunQuery(""));
+  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Keyboard Shortcut Helper",
+            RunQuery(""));
 
   prefs->SetLastLaunchTime(kHostedAppId, base::Time::FromInternalValue(5));
   prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(10));
   prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(20));
-  EXPECT_EQ("Packaged App 2,Packaged App 1,Hosted App", RunQuery(""));
+  EXPECT_EQ("Packaged App 2,Packaged App 1,Hosted App,Keyboard Shortcut Helper",
+            RunQuery(""));
 
   // Times in the future should just be handled as highest priority.
   prefs->SetLastLaunchTime(kHostedAppId,
                            kTestCurrentTime + base::TimeDelta::FromSeconds(5));
   prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(10));
   prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(5));
-  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunQuery(""));
+  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Keyboard Shortcut Helper",
+            RunQuery(""));
 }
 
 TEST_F(AppSearchProviderTest, FetchUnlaunchedRecommendations) {
@@ -285,7 +290,8 @@
   prefs->SetLastLaunchTime(kHostedAppId, base::Time::Now());
   prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(0));
   prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(0));
-  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunQuery(""));
+  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Keyboard Shortcut Helper",
+            RunQuery(""));
 }
 
 TEST_F(AppSearchProviderTest, FilterDuplicate) {
@@ -322,5 +328,12 @@
   EXPECT_EQ(kGmailExtensionName, RunQuery(kGmailQeuery));
 }
 
+TEST_F(AppSearchProviderTest, FetchInternalApp) {
+  CreateSearch();
+  EXPECT_EQ(kKeyboardShortcutHelperInternalName, RunQuery("Keyboard"));
+  EXPECT_EQ(kKeyboardShortcutHelperInternalName, RunQuery("Shortcut"));
+  EXPECT_EQ(kKeyboardShortcutHelperInternalName, RunQuery("Helper"));
+}
+
 }  // namespace test
 }  // namespace app_list
diff --git a/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.h b/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.h
index c159c819..13bec578 100644
--- a/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.h
+++ b/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.h
@@ -27,7 +27,6 @@
   void PlatformSpecificRemoveInfoBar(infobars::InfoBar* infobar) override;
 
   // InfoBarContainerDelegate:
-  SkColor GetInfoBarSeparatorColor() const override;
   void InfoBarContainerStateChanged(bool is_animating) override;
   bool DrawInfoBarArrows(int* x) const override;
 
diff --git a/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.mm b/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.mm
index 8b68367..bcfe2bc 100644
--- a/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.mm
+++ b/chrome/browser/ui/cocoa/infobars/infobar_container_cocoa.mm
@@ -30,10 +30,6 @@
   [controller_ removeInfoBar:infobar_cocoa];
 }
 
-SkColor InfoBarContainerCocoa::GetInfoBarSeparatorColor() const {
-  return SK_ColorBLACK;
-}
-
 void InfoBarContainerCocoa::InfoBarContainerStateChanged(bool is_animating) {
   [controller_ positionInfoBarsAndRedraw:is_animating];
 }
diff --git a/chrome/browser/ui/startup/bad_flags_prompt.cc b/chrome/browser/ui/startup/bad_flags_prompt.cc
index a18b767..6b44ad7 100644
--- a/chrome/browser/ui/startup/bad_flags_prompt.cc
+++ b/chrome/browser/ui/startup/bad_flags_prompt.cc
@@ -39,12 +39,17 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/chrome_feature_list.h"
+#endif  // OS_ANDROID
+
 namespace chrome {
 
 namespace {
 
-// Unsupported flags for which to display a warning that "stability and security
-// will suffer".
+#if !defined(OS_ANDROID)
+// Dangerous command line flags for which to display a warning that "stability
+// and security will suffer".
 static const char* kBadFlags[] = {
     network::switches::kIgnoreCertificateErrorsSPKIList,
     // These flags disable sandbox-related security.
@@ -99,11 +104,15 @@
     // This flag allows sites to access protected media identifiers without
     // getting the user's permission.
     switches::kUnsafelyAllowProtectedMediaIdentifierForDomain};
+#endif  // OS_ANDROID
 
-// Unsupported feature flags for which to display a warning that "stability
-// and security will suffer".
-static const base::Feature* kBadFeatureFlags[] = {
+// Dangerous feature flags in about:flags for which to display a warning that
+// "stability and security will suffer".
+static const base::Feature* kBadFeatureFlagsInAboutFlags[] = {
     &features::kSignedHTTPExchange,
+#if defined(OS_ANDROID)
+    &chrome::android::kCommandLineOnNonRooted,
+#endif  // OS_ANDROID
 };
 
 void ShowBadFeatureFlagsInfoBar(content::WebContents* web_contents,
@@ -119,6 +128,9 @@
 }  // namespace
 
 void ShowBadFlagsPrompt(content::WebContents* web_contents) {
+// On Android, ShowBadFlagsPrompt doesn't show the warning notification
+// for flags which are not available in about:flags.
+#if !defined(OS_ANDROID)
   // Flags only available in specific builds, for which to display a warning
   // "the flag is not implemented in this build", if necessary.
   struct {
@@ -144,8 +156,9 @@
       return;
     }
   }
+#endif  // OS_ANDROID
 
-  for (const base::Feature* feature : kBadFeatureFlags) {
+  for (const base::Feature* feature : kBadFeatureFlagsInAboutFlags) {
     if (base::FeatureList::IsEnabled(*feature)) {
       ShowBadFeatureFlagsInfoBar(web_contents, IDS_BAD_FEATURES_WARNING_MESSAGE,
                                  feature);
diff --git a/chrome/browser/ui/startup/bad_flags_prompt.h b/chrome/browser/ui/startup/bad_flags_prompt.h
index b2bc905..4dbc0b7 100644
--- a/chrome/browser/ui/startup/bad_flags_prompt.h
+++ b/chrome/browser/ui/startup/bad_flags_prompt.h
@@ -12,7 +12,9 @@
 namespace chrome {
 
 // Shows a warning notification in |web_contents| that the app was run with
-// dangerous command line flags.
+// dangerous command line flags or dangerous flags in about:flags.
+// On Android, this method doesn't check any flags which are not available in
+// about:flags.
 void ShowBadFlagsPrompt(content::WebContents* web_contents);
 
 // Shows a warning about a specific flag.  Exposed publicly only for testing;
diff --git a/chrome/browser/ui/views/chrome_cleaner_reboot_dialog_win.cc b/chrome/browser/ui/views/chrome_cleaner_reboot_dialog_win.cc
index fa0764aa..42bdc8db 100644
--- a/chrome/browser/ui/views/chrome_cleaner_reboot_dialog_win.cc
+++ b/chrome/browser/ui/views/chrome_cleaner_reboot_dialog_win.cc
@@ -70,8 +70,6 @@
       this, nullptr, browser->window()->GetNativeWindow());
   widget->SetBounds(GetDialogBounds(browser));
   widget->Show();
-
-  dialog_controller_->DialogShown();
 }
 
 // WidgetDelegate overrides.
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.cc
index befdc056..9c4008e 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.cc
@@ -53,9 +53,8 @@
 //     views::DesktopWindowTreeHostX11 implementation:
 
 void BrowserDesktopWindowTreeHostX11::Init(
-    aura::Window* content_window,
     const views::Widget::InitParams& params) {
-  views::DesktopWindowTreeHostX11::Init(content_window, params);
+  views::DesktopWindowTreeHostX11::Init(params);
 
   // We have now created our backing X11 window. We now need to (possibly)
   // alert Unity that there's a menu bar attached to it.
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h
index 2f60b1cc..39731e73 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h
@@ -35,8 +35,7 @@
   bool UsesNativeSystemMenu() const override;
 
   // Overridden from views::DesktopWindowTreeHostX11:
-  void Init(aura::Window* content_window,
-            const views::Widget::InitParams& params) override;
+  void Init(const views::Widget::InitParams& params) override;
   void CloseNow() override;
   void OnMaximizedStateChanged() override;
   void OnFullscreenStateChanged() override;
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 760e6db..5b19cfa 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2149,11 +2149,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserView, InfoBarContainerDelegate overrides:
 
-SkColor BrowserView::GetInfoBarSeparatorColor() const {
-  return GetThemeProvider()->GetColor(
-      ThemeProperties::COLOR_DETACHED_BOOKMARK_BAR_SEPARATOR);
-}
-
 void BrowserView::InfoBarContainerStateChanged(bool is_animating) {
   ToolbarSizeChanged(is_animating);
 }
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 50627a65..422c701 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -445,7 +445,6 @@
   gfx::Size GetMinimumSize() const override;
 
   // InfoBarContainerDelegate:
-  SkColor GetInfoBarSeparatorColor() const override;
   void InfoBarContainerStateChanged(bool is_animating) override;
   bool DrawInfoBarArrows(int* x) const override;
 
diff --git a/chrome/browser/ui/views/infobars/infobar_background.cc b/chrome/browser/ui/views/infobars/infobar_background.cc
index 5c683a0e..b699470 100644
--- a/chrome/browser/ui/views/infobars/infobar_background.cc
+++ b/chrome/browser/ui/views/infobars/infobar_background.cc
@@ -6,10 +6,12 @@
 
 #include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_flags.h"
+#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/infobar_container_delegate.h"
 #include "chrome/browser/ui/views/infobars/infobar_view.h"
 #include "components/infobars/core/infobar.h"
 #include "ui/base/material_design/material_design_controller.h"
+#include "ui/base/theme_provider.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/views/view.h"
@@ -41,7 +43,8 @@
   SkScalar arrow_height = scale(infobar->arrow_height());
   SkScalar infobar_width = scale(infobar->width());
   if (delegate) {
-    separator_color = delegate->GetInfoBarSeparatorColor();
+    separator_color = view->GetThemeProvider()->GetColor(
+        ThemeProperties::COLOR_DETACHED_BOOKMARK_BAR_SEPARATOR);
     int arrow_x;
     if (delegate->DrawInfoBarArrows(&arrow_x) && infobar->arrow_height() > 0) {
       // Compensate for the fact that a relative movement of n creates a line of
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 76677eb1..4e19cec 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -96,7 +96,69 @@
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
-// OmniboxResultView:
+// OmniboxSeparatedLineView:
+
+class OmniboxSeparatedLineView : public views::View {
+ public:
+  explicit OmniboxSeparatedLineView(OmniboxResultView* result_view);
+  ~OmniboxSeparatedLineView() override;
+
+ protected:
+  // views::View:
+  void Layout() override;
+  const char* GetClassName() const override;
+
+  OmniboxResultView* result_view_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OmniboxSeparatedLineView);
+};
+
+OmniboxSeparatedLineView::OmniboxSeparatedLineView(
+    OmniboxResultView* result_view)
+    : result_view_(result_view) {}
+
+OmniboxSeparatedLineView::~OmniboxSeparatedLineView() = default;
+
+const char* OmniboxSeparatedLineView::GetClassName() const {
+  return "OmniboxSeparatedLineView";
+}
+
+void OmniboxSeparatedLineView::Layout() {
+  // TODO(dschuyler): Fill this in.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OmniboxSuggestionView:
+
+class OmniboxSuggestionView : public OmniboxSeparatedLineView {
+ public:
+  explicit OmniboxSuggestionView(OmniboxResultView* result_view);
+  ~OmniboxSuggestionView() override;
+
+ private:
+  // views::View:
+  void Layout() override;
+  const char* GetClassName() const override;
+
+  DISALLOW_COPY_AND_ASSIGN(OmniboxSuggestionView);
+};
+
+OmniboxSuggestionView::OmniboxSuggestionView(OmniboxResultView* result_view)
+    : OmniboxSeparatedLineView::OmniboxSeparatedLineView(result_view) {}
+
+OmniboxSuggestionView::~OmniboxSuggestionView() = default;
+
+const char* OmniboxSuggestionView::GetClassName() const {
+  return "OmniboxSuggestionView";
+}
+
+void OmniboxSuggestionView::Layout() {
+  // TODO(dschuyler): Fill this in.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OmniboxImageView:
 
 class OmniboxImageView : public views::ImageView {
  public:
@@ -123,9 +185,13 @@
       suggestion_separator_view_(AddOmniboxTextView(font_list)),
       keyword_icon_view_(AddOmniboxImageView()),
       keyword_content_view_(AddOmniboxTextView(font_list)),
-      keyword_description_view_(AddOmniboxTextView(font_list)) {
+      keyword_description_view_(AddOmniboxTextView(font_list)),
+      keyword_separator_view_(AddOmniboxTextView(font_list)) {
   CHECK_GE(model_index, 0);
 
+  AddChildView(suggestion_view_ = new OmniboxSuggestionView(this));
+  AddChildView(keyword_view_ = new OmniboxSeparatedLineView(this));
+
   keyword_icon_view_->EnableCanvasFlippingForRTLUI(true);
   keyword_icon_view_->SetImage(gfx::CreateVectorIcon(
       omnibox::kKeywordSearchIcon, GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
@@ -207,6 +273,8 @@
       l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
   suggestion_separator_view_->SetText(separator);
   suggestion_separator_view_->Dim();
+  keyword_separator_view_->SetText(separator);
+  keyword_separator_view_->Dim();
 
   AutocompleteMatch* keyword_match = match_.associated_keyword.get();
   keyword_content_view_->SetVisible(keyword_match);
@@ -445,6 +513,7 @@
   if (IsTwoLineLayout())
     row_height += match_.answer ? GetAnswerHeight() : GetTextHeight();
   suggestion_separator_view_->SetVisible(false);
+  keyword_separator_view_->SetVisible(false);
 
   // TODO(dschuyler): Refactor these if/else's into separate pieces of code to
   // improve readability/maintainability.
@@ -466,7 +535,7 @@
     int description_width =
         keyword_description_view_->CalculatePreferredSize().width();
     OmniboxPopupModel::ComputeMatchMaxWidths(
-        content_width, suggestion_separator_view_->width(), description_width,
+        content_width, keyword_separator_view_->width(), description_width,
         width(),
         /*description_on_separate_line=*/false,
         !AutocompleteMatch::IsSearchType(match_.type), &content_width,
@@ -475,10 +544,10 @@
     keyword_content_view_->SetBounds(kw_x, y, content_width, text_height);
     if (description_width != 0) {
       kw_x += keyword_content_view_->width();
-      suggestion_separator_view_->SetVisible(true);
-      suggestion_separator_view_->SetBounds(
-          kw_x, y, suggestion_separator_view_->width(), text_height);
-      kw_x += suggestion_separator_view_->width();
+      keyword_separator_view_->SetVisible(true);
+      keyword_separator_view_->SetBounds(
+          kw_x, y, keyword_separator_view_->width(), text_height);
+      kw_x += keyword_separator_view_->width();
       keyword_description_view_->SetBounds(kw_x, y, description_width,
                                            text_height);
     } else if (IsTwoLineLayout()) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.h b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
index 3885150..5ac721b 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
@@ -31,6 +31,8 @@
 }
 
 class OmniboxImageView;
+class OmniboxSeparatedLineView;
+class OmniboxSuggestionView;
 class OmniboxTabSwitchButton;
 class OmniboxTextView;
 
@@ -126,6 +128,12 @@
   std::unique_ptr<gfx::SlideAnimation> animation_;
 
   // Weak pointers for easy reference.
+  OmniboxSuggestionView* suggestion_view_;  // The leading (or left) view.
+  OmniboxSeparatedLineView* keyword_view_;  // The trailing (or right) view.
+
+  // TODO(dschuyler): Move these views into either suggestion_view_ or
+  // keyword_view_. I intend to do so by May, 2018.
+
   views::ImageView* suggestion_icon_view_;   // Small icon. e.g. favicon.
   views::ImageView* suggestion_image_view_;  // For rich suggestions.
   OmniboxTextView* suggestion_content_view_;
@@ -136,6 +144,7 @@
   views::ImageView* keyword_icon_view_;  // An icon resembling a '>'.
   OmniboxTextView* keyword_content_view_;
   OmniboxTextView* keyword_description_view_;
+  OmniboxTextView* keyword_separator_view_;
 
   DISALLOW_COPY_AND_ASSIGN(OmniboxResultView);
 };
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 9e5674b3..077232b 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -1075,9 +1075,15 @@
   const bool shift = event.IsShiftDown();
   const bool control = event.IsControlDown();
   const bool alt = event.IsAltDown() || event.IsAltGrDown();
+#if defined(OS_MACOSX)
+  const bool command = event.IsCommandDown();
+#else
+  const bool command = false;
+#endif
   switch (event.key_code()) {
     case ui::VKEY_RETURN:
-      model()->AcceptInput(alt ? WindowOpenDisposition::NEW_FOREGROUND_TAB
+      model()->AcceptInput(alt || command
+                               ? WindowOpenDisposition::NEW_FOREGROUND_TAB
                                : WindowOpenDisposition::CURRENT_TAB,
                            false);
       return true;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index f0001939..0076f1e 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -206,11 +206,18 @@
     avatar_->set_triggerable_event_flags(ui::EF_LEFT_MOUSE_BUTTON |
                                          ui::EF_MIDDLE_MOUSE_BUTTON);
     avatar_->set_tag(IDC_SHOW_AVATAR_MENU);
-    // TODO(pbos): Incorporate GetAvatarButtonTextForProfile. See AvatarButton.
-    avatar_->SetTooltipText(
-        l10n_util::GetStringUTF16(IDS_GENERIC_USER_AVATAR_LABEL));
-    avatar_->SetAccessibleName(
-        l10n_util::GetStringUTF16(IDS_GENERIC_USER_AVATAR_LABEL));
+    if (browser_->profile()->IsOffTheRecord()) {
+      avatar_->SetTooltipText(
+          l10n_util::GetStringUTF16(IDS_INCOGNITO_AVATAR_BUTTON_TOOLTIP));
+      avatar_->SetEnabled(false);
+    } else {
+      // TODO(pbos): Incorporate GetAvatarButtonTextForProfile. See
+      // AvatarButton.
+      avatar_->SetTooltipText(
+          l10n_util::GetStringUTF16(IDS_GENERIC_USER_AVATAR_LABEL));
+      avatar_->SetAccessibleName(
+          l10n_util::GetStringUTF16(IDS_GENERIC_USER_AVATAR_LABEL));
+    }
     avatar_->set_id(VIEW_ID_AVATAR_BUTTON);
     avatar_->Init();
   }
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index aa06894..29d8903 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -404,10 +404,6 @@
     return MdBookmarksUI::IsEnabled() ? &NewWebUI<MdBookmarksUI>
                                       : &NewWebUI<BookmarksUI>;
   }
-  if (base::FeatureList::IsEnabled(features::kBundledConnectionHelpFeature) &&
-      url.host_piece() == security_interstitials::kChromeUIConnectionHelpHost) {
-    return &NewWebUI<security_interstitials::ConnectionHelpUI>;
-  }
   // Downloads list on Android uses the built-in download manager.
   if (url.host_piece() == chrome::kChromeUIDownloadsHost)
     return &NewWebUI<MdDownloadsUI>;
@@ -606,6 +602,11 @@
   if (IsAboutUI(url))
     return &NewWebUI<AboutUI>;
 
+  if (base::FeatureList::IsEnabled(features::kBundledConnectionHelpFeature) &&
+      url.host_piece() == security_interstitials::kChromeUIConnectionHelpHost) {
+    return &NewWebUI<security_interstitials::ConnectionHelpUI>;
+  }
+
   if (dom_distiller::IsEnableDomDistillerSet() &&
       url.host_piece() == dom_distiller::kChromeUIDomDistillerHost) {
     return &NewWebUI<dom_distiller::DomDistillerUi>;
diff --git a/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc
index 3a23730..a60e3c2 100644
--- a/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/arc_terms_of_service_screen_handler.cc
@@ -99,14 +99,20 @@
   builder->Add("arcTermsOfServiceRetryButton", IDS_ARC_OOBE_TERMS_BUTTON_RETRY);
   builder->Add("arcTermsOfServiceAcceptButton",
                IDS_ARC_OOBE_TERMS_BUTTON_ACCEPT);
+  builder->Add("arcTermsOfServiceNextButton",
+               IDS_ARC_OPT_IN_DIALOG_BUTTON_NEXT);
   builder->Add("arcPolicyLink", IDS_ARC_OPT_IN_PRIVACY_POLICY_LINK);
   builder->Add("arcTextBackupRestore", IDS_ARC_OPT_IN_DIALOG_BACKUP_RESTORE);
   builder->Add("arcTextLocationService", IDS_ARC_OPT_IN_LOCATION_SETTING);
+  builder->Add("arcTextPaiService", IDS_ARC_OPT_IN_PAI);
+  builder->Add("arcTextGoogleServiceConfirmation",
+               IDS_ARC_OPT_IN_GOOGLE_SERVICE_CONFIRMATION);
   builder->Add("arcLearnMoreStatistics", IDS_ARC_OPT_IN_LEARN_MORE_STATISTICS);
   builder->Add("arcLearnMoreLocationService",
       IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES);
   builder->Add("arcLearnMoreBackupAndRestore",
       IDS_ARC_OPT_IN_LEARN_MORE_BACKUP_AND_RESTORE);
+  builder->Add("arcLearnMorePaiService", IDS_ARC_OPT_IN_LEARN_MORE_PAI_SERVICE);
   builder->Add("arcOverlayClose", IDS_ARC_OOBE_TERMS_POPUP_HELP_CLOSE_BUTTON);
 }
 
@@ -127,12 +133,16 @@
   // managed flag.
   const bool owner_profile = !owner.is_valid() || user->GetAccountId() == owner;
 
-  if (owner_profile && !managed) {
+  if (owner_profile && !managed && !enabled) {
     CallJS("setMetricsMode", base::string16(), false);
   } else {
-    int message_id = enabled ?
-        IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_ENABLED :
-        IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED;
+    int message_id;
+    if (owner_profile && !managed) {
+      message_id = IDS_ARC_OOBE_TERMS_DIALOG_METRICS_ENABLED;
+    } else {
+      message_id = enabled ? IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_ENABLED
+                           : IDS_ARC_OOBE_TERMS_DIALOG_METRICS_MANAGED_DISABLED;
+    }
     CallJS("setMetricsMode", l10n_util::GetStringUTF16(message_id), true);
   }
 }
diff --git a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc
index e15089f..990e68a 100644
--- a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc
+++ b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.cc
@@ -32,7 +32,6 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
-#include "components/download/public/common/download_danger_type.h"
 #include "components/download/public/common/download_item.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
@@ -77,8 +76,9 @@
     content::DownloadManager* download_manager, content::WebUI* web_ui)
     : list_tracker_(download_manager, web_ui) {
   // Create our fileicon data source.
-  profile_ = Profile::FromBrowserContext(download_manager->GetBrowserContext());
-  content::URLDataSource::Add(profile_, new FileIconSource());
+  Profile* profile =
+      Profile::FromBrowserContext(download_manager->GetBrowserContext());
+  content::URLDataSource::Add(profile, new FileIconSource());
   CheckForRemovedFiles();
 }
 
@@ -198,32 +198,8 @@
 void MdDownloadsDOMHandler::HandleSaveDangerous(const base::ListValue* args) {
   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
   download::DownloadItem* file = GetDownloadByValue(args);
-  SaveDownload(file);
-}
-
-void MdDownloadsDOMHandler::SaveDownload(download::DownloadItem* download) {
-  if (!download)
-    return;
-  // If danger type is NOT DANGEROUS_FILE, chrome shows users a download danger
-  // prompt.
-  if (download->GetDangerType() !=
-      download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE) {
-    ShowDangerPrompt(download);
-  } else {
-    // If danger type is DANGEROUS_FILE, chrome proceeds to keep this download
-    // without showing download danger prompt.
-    if (profile_) {
-      PrefService* prefs = profile_->GetPrefs();
-      if (!profile_->IsOffTheRecord() &&
-          prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
-        DownloadDangerPrompt::SendSafeBrowsingDownloadReport(
-            safe_browsing::ClientSafeBrowsingReportRequest::
-                DANGEROUS_DOWNLOAD_RECOVERY,
-            true, *download);
-      }
-    }
-    DangerPromptDone(download->GetId(), DownloadDangerPrompt::ACCEPT);
-  }
+  if (file)
+    ShowDangerPrompt(file);
 }
 
 void MdDownloadsDOMHandler::HandleDiscardDangerous(
diff --git a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h
index fd401f5..cdfc1342 100644
--- a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h
+++ b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler.h
@@ -31,8 +31,6 @@
 class DownloadItem;
 }
 
-class Profile;
-
 // The handler for Javascript messages related to the "downloads" view,
 // also observes changes to the download manager.
 class MdDownloadsDOMHandler : public content::WebContentsObserver,
@@ -107,9 +105,6 @@
   // dangerous ones are immediately removed. Protected for testing.
   void RemoveDownloads(const DownloadVector& to_remove);
 
-  // Helper function to handle save download event.
-  void SaveDownload(download::DownloadItem* download);
-
  private:
   using IdSet = std::set<uint32_t>;
 
@@ -131,8 +126,7 @@
 
   // Conveys danger acceptance from the DownloadDangerPrompt to the
   // DownloadItem.
-  virtual void DangerPromptDone(int download_id,
-                                DownloadDangerPrompt::Action action);
+  void DangerPromptDone(int download_id, DownloadDangerPrompt::Action action);
 
   // Returns true if the records of any downloaded items are allowed (and able)
   // to be deleted.
@@ -155,9 +149,6 @@
   // IDs of downloads to remove when this handler gets deleted.
   std::vector<IdSet> removals_;
 
-  // User profile that corresponds to this handler.
-  Profile* profile_ = nullptr;
-
   // Whether the render process has gone.
   bool render_process_gone_ = false;
 
diff --git a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc
index 10fe959..a2ce777f 100644
--- a/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc
+++ b/chrome/browser/ui/webui/md_downloads/md_downloads_dom_handler_unittest.cc
@@ -21,25 +21,11 @@
  public:
   explicit TestMdDownloadsDOMHandler(content::DownloadManager* download_manager,
                                      content::WebUI* web_ui)
-      : MdDownloadsDOMHandler(download_manager, web_ui),
-        danger_prompt_count_(0) {}
+      : MdDownloadsDOMHandler(download_manager, web_ui) {}
 
   using MdDownloadsDOMHandler::set_web_ui;
   using MdDownloadsDOMHandler::FinalizeRemovals;
   using MdDownloadsDOMHandler::RemoveDownloads;
-  using MdDownloadsDOMHandler::SaveDownload;
-
-  int danger_prompt_count() { return danger_prompt_count_; }
-
- private:
-  void ShowDangerPrompt(download::DownloadItem* dangerous) override {
-    danger_prompt_count_++;
-  }
-
-  void DangerPromptDone(int download_id,
-                        DownloadDangerPrompt::Action action) override {}
-
-  int danger_prompt_count_;
 };
 
 }  // namespace
@@ -128,27 +114,3 @@
   EXPECT_CALL(completed, Remove());
   handler.FinalizeRemovals();
 }
-
-TEST_F(MdDownloadsDOMHandlerTest, HandleSaveDownload) {
-  // When user chooses to recover a download, download danger prompt should NOT
-  // be shown if download danger type is DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE.
-  testing::StrictMock<download::MockDownloadItem> dangerous_file_type;
-  EXPECT_CALL(dangerous_file_type, GetDangerType())
-      .WillRepeatedly(
-          testing::Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE));
-  EXPECT_CALL(dangerous_file_type, GetId())
-      .WillOnce(testing::Return(uint32_t()));
-  TestMdDownloadsDOMHandler handler(manager(), web_ui());
-  EXPECT_EQ(0, handler.danger_prompt_count());
-  handler.SaveDownload(&dangerous_file_type);
-  EXPECT_EQ(0, handler.danger_prompt_count());
-
-  // For other download danger types, download danger prompt should
-  // be shown.
-  testing::StrictMock<download::MockDownloadItem> malicious_download;
-  EXPECT_CALL(malicious_download, GetDangerType())
-      .WillRepeatedly(
-          testing::Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL));
-  handler.SaveDownload(&malicious_download);
-  EXPECT_EQ(1, handler.danger_prompt_count());
-}
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 af9f432b..6fa9f97 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
@@ -129,6 +129,7 @@
     {"moreActions", IDS_SETTINGS_MORE_ACTIONS},
     {"ok", IDS_OK},
     {"restart", IDS_SETTINGS_RESTART},
+    {"retry", IDS_SETTINGS_RETRY},
     {"save", IDS_SAVE},
     {"settings", IDS_SETTINGS_SETTINGS},
     {"toggleOn", IDS_SETTINGS_TOGGLE_ON},
@@ -245,6 +246,14 @@
     {"audioHeading", IDS_SETTINGS_ACCESSIBILITY_AUDIO_HEADING},
     {"additionalFeaturesTitle",
      IDS_SETTINGS_ACCESSIBILITY_ADDITIONAL_FEATURES_TITLE},
+    {"manageTtsSettings", IDS_SETTINGS_MANAGE_TTS_SETTINGS},
+    {"ttsSettingsLinkDescription", IDS_SETTINGS_TTS_LINK_DESCRIPTION},
+    {"textToSpeechVoices", IDS_SETTINGS_TEXT_TO_SPEECH_VOICES},
+    {"textToSpeechProperties", IDS_SETTINGS_TEXT_TO_SPEECH_PROPERTIES},
+    {"textToSpeechRate", IDS_SETTINGS_TEXT_TO_SPEECH_RATE},
+    {"textToSpeechPitch", IDS_SETTINGS_TEXT_TO_SPEECH_PITCH},
+    {"textToSpeechEngines", IDS_SETTINGS_TEXT_TO_SPEECH_ENGINES},
+    {"textToSpeechInstallEngines", IDS_SETTINGS_TEXT_TO_SPEECH_INSTALL_ENGINES},
 #endif
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
@@ -675,6 +684,7 @@
       {"displayResolutionSublabel", IDS_SETTINGS_DISPLAY_RESOLUTION_SUBLABEL},
       {"displayResolutionMenuItem", IDS_SETTINGS_DISPLAY_RESOLUTION_MENU_ITEM},
       {"displayZoomTitle", IDS_SETTINGS_DISPLAY_ZOOM_TITLE},
+      {"displayZoomSublabel", IDS_SETTINGS_DISPLAY_ZOOM_SUBLABEL},
       {"displayZoomValue", IDS_SETTINGS_DISPLAY_ZOOM_VALUE},
       {"displaySizeSliderMinLabel", IDS_SETTINGS_DISPLAY_ZOOM_SLIDER_MINIMUM},
       {"displaySizeSliderMaxLabel", IDS_SETTINGS_DISPLAY_ZOOM_SLIDER_MAXIMUM},
@@ -1035,7 +1045,6 @@
        IDS_SETTINGS_EASY_UNLOCK_TURN_OFF_ERROR_TITLE},
       {"easyUnlockTurnOffErrorMessage",
        IDS_SETTINGS_EASY_UNLOCK_TURN_OFF_ERROR_MESSAGE},
-      {"easyUnlockTurnOffRetryButton", IDS_SETTINGS_EASY_UNLOCK_TURN_OFF_RETRY},
       {"easyUnlockAllowSignInLabel",
        IDS_SETTINGS_EASY_UNLOCK_ALLOW_SIGN_IN_LABEL},
       {"easyUnlockProximityThresholdLabel",
@@ -1264,6 +1273,10 @@
     {"noCustomDictionaryWordsFound",
      IDS_SETTINGS_LANGUAGES_DICTIONARY_WORDS_NONE},
     {"spellCheckDisabled", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_DISABLED},
+    {"languagesDictionaryDownloadError",
+     IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED},
+    {"languagesDictionaryDownloadErrorHelp",
+     IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED_HELP},
 #endif
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 077c8a37..5582433 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -2831,23 +2831,32 @@
 
   parent = CreateTransientParent(kWebVrIndicatorTransience,
                                  kToastTimeoutSeconds, true);
-  parent->AddBinding(std::make_unique<Binding<std::pair<bool, bool>>>(
+  parent->AddBinding(std::make_unique<Binding<std::tuple<bool, bool, bool>>>(
       VR_BIND_LAMBDA(
           [](Model* model, UiElement* splash_screen) {
-            return std::make_pair(model->web_vr_enabled() &&
-                                      model->web_vr.presenting_web_vr() &&
-                                      model->web_vr.has_received_permissions &&
-                                      splash_screen->GetTargetOpacity() == 0.f,
-                                  model->controller.app_button_long_pressed);
+            return std::tuple<bool, bool, bool>(
+                model->web_vr_enabled() && model->web_vr.presenting_web_vr() &&
+                    model->web_vr.has_received_permissions &&
+                    splash_screen->GetTargetOpacity() == 0.f,
+                model->controller.app_button_long_pressed,
+                model->web_vr.showing_hosted_ui);
           },
           base::Unretained(model_),
           base::Unretained(
               scene_->GetUiElementByName(kSplashScreenTransientParent))),
       VR_BIND_LAMBDA(
           [](TransientElement* e, Model* model, UiScene* scene,
-             const base::Optional<std::pair<bool, bool>>& last_value,
-             const std::pair<bool, bool>& value) {
-            if (!value.first) {
+             const base::Optional<std::tuple<bool, bool, bool>>& last_value,
+             const std::tuple<bool, bool, bool>& value) {
+            const bool in_web_vr_presentation = std::get<0>(value);
+            const bool in_long_press = std::get<1>(value);
+            const bool showing_hosted_ui = std::get<2>(value);
+            const bool was_in_long_press =
+                last_value && std::get<1>(last_value.value());
+            const bool was_showing_hosted_ui =
+                last_value && std::get<2>(last_value.value());
+
+            if (!in_web_vr_presentation) {
               e->SetVisibleImmediately(false);
               return;
             }
@@ -2856,7 +2865,12 @@
             // situation where the app button has been released after a long
             // press, and the situation when we want to initially show the
             // indicators.
-            if (last_value && last_value.value().second && !value.second)
+            if (was_in_long_press && !in_long_press)
+              return;
+
+            // Similarly, we need to know when we've finished presenting hosted
+            // ui because we should not show indicators then.
+            if (was_showing_hosted_ui && !showing_hosted_ui)
               return;
 
             e->SetVisible(true);
@@ -2864,7 +2878,7 @@
             SetVisibleInLayout(
                 scene->GetUiElementByName(kWebVrExclusiveScreenToast),
                 !model->web_vr_autopresentation_enabled() &&
-                    !model->browsing_disabled && !value.second);
+                    !model->browsing_disabled && !in_long_press);
             SetVisibleInLayout(scene->GetUiElementByName(kWebVrUrlToast),
                                model->web_vr_autopresentation_enabled() &&
                                    model->toolbar_state.should_display_url);
@@ -2879,7 +2893,8 @@
             }
 
             e->RemoveKeyframeModels(TRANSFORM);
-            if (value.second) {
+            if (in_long_press) {
+              // We do not do a translation animation for long press.
               e->SetTranslate(0, 0, 0);
               return;
             }
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index 427751a..d6356bd 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -1382,6 +1382,24 @@
   EXPECT_EQ(original, hosted_ui->world_space_transform());
 }
 
+// Ensures that permissions do not appear after showing hosted UI.
+TEST_F(UiTest, DoNotShowIndicatorsAfterHostedUi) {
+  CreateScene(kNotInCct, kInWebVr);
+  ui_->SetWebVrMode(true);
+  EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
+  ui_->OnWebVrFrameAvailable();
+  ui_->SetCapturingState(CapturingStateModel());
+  OnBeginFrame();
+  EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
+  RunFor(MsToDelta(8000));
+  EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
+  model_->web_vr.showing_hosted_ui = true;
+  OnBeginFrame();
+  model_->web_vr.showing_hosted_ui = false;
+  OnBeginFrame();
+  EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
+}
+
 // Ensures that permissions appear on long press, and that when the app button
 // is released that we do not show the exclusive screen toast. Distinguishing
 // these cases requires knowledge of the previous state.
diff --git a/chrome/installer/setup/BUILD.gn b/chrome/installer/setup/BUILD.gn
index 23f5de92..c0a42c1 100644
--- a/chrome/installer/setup/BUILD.gn
+++ b/chrome/installer/setup/BUILD.gn
@@ -58,8 +58,6 @@
       "installer_crash_reporting.h",
       "installer_state.cc",
       "installer_state.h",
-      "persistent_histogram_storage.cc",
-      "persistent_histogram_storage.h",
       "progress_calculator.cc",
       "progress_calculator.h",
       "setup_constants.cc",
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index 85b9e2ea..c6972d7 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -35,7 +35,6 @@
 #include "chrome/install_static/install_modes.h"
 #include "chrome/install_static/install_util.h"
 #include "chrome/installer/setup/installer_state.h"
-#include "chrome/installer/setup/persistent_histogram_storage.h"
 #include "chrome/installer/setup/setup_constants.h"
 #include "chrome/installer/setup/setup_util.h"
 #include "chrome/installer/setup/update_active_setup_version_work_item.h"
@@ -825,7 +824,7 @@
 
   // Create the directory in which persistent metrics will be stored.
   const base::FilePath histogram_storage_dir(
-      PersistentHistogramStorage::GetReportedStorageDir(target_path));
+      target_path.AppendASCII(kSetupHistogramAllocatorName));
   install_list->AddCreateDirWorkItem(histogram_storage_dir);
 
   if (installer_state.system_install()) {
diff --git a/chrome/installer/setup/persistent_histogram_storage.cc b/chrome/installer/setup/persistent_histogram_storage.cc
deleted file mode 100644
index 309a149..0000000
--- a/chrome/installer/setup/persistent_histogram_storage.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 "chrome/installer/setup/persistent_histogram_storage.h"
-
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/files/important_file_writer.h"
-#include "base/logging.h"
-#include "base/metrics/histogram_base.h"
-#include "base/metrics/persistent_histogram_allocator.h"
-#include "base/metrics/persistent_memory_allocator.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/stringprintf.h"
-#include "base/time/time.h"
-#include "chrome/installer/util/util_constants.h"
-
-namespace installer {
-
-PersistentHistogramStorage::PersistentHistogramStorage() {
-  base::GlobalHistogramAllocator::CreateWithLocalMemory(
-      1 << 20,  // 1 MiB
-      0,        // No identifier.
-      kSetupHistogramAllocatorName);
-  base::GlobalHistogramAllocator::Get()->CreateTrackingHistograms(
-      kSetupHistogramAllocatorName);
-}
-
-PersistentHistogramStorage::~PersistentHistogramStorage() {
-  base::PersistentHistogramAllocator* allocator =
-      base::GlobalHistogramAllocator::Get();
-  allocator->UpdateTrackingHistograms();
-
-  // Stop if |storage_dir_| isn't set or does not exist. That can happen if the
-  // product hasn't been installed yet or if it has been uninstalled.
-  if (storage_dir_.empty() || !base::DirectoryExists(storage_dir_))
-    return;
-
-  // Save data using the current time as the filename. The actual filename
-  // doesn't matter (so long as it ends with the correct extension) but this
-  // works as well as anything.
-  base::Time::Exploded exploded;
-  base::Time::Now().LocalExplode(&exploded);
-  const base::FilePath file_path =
-      storage_dir_
-          .AppendASCII(base::StringPrintf("%04d%02d%02d%02d%02d%02d",
-                                          exploded.year, exploded.month,
-                                          exploded.day_of_month, exploded.hour,
-                                          exploded.minute, exploded.second))
-          .AddExtension(base::PersistentMemoryAllocator::kFileExtension);
-
-  base::StringPiece contents(static_cast<const char*>(allocator->data()),
-                             allocator->used());
-  if (base::ImportantFileWriter::WriteFileAtomically(file_path, contents))
-    VLOG(1) << "Persistent histograms saved in file: " << file_path.value();
-}
-
-// static
-base::FilePath PersistentHistogramStorage::GetReportedStorageDir(
-    const base::FilePath& target_path) {
-  return target_path.AppendASCII(kSetupHistogramAllocatorName);
-}
-
-}  // namespace installer
diff --git a/chrome/installer/setup/persistent_histogram_storage.h b/chrome/installer/setup/persistent_histogram_storage.h
deleted file mode 100644
index db0871e7..0000000
--- a/chrome/installer/setup/persistent_histogram_storage.h
+++ /dev/null
@@ -1,45 +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 CHROME_INSTALLER_SETUP_PERSISTENT_HISTOGRAM_STORAGE_H_
-#define CHROME_INSTALLER_SETUP_PERSISTENT_HISTOGRAM_STORAGE_H_
-
-#include "base/files/file_path.h"
-#include "base/macros.h"
-
-namespace installer {
-
-// When a PersistentHistogramStorage is destructed, histograms recorded during
-// its lifetime are persisted in the directory set by set_storage_dir().
-// Histograms are not persisted if set_storage_dir() has not been called or if
-// the storage directory does not exist on destruction.
-// PersistentHistogramStorage should be instantiated as early as possible in the
-// process lifetime and should never be instantiated again. Persisted histograms
-// will eventually be reported by the browser process.
-class PersistentHistogramStorage {
- public:
-  PersistentHistogramStorage();
-  ~PersistentHistogramStorage();
-
-  // Sets |storage_dir| as the directory in which histograms will be persisted
-  // when this PersistentHistogramStorage is destructed.
-  void set_storage_dir(const base::FilePath& storage_dir) {
-    storage_dir_ = storage_dir;
-  }
-
-  // Returns a directory in which setup histograms must be persisted to be
-  // reported by a product installed in |target_path|. Should be used as
-  // argument to set_storage_dir().
-  static base::FilePath GetReportedStorageDir(
-      const base::FilePath& target_path);
-
- private:
-  base::FilePath storage_dir_;
-
-  DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorage);
-};
-
-}  // namespace installer
-
-#endif  // CHROME_INSTALLER_SETUP_PERSISTENT_HISTOGRAM_STORAGE_H_
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 74cece8..32b8c44 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -24,6 +24,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/persistent_histogram_storage.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/path_service.h"
 #include "base/process/launch.h"
@@ -53,7 +54,6 @@
 #include "chrome/installer/setup/install_worker.h"
 #include "chrome/installer/setup/installer_crash_reporting.h"
 #include "chrome/installer/setup/installer_state.h"
-#include "chrome/installer/setup/persistent_histogram_storage.h"
 #include "chrome/installer/setup/setup_constants.h"
 #include "chrome/installer/setup/setup_install_details.h"
 #include "chrome/installer/setup/setup_singleton.h"
@@ -1290,8 +1290,12 @@
   if (!installer::IsProcessorSupported())
     return installer::CPU_NOT_SUPPORTED;
 
-  // Persist histograms so they can be uploaded later.
-  installer::PersistentHistogramStorage persistent_histogram_storage;
+  // Persist histograms so they can be uploaded later. The storage directory is
+  // created during installation when the main WorkItemList is evaluated. So
+  // disable storage directory creation in PersistentHistogramStorage.
+  base::PersistentHistogramStorage persistent_histogram_storage(
+      installer::kSetupHistogramAllocatorName,
+      base::PersistentHistogramStorage::StorageDirCreation::kDisable);
 
   // The exit manager is in charge of calling the dtors of singletons.
   base::AtExitManager exit_manager;
@@ -1331,9 +1335,8 @@
   VLOG(1) << "is_migrating_to_single is "
           << installer_state.is_migrating_to_single();
 
-  persistent_histogram_storage.set_storage_dir(
-      installer::PersistentHistogramStorage::GetReportedStorageDir(
-          installer_state.target_path()));
+  persistent_histogram_storage.set_storage_base_dir(
+      installer_state.target_path());
 
   installer::ConfigureCrashReporting(installer_state);
   installer::SetInitialCrashKeys(installer_state);
diff --git a/chrome/test/data/webui/print_preview/link_container_test.js b/chrome/test/data/webui/print_preview/link_container_test.js
new file mode 100644
index 0000000..6acbf6f5
--- /dev/null
+++ b/chrome/test/data/webui/print_preview/link_container_test.js
@@ -0,0 +1,83 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('link_container_test', function() {
+  /** @enum {string} */
+  const TestNames = {
+    HideInAppKioskMode: 'hide in app kiosk mode',
+    SystemDialogLinkClick: 'system dialog link click',
+    OpenInPreviewLinkClick: 'open in preview link click',
+  };
+
+  const suiteName = 'LinkContainerTest';
+  suite(suiteName, function() {
+    /** @type {?PrintPreviewLinkContainerElement} */
+    let linkContainer = null;
+
+    /** @override */
+    setup(function() {
+      PolymerTest.clearBody();
+      linkContainer = document.createElement('print-preview-link-container');
+      document.body.appendChild(linkContainer);
+
+      /** Set inputs to some defaults. */
+      const fooDestination = new print_preview.Destination(
+          'FooPrinter', print_preview.DestinationType.LOCAL,
+          print_preview.DestinationOrigin.LOCAL, 'Foo Printer',
+          false /* isRecent */,
+          print_preview.DestinationConnectionStatus.ONLINE);
+      fooDestination.capabilities =
+          print_preview_test_utils.getCddTemplate(fooDestination.id)
+              .capabilities;
+      linkContainer.destination = fooDestination;
+      linkContainer.appKioskMode = false;
+      linkContainer.disabled = false;
+    });
+
+    /** Tests that the system dialog link is hidden in App Kiosk mode. */
+    test(assert(TestNames.HideInAppKioskMode), function() {
+      const systemDialogLink = linkContainer.$.systemDialogLink;
+      assertFalse(systemDialogLink.hidden);
+      linkContainer.set('appKioskMode', true);
+      assertTrue(systemDialogLink.hidden);
+    });
+
+    /**
+     * Test that clicking the system dialog link click results in an event
+     * firing, and the throbber appears on non-Windows.
+     */
+    test(assert(TestNames.SystemDialogLinkClick), function() {
+      const promise =
+          test_util.eventToPromise('print-with-system-dialog', linkContainer);
+      const throbber = linkContainer.$.systemDialogThrobber;
+      assertTrue(throbber.hidden);
+
+      linkContainer.$.systemDialogLink.click();
+      return promise.then(function() {
+        assertEquals(cr.isWindows, throbber.hidden);
+      });
+    });
+
+    /**
+     * Test that clicking the open in preview link correctly results in a
+     * property change and that the throbber appears. Mac only.
+     */
+    test(assert(TestNames.OpenInPreviewLinkClick), function() {
+      const throbber = linkContainer.$.openPdfInPreviewThrobber;
+      assertTrue(throbber.hidden);
+      const promise =
+          test_util.eventToPromise('open-pdf-in-preview', linkContainer);
+
+      linkContainer.$.openPdfInPreviewLink.click();
+      return promise.then(function() {
+        assertFalse(throbber.hidden);
+      });
+    });
+  });
+
+  return {
+    suiteName: suiteName,
+    TestNames: TestNames,
+  };
+});
diff --git a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
index 17eadab..cf78505 100644
--- a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
@@ -264,3 +264,77 @@
 TEST_F('PrintPreviewPreviewGenerationTest', 'Destination', function() {
   this.runMochaTest(preview_generation_test.TestNames.Destination);
 });
+
+GEN('#if !defined(OS_CHROMEOS)');
+PrintPreviewLinkContainerTest = class extends NewPrintPreviewTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/new/link_container.html';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      '../settings/test_util.js',
+      'print_preview_test_utils.js',
+      'link_container_test.js',
+    ]);
+  }
+
+  /** @override */
+  get suiteName() {
+    return link_container_test.suiteName;
+  }
+};
+
+TEST_F('PrintPreviewLinkContainerTest', 'HideInAppKioskMode', function() {
+  this.runMochaTest(link_container_test.TestNames.HideInAppKioskMode);
+});
+
+TEST_F('PrintPreviewLinkContainerTest', 'SystemDialogLinkClick', function() {
+  this.runMochaTest(link_container_test.TestNames.SystemDialogLinkClick);
+});
+GEN('#endif');  // !defined(OS_CHROMEOS)
+
+GEN('#if defined(OS_MACOSX)');
+TEST_F('PrintPreviewLinkContainerTest', 'OpenInPreviewLinkClick', function() {
+  this.runMochaTest(link_container_test.TestNames.OpenInPreviewLinkClick);
+});
+GEN('#endif');  // defined(OS_MACOSX)
+
+GEN('#if defined(OS_WIN) || defined(OS_MACOSX)');
+PrintPreviewSystemDialogBrowserTest = class extends NewPrintPreviewTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/new/app.html';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      ROOT_PATH + 'chrome/test/data/webui/settings/test_util.js',
+      '../test_browser_proxy.js',
+      'native_layer_stub.js',
+      'plugin_stub.js',
+      'print_preview_test_utils.js',
+      'system_dialog_browsertest.js',
+    ]);
+  }
+
+  /** @override */
+  get suiteName() {
+    return system_dialog_browsertest.suiteName;
+  }
+};
+
+TEST_F('PrintPreviewSystemDialogBrowserTest', 'LinkTriggersLocalPrint',
+       function() {
+  this.runMochaTest(system_dialog_browsertest.TestNames.LinkTriggersLocalPrint);
+});
+
+TEST_F('PrintPreviewSystemDialogBrowserTest', 'InvalidSettingsDisableLink',
+       function() {
+  this.runMochaTest(
+      system_dialog_browsertest.TestNames.InvalidSettingsDisableLink);
+});
+GEN('#endif');  // defined(OS_WIN) || defined(OS_MACOSX)
diff --git a/chrome/test/data/webui/print_preview/system_dialog_browsertest.js b/chrome/test/data/webui/print_preview/system_dialog_browsertest.js
new file mode 100644
index 0000000..5d0696a
--- /dev/null
+++ b/chrome/test/data/webui/print_preview/system_dialog_browsertest.js
@@ -0,0 +1,123 @@
+// 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.
+
+cr.define('system_dialog_browsertest', function() {
+  /** @enum {string} */
+  const TestNames = {
+    LinkTriggersLocalPrint: 'link triggers local print',
+    InvalidSettingsDisableLink: 'invalid settings disable link',
+  };
+
+  const suiteName = 'SystemDialogBrowserTest';
+  suite(suiteName, function() {
+    /** @type {?PrintPreviewAppElement} */
+    let page = null;
+
+    /** @type {?print_preview.NativeLayer} */
+    let nativeLayer = null;
+
+    /** @type {?PrintPreviewLinkContainerElement} */
+    let linkContainer = null;
+
+    /** @type {?HTMLElement} */
+    let link = null;
+
+    /** @type {string} */
+    let printTicketKey = '';
+
+    /** @override */
+    setup(function() {
+      nativeLayer = new print_preview.NativeLayerStub();
+      print_preview.NativeLayer.setInstance(nativeLayer);
+      PolymerTest.clearBody();
+
+      const initialSettings = {
+        isInKioskAutoPrintMode: false,
+        isInAppKioskMode: false,
+        thousandsDelimeter: ',',
+        decimalDelimeter: '.',
+        unitType: 1,
+        previewModifiable: true,
+        documentTitle: 'title',
+        documentHasSelection: true,
+        shouldPrintSelectionOnly: false,
+        printerName: 'FooDevice',
+        serializedAppStateStr: null,
+        serializedDefaultDestinationSelectionRulesStr: null
+      };
+      nativeLayer.setInitialSettings(initialSettings);
+      nativeLayer.setLocalDestinationCapabilities(
+          print_preview_test_utils.getCddTemplate(initialSettings.printerName));
+
+      page = document.createElement('print-preview-app');
+      linkContainer = page.$$('print-preview-link-container');
+      const previewArea = page.$$('print-preview-preview-area');
+      previewArea.plugin_ = new print_preview.PDFPluginStub(previewArea);
+      previewArea.plugin_.setLoadCallback(
+          previewArea.onPluginLoad_.bind(previewArea));
+      document.body.appendChild(page);
+      return Promise.all([
+        nativeLayer.whenCalled('getInitialSettings'),
+        nativeLayer.whenCalled('getPrinterCapabilities'),
+      ]).then(function() {
+        return nativeLayer.whenCalled('getPreview');
+      }).then(function() {
+        assertEquals('FooDevice', page.destination_.id);
+        link = cr.isWindows ?
+            linkContainer.$.systemDialogLink :
+            linkContainer.$.openPdfInPreviewLink;
+        printTicketKey = cr.isWindows ? 'showSystemDialog' : 'OpenPDFInPreview';
+      });
+    });
+
+    test(assert(TestNames.LinkTriggersLocalPrint), function() {
+      assertFalse(linkContainer.disabled);
+      assertFalse(link.hidden);
+      link.click();
+      // Should result in a print call and dialog should close.
+      return nativeLayer.whenCalled('print').then(function(printTicket) {
+        expectTrue(JSON.parse(printTicket)[printTicketKey]);
+        return nativeLayer.whenCalled('dialogClose');
+      });
+    });
+
+    test(assert(TestNames.InvalidSettingsDisableLink), function() {
+      assertFalse(linkContainer.disabled);
+      assertFalse(link.hidden);
+
+      const pageSettings = page.$$('print-preview-pages-settings');
+      assertFalse(pageSettings.hidden);
+      nativeLayer.resetResolver('getPreview');
+
+      // Set page settings to a bad value
+      pageSettings.$$('#custom-radio-button').checked = true;
+      pageSettings.$$('#all-radio-button').dispatchEvent(
+        new CustomEvent('change'));
+      const pageSettingsInput = pageSettings.$$('.user-value');
+      pageSettingsInput.value = 'abc';
+      pageSettingsInput.dispatchEvent(new CustomEvent('input'));
+
+      // No new preview
+      nativeLayer.whenCalled('getPreview').then(function() {
+        assertTrue(false);
+      });
+
+      return test_util.eventToPromise('input-change', pageSettings).then(
+          function() {
+            // Expect disabled print button and Pdf in preview link
+            const header = page.$$('print-preview-header');
+            const printButton = header.$$('.print');
+            assertTrue(printButton.disabled);
+            assertTrue(linkContainer.disabled);
+            assertFalse(link.hidden);
+            assertTrue(link.disabled);
+          });
+    });
+  });
+
+  return {
+    suiteName: suiteName,
+    TestNames: TestNames,
+  };
+});
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 91a2deb..c06c05c2 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -1874,3 +1874,28 @@
 TEST_F('CrSettingsOnStartupPageTest', 'All', function() {
   mocha.run();
 });
+GEN('#if defined(OS_CHROMEOS)');
+
+/**
+ * @constructor
+ * @extends {CrSettingsBrowserTest}
+ */
+function CrSettingsDisplaySizeSliderTest() {}
+
+CrSettingsDisplaySizeSliderTest.prototype = {
+  __proto__: CrSettingsBrowserTest.prototype,
+
+  /** @override */
+  browsePreload: 'chrome://settings/device_page/display_size_slider.html',
+
+  /** @override */
+  extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
+    'display_size_slider_test.js',
+  ]),
+};
+
+TEST_F('CrSettingsDisplaySizeSliderTest', 'All', function() {
+  mocha.run();
+});
+GEN('#endif  // defined(OS_CHROMEOS)');
+
diff --git a/chrome/test/data/webui/settings/display_size_slider_test.js b/chrome/test/data/webui/settings/display_size_slider_test.js
new file mode 100644
index 0000000..a1bf439
--- /dev/null
+++ b/chrome/test/data/webui/settings/display_size_slider_test.js
@@ -0,0 +1,292 @@
+// 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 Suite of tests for display-size-slider. */
+suite('DisplaySizeSlider', function() {
+  /** @type {!CrSliderElement} */
+  let slider;
+
+  /** @type {!SliderTicks} */
+  let ticks = [];
+
+  setup(function() {
+    PolymerTest.clearBody();
+    const tickValues = [2, 4, 8, 16, 32, 64, 128];
+    ticks = [];
+    for (let i = 0; i < tickValues.length; i++) {
+      ticks.push({
+        value: tickValues[i],
+        label: tickValues[i].toString()
+      });
+    }
+
+    slider = document.createElement('display-size-slider');
+    slider.ticks = ticks;
+    slider.pref = {
+      type: chrome.settingsPrivate.PrefType.NUMBER,
+      value: ticks[3].value,
+    };
+    document.body.appendChild(slider);
+  });
+
+  test('initialization', function() {
+    const selectedIndex = 3;
+
+    assertFalse(slider.disabled);
+    assertFalse(slider.dragging);
+    assertFalse(slider.expand);
+    expectEquals(selectedIndex, slider.index);
+    expectEquals(ticks.length - 1, slider.markers.length);
+    expectEquals(ticks[selectedIndex].value, slider.pref.value);
+  });
+
+  test('check knob position changes', function() {
+    let expectedIndex = 3;
+    expectEquals(expectedIndex, slider.index);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    let expectedLeftPercentage = (expectedIndex * 100) / (ticks.length - 1);
+    expectEquals(expectedLeftPercentage + '%', slider.$.sliderKnob.style.left);
+
+    // Reset the index to the first tick.
+    slider.resetToMinIndex_();
+    expectedIndex = 0;
+    expectEquals(expectedIndex, slider.index);
+    expectEquals('0%', slider.$.sliderKnob.style.left);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    // Decrementing the slider below the lowest index should have no effect.
+    slider.decrement_();
+    expectEquals(expectedIndex, slider.index);
+    expectEquals('0%', slider.$.sliderKnob.style.left);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    // Reset the index to the last tick.
+    slider.resetToMaxIndex_();
+    expectedIndex = ticks.length - 1;
+    expectEquals(expectedIndex, slider.index);
+    expectEquals('100%', slider.$.sliderKnob.style.left);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    // Incrementing the slider when it is already at max should have no effect.
+    slider.increment_();
+    expectEquals(expectedIndex, slider.index);
+    expectEquals('100%', slider.$.sliderKnob.style.left);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    slider.decrement_();
+    expectedIndex -= 1;
+    expectEquals(expectedIndex, slider.index);
+    expectedLeftPercentage = (expectedIndex * 100) / (ticks.length - 1);
+    expectEquals(Math.round(expectedLeftPercentage),
+                 Math.round(parseFloat(slider.$.sliderKnob.style.left)));
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+  });
+
+  test('check keyboard events', function() {
+    let expectedIndex = 3;
+    expectEquals(expectedIndex, slider.index);
+
+    // Right keypress should increment the slider by 1.
+    MockInteractions.pressAndReleaseKeyOn(slider, 39 /* right */);
+    expectedIndex += 1;
+    expectEquals(expectedIndex, slider.index);
+
+    // Left keypress should decrement the slider by 1.
+    MockInteractions.pressAndReleaseKeyOn(slider, 37 /* left */);
+    expectedIndex -= 1;
+    expectEquals(expectedIndex, slider.index);
+
+    // Page down should take the slider to the first value.
+    MockInteractions.pressAndReleaseKeyOn(slider, 34 /* page down */);
+    expectedIndex = 0;
+    expectEquals(expectedIndex, slider.index);
+
+    // Page up should take the slider to the last value.
+    MockInteractions.pressAndReleaseKeyOn(slider, 33 /* page up */);
+    expectedIndex = ticks.length - 1;
+    expectEquals(expectedIndex, slider.index);
+
+    // Down keypress should decrement the slider index by 1.
+    MockInteractions.pressAndReleaseKeyOn(slider, 40 /* down */);
+    expectedIndex -= 1;
+    expectEquals(expectedIndex, slider.index);
+
+    // Up keypress should increment the slider index by 1.
+    MockInteractions.pressAndReleaseKeyOn(slider, 38 /* up */);
+    expectedIndex += 1;
+    expectEquals(expectedIndex, slider.index);
+
+    // Home button should take the slider to the first value.
+    MockInteractions.pressAndReleaseKeyOn(slider, 36 /* home */);
+    expectedIndex = 0;
+    expectEquals(expectedIndex, slider.index);
+
+    // End button should take the slider to the last value.
+    MockInteractions.pressAndReleaseKeyOn(slider, 35 /* up */);
+    expectedIndex = ticks.length - 1;
+    expectEquals(expectedIndex, slider.index);
+  });
+
+  test('set pref updates slider', function() {
+    let expectedIndex = 3;
+    expectEquals(expectedIndex, slider.index);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+
+    let newIndex = 4;
+    slider.set('pref.value', ticks[newIndex].value);
+    expectEquals(newIndex, slider.index);
+
+    let expectedLeftPercentage = (newIndex * 100) / (ticks.length - 1);
+    expectEquals(Math.round(expectedLeftPercentage),
+                 Math.round(parseFloat(slider.$.sliderKnob.style.left)));
+  });
+
+  test('check label values', function() {
+    expectEquals('none', getComputedStyle(slider.$.labelContainer).display);
+    let hoverClassName = 'hover';
+    slider.$.sliderContainer.classList.add(hoverClassName);
+    expectEquals('block', getComputedStyle(slider.$.labelContainer).display);
+
+    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
+
+    slider.increment_();
+    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
+
+    slider.resetToMaxIndex_();
+    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
+
+    slider.resetToMinIndex_();
+    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
+  });
+
+  test('check knob expansion', function() {
+    assertFalse(slider.expand);
+    let oldIndex = slider.index;
+
+    MockInteractions.down(slider.$.sliderKnob);
+    assertTrue(slider.expand);
+    expectEquals(oldIndex, slider.index);
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+
+
+    MockInteractions.up(slider.$.sliderKnob);
+    assertFalse(slider.expand);
+    expectEquals(oldIndex, slider.index);
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+  });
+
+  test('mouse interactions with the slider knobs', function() {
+    let oldIndex = slider.index;
+    const sliderKnob = slider.$.sliderKnob;
+    // Width of each tick.
+    const tickWidth = slider.$.sliderBar.offsetWidth / (ticks.length - 1);
+
+    MockInteractions.down(sliderKnob);
+
+    let currentPos = MockInteractions.middleOfNode(sliderKnob);
+    let nextPos = {
+      x: currentPos.x + tickWidth,
+      y: currentPos.y
+    };
+    MockInteractions.move(sliderKnob, currentPos, nextPos);
+
+    // Mouse is still down. So the slider should still be expanded.
+    assertTrue(slider.expand);
+
+    // The label should be visible.
+    expectEquals('block', getComputedStyle(slider.$.labelContainer).display);
+
+    // We moved by 1 tick width, so the slider index must have increased.
+    expectEquals(oldIndex + 1, slider.index);
+
+    // The mouse is still down, so the pref should not be updated.
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+
+    MockInteractions.up(sliderKnob);
+
+    // Now that the mouse is down, the pref value should be updated.
+    expectEquals(ticks[oldIndex + 1].value, slider.pref.value);
+
+    oldIndex = slider.index;
+    MockInteractions.track(sliderKnob, -3 * tickWidth, 0);
+    expectEquals(oldIndex - 3, slider.index);
+    expectEquals(ticks[oldIndex - 3].value, slider.pref.value);
+
+    // Moving by a very large amount should clamp the value.
+    oldIndex = slider.index;
+    MockInteractions.track(sliderKnob, 100 * tickWidth, 0);
+    expectEquals(ticks.length - 1, slider.index);
+    expectEquals(ticks[ticks.length - 1].value, slider.pref.value);
+
+    // Similarly for the other side.
+    oldIndex = slider.index;
+    MockInteractions.track(sliderKnob, -100 * tickWidth, 0);
+    expectEquals(0, slider.index);
+    expectEquals(ticks[0].value, slider.pref.value);
+  });
+
+  test('mouse interaction with the bar', function() {
+    const sliderBar = slider.$.sliderBar;
+    const sliderBarOrigin = {
+      x: sliderBar.getBoundingClientRect().x,
+      y: sliderBar.getBoundingClientRect().y
+    };
+
+    let oldIndex = slider.index;
+    MockInteractions.down(sliderBar, sliderBarOrigin);
+
+    // Mouse down on the left end of the slider bar should move the knob there.
+    expectEquals(0, slider.index);
+    expectEquals(0, Math.round(parseFloat(slider.$.sliderKnob.style.left)));
+
+    // However the pref value should not update until the mouse is released.
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+
+    // Release mouse to update pref value.
+    MockInteractions.up(sliderBar);
+    expectEquals(ticks[slider.index].value, slider.pref.value);
+
+    const tickWidth =
+        sliderBar.getBoundingClientRect().width / (ticks.length - 1);
+    let expectedIndex = 3;
+    // The knob position for the 3rd index.
+    let sliderBarPos = {
+      x: sliderBarOrigin.x + tickWidth * expectedIndex,
+      y: sliderBarOrigin.y
+    };
+
+    oldIndex = slider.index;
+    // Clicking at the 3rd index position on the slider bar should update the
+    // knob.
+    MockInteractions.down(sliderBar, sliderBarPos);
+    let expectedLeftPercentage =
+        (tickWidth * expectedIndex * 100) / sliderBar.offsetWidth;
+    expectEquals(Math.round(expectedLeftPercentage),
+                 Math.round(parseFloat(slider.$.sliderKnob.style.left)));
+    expectEquals(expectedIndex, slider.index);
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+
+    // The slider has not yet started dragging.
+    assertFalse(slider.dragging);
+
+    expectedIndex = 5;
+    const nextSliderBarPos = {
+      x: sliderBarPos.x + tickWidth * (expectedIndex - slider.index),
+      y: sliderBarPos.y
+    };
+    MockInteractions.move(sliderBar, sliderBarPos, nextSliderBarPos);
+    expectEquals(expectedIndex, slider.index);
+    expectedLeftPercentage =
+        (tickWidth * expectedIndex * 100) / sliderBar.offsetWidth;
+    expectEquals(Math.round(expectedLeftPercentage),
+                 Math.round(parseFloat(slider.$.sliderKnob.style.left)));
+
+    expectEquals(ticks[oldIndex].value, slider.pref.value);
+
+    MockInteractions.up(sliderBar);
+    expectEquals(ticks[expectedIndex].value, slider.pref.value);
+  });
+});
diff --git a/chrome/test/data/webui/settings/fake_language_settings_private.js b/chrome/test/data/webui/settings/fake_language_settings_private.js
index dfa02c0..3f85d626 100644
--- a/chrome/test/data/webui/settings/fake_language_settings_private.js
+++ b/chrome/test/data/webui/settings/fake_language_settings_private.js
@@ -255,8 +255,7 @@
      *     callback
      */
     getSpellcheckDictionaryStatuses(callback) {
-      assertNotReached(
-          'Not implemented in fake: getSpellcheckDictionaryStatuses');
+      callback([]);
     }
 
     /**
@@ -343,6 +342,19 @@
           this.settingsPrefs_.prefs.settings.language.preload_engines.value
               .replace(inputMethodId, ''));
     }
+
+    /**
+     * Tries to download the dictionary after a failed download.
+     * @param {string} languageCode
+     */
+    retryDownloadDictionary(languageCode) {
+      this.onSpellcheckDictionariesChanged.callListeners([
+        {languageCode, isReady: false, isDownlading: true},
+      ]);
+      this.onSpellcheckDictionariesChanged.callListeners([
+        {languageCode, isReady: false, downloadFailed: true},
+      ]);
+    }
   }
 
   // List of language-related preferences suitable for testing.
diff --git a/chrome/test/data/webui/settings/languages_page_tests.js b/chrome/test/data/webui/settings/languages_page_tests.js
index d5da04b..ae770fcc 100644
--- a/chrome/test/data/webui/settings/languages_page_tests.js
+++ b/chrome/test/data/webui/settings/languages_page_tests.js
@@ -406,16 +406,19 @@
       }
     });
 
-    test(TestNames.Spellcheck, function() {
-      const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
-      const spellCheckSettingsExist = !!spellCheckCollapse;
-      if (cr.isMac) {
-        assertFalse(spellCheckSettingsExist);
-      } else {
+    suite(TestNames.Spellcheck, function() {
+      test('structure', function() {
+        const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
+        const spellCheckSettingsExist = !!spellCheckCollapse;
+        if (cr.isMac) {
+          assertFalse(spellCheckSettingsExist);
+          return;
+        }
+
         assertTrue(spellCheckSettingsExist);
 
-        // The row button should have a secondary row specifying which language
-        // spell check is enabled for.
+        // The row button should have a secondary row specifying which
+        // language spell check is enabled for.
         const triggerRow = languagesPage.$.spellCheckSubpageTrigger;
 
         // en-US starts with spellcheck enabled, so the secondary row is
@@ -434,8 +437,7 @@
         MockInteractions.tap(spellcheckLanguageToggle);
         assertFalse(spellcheckLanguageToggle.checked);
         assertEquals(
-            0,
-            languageHelper.prefs.spellcheck.dictionaries.value.length);
+            0, languageHelper.prefs.spellcheck.dictionaries.value.length);
 
         // Now the secondary row is empty, so it shouldn't be shown.
         assertFalse(triggerRow.classList.contains('two-line'));
@@ -467,7 +469,57 @@
         languageHelper.setPrefValue('browser.enable_spellchecking', true);
         Polymer.dom.flush();
         assertFalse(!!triggerRow.querySelector('cr-policy-pref-indicator'));
-      }
+      });
+
+      test('error handling', function() {
+        if (cr.isMac)
+          return;
+
+        const checkAllHidden = nodes => {
+          assertTrue(nodes.every(node => node.hidden));
+        };
+
+        const languageSettingsPrivate =
+            browserProxy.getLanguageSettingsPrivate();
+        const spellCheckCollapse = languagesPage.$.spellCheckCollapse;
+        const errorDivs = Array.from(
+            spellCheckCollapse.querySelectorAll('.name-with-error-list div'));
+        assertEquals(4, errorDivs.length);
+        checkAllHidden(errorDivs);
+
+        const retryButtons =
+            Array.from(spellCheckCollapse.querySelectorAll('paper-button'));
+        assertEquals(2, retryButtons.length);
+        checkAllHidden(retryButtons);
+
+        const languageCode =
+            languagesPage.get('languages.enabled.0.language.code');
+        languageSettingsPrivate.onSpellcheckDictionariesChanged.callListeners([
+          {languageCode, isReady: false, downloadFailed: true},
+        ]);
+
+        Polymer.dom.flush();
+        assertFalse(errorDivs[0].hidden);
+        checkAllHidden(errorDivs.slice(1));
+        assertFalse(retryButtons[0].hidden);
+        assertTrue(retryButtons[1].hidden);
+
+        // Check that more information is provided when subsequent downloads
+        // fail.
+        const moreInfo = errorDivs[1];
+        assertTrue(moreInfo.hidden);
+        // No change when status is the same as last update.
+        const currentStatus =
+            languagesPage.get('languages.enabled.0.downloadDictionaryStatus');
+        languageSettingsPrivate.onSpellcheckDictionariesChanged.callListeners(
+            [currentStatus]);
+        Polymer.dom.flush();
+        assertTrue(moreInfo.hidden);
+
+        retryButtons[0].click();
+        Polymer.dom.flush();
+        assertFalse(moreInfo.hidden);
+      });
     });
   });
 
diff --git a/chromeos/dbus/fake_session_manager_client.cc b/chromeos/dbus/fake_session_manager_client.cc
index 5cbc898..095806c 100644
--- a/chromeos/dbus/fake_session_manager_client.cc
+++ b/chromeos/dbus/fake_session_manager_client.cc
@@ -248,6 +248,8 @@
     observer.EmitLoginPromptVisibleCalled();
 }
 
+void FakeSessionManagerClient::EmitAshInitialized() {}
+
 void FakeSessionManagerClient::RestartJob(int socket_fd,
                                           const std::vector<std::string>& argv,
                                           VoidDBusMethodCallback callback) {}
diff --git a/chromeos/dbus/fake_session_manager_client.h b/chromeos/dbus/fake_session_manager_client.h
index d0f201d11..78c376c 100644
--- a/chromeos/dbus/fake_session_manager_client.h
+++ b/chromeos/dbus/fake_session_manager_client.h
@@ -41,6 +41,7 @@
   bool HasObserver(const Observer* observer) const override;
   bool IsScreenLocked() const override;
   void EmitLoginPromptVisible() override;
+  void EmitAshInitialized() override;
   void RestartJob(int socket_fd,
                   const std::vector<std::string>& argv,
                   VoidDBusMethodCallback callback) override;
diff --git a/chromeos/dbus/session_manager_client.cc b/chromeos/dbus/session_manager_client.cc
index 12804739..ebb9fc19 100644
--- a/chromeos/dbus/session_manager_client.cc
+++ b/chromeos/dbus/session_manager_client.cc
@@ -155,6 +155,11 @@
       observer.EmitLoginPromptVisibleCalled();
   }
 
+  void EmitAshInitialized() override {
+    SimpleMethodCallToSessionManager(
+        login_manager::kSessionManagerEmitAshInitialized);
+  }
+
   void RestartJob(int socket_fd,
                   const std::vector<std::string>& argv,
                   VoidDBusMethodCallback callback) override {
diff --git a/chromeos/dbus/session_manager_client.h b/chromeos/dbus/session_manager_client.h
index d88f709e0..afde5e59 100644
--- a/chromeos/dbus/session_manager_client.h
+++ b/chromeos/dbus/session_manager_client.h
@@ -107,6 +107,9 @@
   // Kicks off an attempt to emit the "login-prompt-visible" upstart signal.
   virtual void EmitLoginPromptVisible() = 0;
 
+  // Kicks off an attempt to emit the "ash-initialized" upstart signal.
+  virtual void EmitAshInitialized() = 0;
+
   // Restarts the browser job, passing |argv| as the updated command line.
   // The session manager requires a RestartJob caller to open a socket pair and
   // pass one end while holding the local end open for the duration of the call.
diff --git a/components/crash/content/app/breakpad_linux.cc b/components/crash/content/app/breakpad_linux.cc
index 7d0aa51..389bc0c 100644
--- a/components/crash/content/app/breakpad_linux.cc
+++ b/components/crash/content/app/breakpad_linux.cc
@@ -1713,8 +1713,6 @@
     static const char brand[] = "brand";
     static const char board[] = "board";
     static const char exception_info[] = "exception_info";
-    static const char custom_themes[] = "custom_themes";
-    static const char resources_version[] = "resources_version";
 
     base::android::BuildInfo* android_build_info =
         base::android::BuildInfo::GetInstance();
@@ -1740,11 +1738,6 @@
     writer.AddBoundary();
     writer.AddPairString(abi_name, android_build_info->abi_name());
     writer.AddBoundary();
-    writer.AddPairString(custom_themes, android_build_info->custom_themes());
-    writer.AddBoundary();
-    writer.AddPairString(resources_version,
-                         android_build_info->resources_version());
-    writer.AddBoundary();
     WriteAndroidPackage(writer, android_build_info);
     writer.AddBoundary();
     if (android_build_info->java_exception_info() != nullptr) {
diff --git a/components/infobars/core/infobar_container.h b/components/infobars/core/infobar_container.h
index 9707e91..bbd2950 100644
--- a/components/infobars/core/infobar_container.h
+++ b/components/infobars/core/infobar_container.h
@@ -34,9 +34,6 @@
  public:
   class Delegate {
    public:
-    // The separator color may vary depending on where the container is hosted.
-    virtual SkColor GetInfoBarSeparatorColor() const = 0;
-
     // The delegate is notified each time the infobar container changes height,
     // as well as when it stops animating.
     virtual void InfoBarContainerStateChanged(bool is_animating) = 0;
diff --git a/components/offline_pages/core/BUILD.gn b/components/offline_pages/core/BUILD.gn
index e24ca3882..2b55f7e 100644
--- a/components/offline_pages/core/BUILD.gn
+++ b/components/offline_pages/core/BUILD.gn
@@ -57,6 +57,8 @@
     "offline_page_model.h",
     "offline_page_model_event_logger.cc",
     "offline_page_model_event_logger.h",
+    "offline_page_thumbnail.cc",
+    "offline_page_thumbnail.h",
     "offline_page_types.h",
     "offline_pages_ukm_reporter.cc",
     "offline_pages_ukm_reporter.h",
@@ -162,6 +164,7 @@
     "offline_page_feature_unittest.cc",
     "offline_page_metadata_store_unittest.cc",
     "offline_page_model_event_logger_unittest.cc",
+    "offline_page_thumbnail_unittest.cc",
     "offline_pages_ukm_reporter_unittest.cc",
     "snapshot_controller_unittest.cc",
     "task_queue_unittest.cc",
diff --git a/components/offline_pages/core/archive_manager.h b/components/offline_pages/core/archive_manager.h
index 3f4cc83..dbb2990 100644
--- a/components/offline_pages/core/archive_manager.h
+++ b/components/offline_pages/core/archive_manager.h
@@ -34,9 +34,8 @@
   //   directory and records the total file size. This will include mhtml files
   //   shared into the public directory through other approaches.
   struct StorageStats {
-    int64_t total_archives_size() const {
-      return temporary_archives_size + private_archives_size +
-             public_archives_size;
+    int64_t internal_archives_size() const {
+      return temporary_archives_size + private_archives_size;
     }
     int64_t internal_free_disk_space;
     int64_t external_free_disk_space;
@@ -45,6 +44,10 @@
     int64_t public_archives_size;
   };
 
+  typedef base::OnceCallback<void(
+      const ArchiveManager::StorageStats& storage_stats)>
+      StorageStatsCallback;
+
   ArchiveManager(const base::FilePath& temporary_archives_dir,
                  const base::FilePath& private_archives_dir_,
                  const base::FilePath& public_archives_dir,
@@ -56,9 +59,7 @@
 
   // Gets stats about archive storage, i.e. sizes of all archive directories
   // and free disk spaces.
-  virtual void GetStorageStats(
-      base::OnceCallback<void(const StorageStats& storage_sizes)> callback)
-      const;
+  virtual void GetStorageStats(StorageStatsCallback callback) const;
 
   // Gets the archive directories.
   const base::FilePath& GetTemporaryArchivesDir() const;
diff --git a/components/offline_pages/core/archive_manager_unittest.cc b/components/offline_pages/core/archive_manager_unittest.cc
index 04b3591..b05d3ec 100644
--- a/components/offline_pages/core/archive_manager_unittest.cc
+++ b/components/offline_pages/core/archive_manager_unittest.cc
@@ -191,9 +191,8 @@
   PumpLoop();
   EXPECT_EQ(base::SysInfo::AmountOfFreeDiskSpace(temporary_archive_path()),
             last_storage_sizes().internal_free_disk_space);
-  EXPECT_EQ(base::ComputeDirectorySize(private_archive_path()) +
-                base::ComputeDirectorySize(public_archive_path()),
-            last_storage_sizes().total_archives_size());
+  EXPECT_EQ(base::ComputeDirectorySize(private_archive_path()),
+            last_storage_sizes().internal_archives_size());
   EXPECT_EQ(0, last_storage_sizes().temporary_archives_size);
 }
 
@@ -208,7 +207,7 @@
             last_storage_sizes().external_free_disk_space);
   EXPECT_EQ(base::ComputeDirectorySize(temporary_archive_path()) +
                 base::ComputeDirectorySize(private_archive_path()),
-            last_storage_sizes().total_archives_size());
+            last_storage_sizes().internal_archives_size());
   EXPECT_EQ(0, last_storage_sizes().public_archives_size);
 }
 
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.cc b/components/offline_pages/core/model/offline_page_model_taskified.cc
index abe4d88..296307d2 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified.cc
@@ -114,6 +114,45 @@
   callback.Run(all_items);
 }
 
+void ReportStorageUsage(const ArchiveManager::StorageStats& storage_stats) {
+  const int kMiB = 1024 * 1024;
+  int internal_free_disk_space_mib =
+      static_cast<int>(storage_stats.internal_free_disk_space / kMiB);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("OfflinePages.StorageInfo.InternalFreeSpaceMiB",
+                              internal_free_disk_space_mib, 1, 500000, 50);
+  int external_free_disk_space_mib =
+      static_cast<int>(storage_stats.external_free_disk_space / kMiB);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("OfflinePages.StorageInfo.ExternalFreeSpaceMiB",
+                              external_free_disk_space_mib, 1, 500000, 50);
+  int internal_page_size_mib =
+      static_cast<int>(storage_stats.internal_archives_size() / kMiB);
+  UMA_HISTOGRAM_COUNTS_10000("OfflinePages.StorageInfo.InternalArchiveSizeMiB",
+                             internal_page_size_mib);
+  int external_page_size_mib =
+      static_cast<int>(storage_stats.public_archives_size / kMiB);
+  UMA_HISTOGRAM_COUNTS_10000("OfflinePages.StorageInfo.ExternalArchiveSizeMiB",
+                             external_page_size_mib);
+
+  int64_t internal_volume_storage = storage_stats.internal_archives_size() +
+                                    storage_stats.internal_free_disk_space;
+  if (internal_volume_storage > 0) {
+    int internal_percentage =
+        static_cast<int>(100.0 * storage_stats.internal_archives_size() /
+                         internal_volume_storage);
+    UMA_HISTOGRAM_PERCENTAGE("OfflinePages.StorageInfo.InternalUsagePercentage",
+                             internal_percentage);
+  }
+
+  int64_t external_volume_storage = storage_stats.public_archives_size +
+                                    storage_stats.external_free_disk_space;
+  if (external_volume_storage > 0) {
+    int external_percentage = static_cast<int>(
+        100.0 * storage_stats.public_archives_size / external_volume_storage);
+    UMA_HISTOGRAM_PERCENTAGE("OfflinePages.StorageInfo.ExternalUsagePercentage",
+                             external_percentage);
+  }
+}
+
 }  // namespace
 
 // static
@@ -163,6 +202,7 @@
 void OfflinePageModelTaskified::SavePage(
     const SavePageParams& save_page_params,
     std::unique_ptr<OfflinePageArchiver> archiver,
+    content::WebContents* web_contents,
     const SavePageCallback& callback) {
   // Skip saving the page that is not intended to be saved, like local file
   // page.
@@ -192,7 +232,7 @@
       save_page_params.use_page_problem_detectors;
   archiver->CreateArchive(
       GetInternalArchiveDirectory(save_page_params.client_id.name_space),
-      create_archive_params,
+      create_archive_params, web_contents,
       base::Bind(&OfflinePageModelTaskified::OnCreateArchiveDone,
                  weak_ptr_factory_.GetWeakPtr(), save_page_params, offline_id,
                  GetCurrentTime(), callback));
@@ -382,6 +422,10 @@
                                       "OfflinePages.SavePageResult"),
       result, SavePageResult::RESULT_COUNT);
 
+  // Report storage usage if saving page succeeded.
+  if (result == SavePageResult::SUCCESS)
+    archive_manager_->GetStorageStats(base::BindOnce(&ReportStorageUsage));
+
   if (result == SavePageResult::ARCHIVE_CREATION_FAILED)
     CreateArchivesDirectoryIfNeeded();
   if (!callback.is_null())
@@ -482,6 +526,39 @@
                      weak_ptr_factory_.GetWeakPtr(), save_page_callback, page));
 }
 
+void OfflinePageModelTaskified::PublishInternalArchive(
+    const OfflinePageItem& offline_page,
+    std::unique_ptr<OfflinePageArchiver> archiver,
+    PublishPageCallback publish_done_callback) {
+  archiver->PublishArchive(
+      offline_page, task_runner_, archive_manager_->GetPublicArchivesDir(),
+      download_manager_.get(),
+      base::BindOnce(&OfflinePageModelTaskified::PublishInternalArchiveDone,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(publish_done_callback)));
+}
+
+void OfflinePageModelTaskified::PublishInternalArchiveDone(
+    PublishPageCallback publish_done_callback,
+    const OfflinePageItem& offline_page,
+    PublishArchiveResult* publish_results) {
+  // Return an empty OfflinePageItem if we were unable to move the page.  The
+  // offline_id will be 0, which marks it as invalid.
+  if (publish_results->move_result != SavePageResult::SUCCESS) {
+    OfflinePageItem empty_offline_page;
+    std::move(publish_done_callback).Run(empty_offline_page);
+    return;
+  }
+
+  // TODO(petewil): Update the OfflinePageModel with the new location for the
+  // page, which is found in move_results.new_file_path, and with the download
+  // ID found at move_results.download_id.  Return the updated offline_page to
+  // the callback.
+
+  // Return to the OfflinePageBridge callback passed in.
+  std::move(publish_done_callback).Run(offline_page);
+}
+
 void OfflinePageModelTaskified::OnAddPageForSavePageDone(
     const SavePageCallback& callback,
     const OfflinePageItem& page_attempted,
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 cf7a786..4c42c6e 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.h
+++ b/components/offline_pages/core/model/offline_page_model_taskified.h
@@ -81,6 +81,7 @@
 
   void SavePage(const SavePageParams& save_page_params,
                 std::unique_ptr<OfflinePageArchiver> archiver,
+                content::WebContents* web_contents,
                 const SavePageCallback& callback) override;
   void AddPage(const OfflinePageItem& page,
                const AddPageCallback& callback) override;
@@ -137,6 +138,12 @@
 
   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,
+      PublishPageCallback publish_done_callback) override;
+
   // Methods for testing only:
   OfflinePageMetadataStoreSQL* GetStoreForTesting() { return store_.get(); }
   void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
@@ -206,6 +213,11 @@
                           const OfflinePageItem& offline_page,
                           PublishArchiveResult* archive_result);
 
+  // Callback for when publishing an internal archive has completed.
+  void PublishInternalArchiveDone(PublishPageCallback publish_done_callback,
+                                  const OfflinePageItem& offline_page,
+                                  PublishArchiveResult* move_results);
+
   // Method for unpublishing the page from the system download manager.
   static void RemoveFromDownloadManager(
       SystemDownloadManager* download_manager,
diff --git a/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc b/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
index 89619e9..abe9800 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified_unittest.cc
@@ -283,7 +283,7 @@
   save_page_params.original_url = original_url;
   save_page_params.request_origin = request_origin;
   save_page_params.is_background = false;
-  model()->SavePage(save_page_params, std::move(archiver), callback);
+  model()->SavePage(save_page_params, std::move(archiver), nullptr, callback);
   PumpLoop();
 }
 
@@ -364,6 +364,18 @@
       model_utils::AddHistogramSuffix(kTestClientId1.name_space,
                                       "OfflinePages.SavePageTime"),
       1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.InternalFreeSpaceMiB", 1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.ExternalFreeSpaceMiB", 1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.InternalUsagePercentage", 1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.ExternalUsagePercentage", 1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.InternalArchiveSizeMiB", 1);
+  histogram_tester()->ExpectTotalCount(
+      "OfflinePages.StorageInfo.ExternalArchiveSizeMiB", 1);
 }
 
 TEST_F(OfflinePageModelTaskifiedTest, SavePageSuccessfulWithSameOriginalUrl) {
@@ -692,7 +704,8 @@
 
   base::MockCallback<SavePageCallback> callback;
   EXPECT_CALL(callback, Run(Eq(SavePageResult::SUCCESS), A<int64_t>()));
-  model()->SavePage(save_page_params, std::move(archiver), callback.Get());
+  model()->SavePage(save_page_params, std::move(archiver), nullptr,
+                    callback.Get());
   EXPECT_TRUE(archiver_ptr->create_archive_called());
   // |remove_popup_overlay| should be turned on on background mode.
   EXPECT_TRUE(archiver_ptr->create_archive_params().remove_popup_overlay);
diff --git a/components/offline_pages/core/offline_page_archiver.h b/components/offline_pages/core/offline_page_archiver.h
index b03e8b2..81fb7a5 100644
--- a/components/offline_pages/core/offline_page_archiver.h
+++ b/components/offline_pages/core/offline_page_archiver.h
@@ -20,6 +20,10 @@
 class SequencedTaskRunner;
 }  // namespace base
 
+namespace content {
+class WebContents;
+}  // namespace content
+
 namespace offline_pages {
 
 class SystemDownloadManager;
@@ -106,6 +110,7 @@
   // with the result and additional information.
   virtual void CreateArchive(const base::FilePath& archives_dir,
                              const CreateArchiveParams& create_archive_params,
+                             content::WebContents* web_contents,
                              const CreateArchiveCallback& callback) = 0;
 
   // Publishes the page on a background thread, then returns to the
diff --git a/components/offline_pages/core/offline_page_archiver_unittest.cc b/components/offline_pages/core/offline_page_archiver_unittest.cc
index 6ef23d6..b0ce0703 100644
--- a/components/offline_pages/core/offline_page_archiver_unittest.cc
+++ b/components/offline_pages/core/offline_page_archiver_unittest.cc
@@ -24,6 +24,7 @@
  public:
   void CreateArchive(const base::FilePath& archives_dir,
                      const CreateArchiveParams& create_archive_params,
+                     content::WebContents* web_contents,
                      const CreateArchiveCallback& callback) override {}
 };
 
diff --git a/components/offline_pages/core/offline_page_metadata_store_sql.cc b/components/offline_pages/core/offline_page_metadata_store_sql.cc
index da4c16a..bbf8979 100644
--- a/components/offline_pages/core/offline_page_metadata_store_sql.cc
+++ b/components/offline_pages/core/offline_page_metadata_store_sql.cc
@@ -23,19 +23,17 @@
 #include "sql/transaction.h"
 
 namespace offline_pages {
+
+const int OfflinePageMetadataStoreSQL::kFirstPostLegacyVersion;
+const int OfflinePageMetadataStoreSQL::kCurrentVersion;
+const int OfflinePageMetadataStoreSQL::kCompatibleVersion;
+
 namespace {
 
 // This is a macro instead of a const so that
 // it can be used inline in other SQL statements below.
 #define OFFLINE_PAGES_TABLE_NAME "offlinepages_v1"
 
-// This is the first version saved in the meta table, which was introduced in
-// the store in M65. It is set once a legacy upgrade is run successfully for the
-// last time in |UpgradeFromLegacyVersion|.
-static const int kFirstPostLegacyVersion = 1;
-static const int kCurrentVersion = 2;
-static const int kCompatibleVersion = kFirstPostLegacyVersion;
-
 void ReportStoreEvent(OfflinePagesStoreEvent event) {
   UMA_HISTOGRAM_ENUMERATION("OfflinePages.SQLStorage.StoreEvent", event,
                             OfflinePagesStoreEvent::STORE_EVENT_COUNT);
@@ -175,6 +173,16 @@
   return UpgradeWithQuery(db, kSql);
 }
 
+bool CreatePageThumbnailsTable(sql::Connection* db) {
+  const char kSql[] =
+      "CREATE TABLE IF NOT EXISTS page_thumbnails"
+      " (offline_id INTEGER PRIMARY KEY NOT NULL,"
+      " expiration INTEGER NOT NULL,"
+      " thumbnail BLOB NOT NULL"
+      ")";
+  return db->Execute(kSql);
+}
+
 bool CreateLatestSchema(sql::Connection* db) {
   sql::Transaction transaction(db);
   if (!transaction.Begin())
@@ -183,14 +191,19 @@
   // First time database initialization.
   if (!CreateOfflinePagesTable(db))
     return false;
+  if (!CreatePageThumbnailsTable(db))
+    return false;
 
   sql::MetaTable meta_table;
-  if (!meta_table.Init(db, kCurrentVersion, kCompatibleVersion))
+  if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kCurrentVersion,
+                       OfflinePageMetadataStoreSQL::kCompatibleVersion))
     return false;
 
   return transaction.Commit();
 }
 
+// Upgrades the database from before the database version was stored in the
+// MetaTable. This function should never need to be modified.
 bool UpgradeFromLegacyVersion(sql::Connection* db) {
   sql::Transaction transaction(db);
   if (!transaction.Begin())
@@ -222,7 +235,8 @@
   }
 
   sql::MetaTable meta_table;
-  if (!meta_table.Init(db, kFirstPostLegacyVersion, kCompatibleVersion))
+  if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kFirstPostLegacyVersion,
+                       OfflinePageMetadataStoreSQL::kCompatibleVersion))
     return false;
 
   return transaction.Commit();
@@ -249,6 +263,19 @@
   return transaction.Commit();
 }
 
+bool UpgradeFromVersion2ToVersion3(sql::Connection* db,
+                                   sql::MetaTable* meta_table) {
+  sql::Transaction transaction(db);
+  if (!transaction.Begin())
+    return false;
+
+  if (!CreatePageThumbnailsTable(db)) {
+    return false;
+  }
+  meta_table->SetVersionNumber(3);
+  return transaction.Commit();
+}
+
 bool CreateSchema(sql::Connection* db) {
   if (!sql::MetaTable::DoesTableExist(db)) {
     // If this looks like a completely empty DB, simply start from scratch.
@@ -261,16 +288,26 @@
   }
 
   sql::MetaTable meta_table;
-  if (!meta_table.Init(db, kCurrentVersion, kCompatibleVersion))
+  if (!meta_table.Init(db, OfflinePageMetadataStoreSQL::kCurrentVersion,
+                       OfflinePageMetadataStoreSQL::kCompatibleVersion))
     return false;
 
-  if (meta_table.GetVersionNumber() == 1) {
-    if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
-      return false;
+  for (;;) {
+    switch (meta_table.GetVersionNumber()) {
+      case 1:
+        if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
+          return false;
+        break;
+      case 2:
+        if (!UpgradeFromVersion2ToVersion3(db, &meta_table))
+          return false;
+        break;
+      case OfflinePageMetadataStoreSQL::kCurrentVersion:
+        return true;
+      default:
+        return false;
+    }
   }
-
-  // This would be a great place to add indices when we need them.
-  return true;
 }
 
 bool PrepareDirectory(const base::FilePath& path) {
diff --git a/components/offline_pages/core/offline_page_metadata_store_sql.h b/components/offline_pages/core/offline_page_metadata_store_sql.h
index de3d527..74d727af 100644
--- a/components/offline_pages/core/offline_page_metadata_store_sql.h
+++ b/components/offline_pages/core/offline_page_metadata_store_sql.h
@@ -90,6 +90,13 @@
   template <typename T>
   using ResultCallback = base::OnceCallback<void(T)>;
 
+  // This is the first version saved in the meta table, which was introduced in
+  // the store in M65. It is set once a legacy upgrade is run successfully for
+  // the last time in |UpgradeFromLegacyVersion|.
+  static const int kFirstPostLegacyVersion = 1;
+  static const int kCurrentVersion = 3;
+  static const int kCompatibleVersion = kFirstPostLegacyVersion;
+
   // Defines inactivity time of DB after which it is going to be closed.
   // TODO(fgorski): Derive appropriate value in a scientific way.
   static constexpr base::TimeDelta kClosingDelay =
diff --git a/components/offline_pages/core/offline_page_metadata_store_unittest.cc b/components/offline_pages/core/offline_page_metadata_store_unittest.cc
index cce98f1..db3e759c 100644
--- a/components/offline_pages/core/offline_page_metadata_store_unittest.cc
+++ b/components/offline_pages/core/offline_page_metadata_store_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_metadata_store_sql.h"
 #include "components/offline_pages/core/offline_page_model.h"
+#include "components/offline_pages/core/offline_page_thumbnail.h"
 #include "components/offline_pages/core/offline_store_utils.h"
 #include "sql/connection.h"
 #include "sql/meta_table.h"
@@ -47,6 +48,7 @@
 const char kTestRequestOrigin[] = "request.origin";
 int64_t kTestSystemDownloadId = 42LL;
 const char kTestDigest[] = "test-digest";
+const base::Time kThumbnailExpiration = store_utils::FromDatabaseTime(42);
 
 // Build a store with outdated schema to simulate the upgrading process.
 void BuildTestStoreWithSchemaFromM52(const base::FilePath& file) {
@@ -462,6 +464,22 @@
   InjectItemInM62Store(&connection, generator.CreateItem());
 }
 
+void BuildTestStoreWithSchemaVersion2(const base::FilePath& file) {
+  BuildTestStoreWithSchemaVersion1(file);
+  sql::Connection db;
+  ASSERT_TRUE(db.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
+  sql::MetaTable meta_table;
+  ASSERT_TRUE(meta_table.Init(&db, OfflinePageMetadataStoreSQL::kCurrentVersion,
+                              OfflinePageMetadataStoreSQL::kCompatibleVersion));
+  const char kSql[] =
+      "CREATE TABLE page_thumbnails"
+      " (offline_id INTEGER PRIMARY KEY NOT NULL,"
+      " expiration INTEGER NOT NULL,"
+      " thumbnail BLOB NOT NULL"
+      ")";
+  ASSERT_TRUE(db.Execute(kSql));
+}
+
 // Create an offline page item from a SQL result.  Expects complete rows with
 // all columns present.
 OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
@@ -578,6 +596,18 @@
     EXPECT_EQ(offline_page, pages[0]);
   }
 
+  void CheckThatPageThumbnailCanBeSaved(OfflinePageMetadataStoreSQL* store) {
+    OfflinePageThumbnail thumbnail;
+    thumbnail.offline_id = kOfflineId;
+    thumbnail.expiration = kThumbnailExpiration;
+    thumbnail.thumbnail = "content";
+
+    AddThumbnail(store, thumbnail);
+    std::vector<OfflinePageThumbnail> thumbnails = GetThumbnails(store);
+    EXPECT_EQ(1UL, thumbnails.size());
+    EXPECT_EQ(thumbnail, thumbnails[0]);
+  }
+
   void VerifyMetaVersions() {
     sql::Connection connection;
     ASSERT_TRUE(connection.Open(temp_directory_.GetPath().Append(
@@ -587,14 +617,42 @@
     sql::MetaTable meta_table;
     EXPECT_TRUE(meta_table.Init(&connection, 1, 1));
 
-    EXPECT_EQ(2, meta_table.GetVersionNumber());
-    EXPECT_EQ(1, meta_table.GetCompatibleVersionNumber());
+    EXPECT_EQ(OfflinePageMetadataStoreSQL::kCurrentVersion,
+              meta_table.GetVersionNumber());
+    EXPECT_EQ(OfflinePageMetadataStoreSQL::kCompatibleVersion,
+              meta_table.GetCompatibleVersionNumber());
   }
 
   void LoadAndCheckStore() {
     auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
         base::ThreadTaskRunnerHandle::Get(), TempPath());
     OfflinePageItem item = CheckThatStoreHasOneItem(store.get());
+    CheckThatPageThumbnailCanBeSaved((OfflinePageMetadataStoreSQL*)store.get());
+    CheckThatOfflinePageCanBeSaved(std::move(store));
+    VerifyMetaVersions();
+  }
+
+  void LoadAndCheckStoreFromMetaVersion1AndUp() {
+    // At meta version 1, more items were added to the database for testing,
+    // which necessitates different checks.
+    auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
+        base::ThreadTaskRunnerHandle::Get(), TempPath());
+    std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
+    EXPECT_EQ(5U, pages.size());
+
+    // TODO(fgorski): Use persistent namespaces from the client policy
+    // controller once an appropriate method is available.
+    std::set<std::string> upgradeable_namespaces{
+        kAsyncNamespace, kDownloadNamespace, kBrowserActionsNamespace,
+        kNTPSuggestionsNamespace};
+
+    for (const OfflinePageItem& page : pages) {
+      if (upgradeable_namespaces.count(page.client_id.name_space) > 0)
+        EXPECT_EQ(5, page.upgrade_attempt);
+      else
+        EXPECT_EQ(0, page.upgrade_attempt);
+    }
+    CheckThatPageThumbnailCanBeSaved((OfflinePageMetadataStoreSQL*)store.get());
     CheckThatOfflinePageCanBeSaved(std::move(store));
     VerifyMetaVersions();
   }
@@ -675,6 +733,47 @@
     return ExecuteSync<ItemActionStatus>(store, result_callback);
   }
 
+  std::vector<OfflinePageThumbnail> GetThumbnails(
+      OfflinePageMetadataStoreSQL* store) {
+    std::vector<OfflinePageThumbnail> thumbnails;
+    auto run_callback = base::BindLambdaForTesting([&](sql::Connection* db) {
+      const char kSql[] = "SELECT * FROM page_thumbnails";
+      sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+
+      while (statement.Step()) {
+        OfflinePageThumbnail thumb;
+        thumb.offline_id = statement.ColumnInt64(0);
+        thumb.expiration =
+            store_utils::FromDatabaseTime(statement.ColumnInt64(1));
+        statement.ColumnBlobAsString(2, &thumb.thumbnail);
+        thumbnails.push_back(std::move(thumb));
+      }
+
+      EXPECT_TRUE(statement.Succeeded());
+      return thumbnails;
+    });
+    return ExecuteSync<std::vector<OfflinePageThumbnail>>(store, run_callback);
+  }
+
+  void AddThumbnail(OfflinePageMetadataStoreSQL* store,
+                    const OfflinePageThumbnail& thumbnail) {
+    std::vector<OfflinePageThumbnail> thumbnails;
+    auto run_callback = base::BindLambdaForTesting([&](sql::Connection* db) {
+      const char kSql[] =
+          "INSERT INTO page_thumbnails"
+          " (offline_id, expiration, thumbnail) VALUES (?, ?, ?)";
+      sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
+
+      statement.BindInt64(0, thumbnail.offline_id);
+      statement.BindInt64(1, store_utils::ToDatabaseTime(thumbnail.expiration));
+      statement.BindString(2, thumbnail.thumbnail);
+      EXPECT_TRUE(statement.Run());
+      return thumbnails;
+    });
+    ExecuteSync<std::vector<OfflinePageThumbnail>>(store, run_callback);
+  }
+
+ protected:
   base::ScopedTempDir temp_directory_;
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
   base::ThreadTaskRunnerHandle task_runner_handle_;
@@ -755,26 +854,12 @@
 
 TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion1) {
   BuildTestStoreWithSchemaVersion1(TempPath());
-  auto store = std::make_unique<OfflinePageMetadataStoreSQL>(
-      base::ThreadTaskRunnerHandle::Get(), TempPath());
+  LoadAndCheckStoreFromMetaVersion1AndUp();
+}
 
-  std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
-  EXPECT_EQ(5U, pages.size());
-
-  // TODO(fgorski): Use persistent namespaces from the client policy controller
-  // once an appropriate method is available.
-  std::set<std::string> upgradeable_namespaces{
-      kAsyncNamespace, kDownloadNamespace, kBrowserActionsNamespace,
-      kNTPSuggestionsNamespace};
-
-  for (const OfflinePageItem& page : pages) {
-    if (upgradeable_namespaces.count(page.client_id.name_space) > 0)
-      EXPECT_EQ(5, page.upgrade_attempt);
-    else
-      EXPECT_EQ(0, page.upgrade_attempt);
-  }
-  CheckThatOfflinePageCanBeSaved(std::move(store));
-  VerifyMetaVersions();
+TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion2) {
+  BuildTestStoreWithSchemaVersion2(TempPath());
+  LoadAndCheckStoreFromMetaVersion1AndUp();
 }
 
 // Adds metadata of an offline page into a store and then opens the store
diff --git a/components/offline_pages/core/offline_page_model.h b/components/offline_pages/core/offline_page_model.h
index 37a73d0c..5d1ca0e 100644
--- a/components/offline_pages/core/offline_page_model.h
+++ b/components/offline_pages/core/offline_page_model.h
@@ -131,6 +131,7 @@
   // id in |save_page_params| and returns it.
   virtual void SavePage(const SavePageParams& save_page_params,
                         std::unique_ptr<OfflinePageArchiver> archiver,
+                        content::WebContents* web_contents,
                         const SavePageCallback& callback) = 0;
 
   // Adds a page entry to the metadata store.
@@ -211,6 +212,14 @@
       const ClientId& client_id,
       const MultipleOfflineIdCallback& callback) = 0;
 
+  // Publishes an offline page from the internal offline page directory.  This
+  // includes putting it in a public directory, updating the system download
+  // manager, if any, and updating the offline page model database.
+  virtual void PublishInternalArchive(
+      const OfflinePageItem& offline_page,
+      std::unique_ptr<OfflinePageArchiver> archiver,
+      PublishPageCallback publish_done_callback) = 0;
+
   // Returns the policy controller.
   virtual ClientPolicyController* GetPolicyController() = 0;
 
diff --git a/components/offline_pages/core/offline_page_test_archiver.cc b/components/offline_pages/core/offline_page_test_archiver.cc
index 1b77f7a..14bad88f 100644
--- a/components/offline_pages/core/offline_page_test_archiver.cc
+++ b/components/offline_pages/core/offline_page_test_archiver.cc
@@ -37,6 +37,7 @@
 void OfflinePageTestArchiver::CreateArchive(
     const base::FilePath& archives_dir,
     const CreateArchiveParams& create_archive_params,
+    content::WebContents* web_contents,
     const CreateArchiveCallback& callback) {
   create_archive_called_ = true;
   callback_ = callback;
diff --git a/components/offline_pages/core/offline_page_test_archiver.h b/components/offline_pages/core/offline_page_test_archiver.h
index bf3ed725..609fff76 100644
--- a/components/offline_pages/core/offline_page_test_archiver.h
+++ b/components/offline_pages/core/offline_page_test_archiver.h
@@ -46,6 +46,7 @@
   // OfflinePageArchiver implementation:
   void CreateArchive(const base::FilePath& archives_dir,
                      const CreateArchiveParams& create_archive_params,
+                     content::WebContents* web_contents,
                      const CreateArchiveCallback& callback) override;
 
   void PublishArchive(
diff --git a/components/offline_pages/core/offline_page_thumbnail.cc b/components/offline_pages/core/offline_page_thumbnail.cc
new file mode 100644
index 0000000..5b0fe99d
--- /dev/null
+++ b/components/offline_pages/core/offline_page_thumbnail.cc
@@ -0,0 +1,51 @@
+// 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/offline_pages/core/offline_page_thumbnail.h"
+
+#include <iostream>
+#include "base/base64.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/offline_pages/core/offline_store_utils.h"
+
+namespace offline_pages {
+
+OfflinePageThumbnail::OfflinePageThumbnail() = default;
+OfflinePageThumbnail::OfflinePageThumbnail(int64_t id,
+                                           base::Time in_expiration,
+                                           const std::string& in_thumbnail)
+    : offline_id(id), expiration(in_expiration), thumbnail(in_thumbnail) {}
+OfflinePageThumbnail::OfflinePageThumbnail(const OfflinePageThumbnail& other) =
+    default;
+OfflinePageThumbnail::OfflinePageThumbnail(OfflinePageThumbnail&& other) =
+    default;
+OfflinePageThumbnail::~OfflinePageThumbnail() {}
+
+bool OfflinePageThumbnail::operator==(const OfflinePageThumbnail& other) const {
+  return offline_id == other.offline_id && expiration == other.expiration &&
+         thumbnail == other.thumbnail;
+}
+
+bool OfflinePageThumbnail::operator<(const OfflinePageThumbnail& other) const {
+  return offline_id < other.offline_id;
+}
+
+std::string OfflinePageThumbnail::ToString() const {
+  std::string thumb_data_base64;
+  base::Base64Encode(thumbnail, &thumb_data_base64);
+
+  std::string s("OfflinePageThumbnail(");
+  s.append(base::Int64ToString(offline_id)).append(", ");
+  s.append(base::Int64ToString(store_utils::ToDatabaseTime(expiration)))
+      .append(", ");
+  s.append(thumb_data_base64).append(")");
+  return s;
+}
+
+std::ostream& operator<<(std::ostream& out, const OfflinePageThumbnail& thumb) {
+  return out << thumb.ToString();
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/offline_page_thumbnail.h b/components/offline_pages/core/offline_page_thumbnail.h
new file mode 100644
index 0000000..be1db909
--- /dev/null
+++ b/components/offline_pages/core/offline_page_thumbnail.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 COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
+#define COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
+
+#include <stdint.h>
+#include <iosfwd>
+#include <string>
+
+#include "base/time/time.h"
+
+namespace offline_pages {
+
+// Thumbnail for an offline page. This maps to a row in the page_thumbnails
+// table.
+struct OfflinePageThumbnail {
+ public:
+  OfflinePageThumbnail();
+  OfflinePageThumbnail(int64_t offline_id,
+                       base::Time expiration,
+                       const std::string& thumbnail);
+  OfflinePageThumbnail(const OfflinePageThumbnail& other);
+  OfflinePageThumbnail(OfflinePageThumbnail&& other);
+  ~OfflinePageThumbnail();
+  OfflinePageThumbnail& operator=(const OfflinePageThumbnail& other) = default;
+  bool operator==(const OfflinePageThumbnail& other) const;
+  bool operator<(const OfflinePageThumbnail& other) const;
+  std::string ToString() const;
+
+  // The primary key/ID for the page in offline pages internal database.
+  int64_t offline_id = 0;
+  // The time at which the thumbnail can be removed from the table, but only
+  // if the offline_id does not match an offline_id in the offline pages table.
+  base::Time expiration;
+  // The thumbnail raw image data.
+  std::string thumbnail;
+};
+
+std::ostream& operator<<(std::ostream& out, const OfflinePageThumbnail& thumb);
+}  // namespace offline_pages
+
+#endif  // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_THUMBNAIL_H_
diff --git a/components/offline_pages/core/offline_page_thumbnail_unittest.cc b/components/offline_pages/core/offline_page_thumbnail_unittest.cc
new file mode 100644
index 0000000..8349eb2
--- /dev/null
+++ b/components/offline_pages/core/offline_page_thumbnail_unittest.cc
@@ -0,0 +1,49 @@
+// 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/offline_pages/core/offline_page_thumbnail.h"
+
+#include "components/offline_pages/core/offline_store_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace offline_pages {
+namespace {
+
+const base::Time kTestTime = store_utils::FromDatabaseTime(42);
+const char kThumbnailData[] = "abc123";
+
+TEST(OfflinePageThumbnailTest, Construct) {
+  OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
+  EXPECT_EQ(1, thumbnail.offline_id);
+  EXPECT_EQ(kTestTime, thumbnail.expiration);
+  EXPECT_EQ(kThumbnailData, thumbnail.thumbnail);
+}
+
+TEST(OfflinePageThumbnailTest, Equal) {
+  OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
+  auto copy = thumbnail;
+
+  EXPECT_EQ(1, copy.offline_id);
+  EXPECT_EQ(kTestTime, copy.expiration);
+  EXPECT_EQ(kThumbnailData, copy.thumbnail);
+  EXPECT_EQ(copy, thumbnail);
+}
+
+TEST(OfflinePageThumbnailTest, Compare) {
+  OfflinePageThumbnail thumbnail_a(1, kTestTime, kThumbnailData);
+  OfflinePageThumbnail thumbnail_b(2, kTestTime, kThumbnailData);
+
+  EXPECT_TRUE(thumbnail_a < thumbnail_b);
+  EXPECT_FALSE(thumbnail_b < thumbnail_a);
+  EXPECT_FALSE(thumbnail_a < thumbnail_a);
+}
+
+TEST(OfflinePageThumbnailTest, ToString) {
+  OfflinePageThumbnail thumbnail(1, kTestTime, kThumbnailData);
+
+  EXPECT_EQ("OfflinePageThumbnail(1, 42, YWJjMTIz)", thumbnail.ToString());
+}
+
+}  // namespace
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/offline_page_types.h b/components/offline_pages/core/offline_page_types.h
index da1235e..71a509d 100644
--- a/components/offline_pages/core/offline_page_types.h
+++ b/components/offline_pages/core/offline_page_types.h
@@ -103,6 +103,9 @@
     MultipleOfflinePageItemCallback;
 typedef base::Callback<bool(const GURL&)> UrlPredicate;
 typedef base::Callback<void(int64_t)> SizeInBytesCallback;
+
+// Callback used for publishing an offline page.
+using PublishPageCallback = base::OnceCallback<void(const OfflinePageItem&)>;
 }  // namespace offline_pages
 
 #endif  // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_TYPES_H_
diff --git a/components/offline_pages/core/stub_offline_page_model.cc b/components/offline_pages/core/stub_offline_page_model.cc
index 8979d82..9dccefd0 100644
--- a/components/offline_pages/core/stub_offline_page_model.cc
+++ b/components/offline_pages/core/stub_offline_page_model.cc
@@ -17,6 +17,7 @@
 void StubOfflinePageModel::SavePage(
     const SavePageParams& save_page_params,
     std::unique_ptr<OfflinePageArchiver> archiver,
+    content::WebContents* web_contents,
     const SavePageCallback& callback) {}
 void StubOfflinePageModel::AddPage(const OfflinePageItem& page,
                                    const AddPageCallback& callback) {}
@@ -63,6 +64,10 @@
     const MultipleOfflinePageItemCallback& callback) {}
 void StubOfflinePageModel::GetPagesSupportedByDownloads(
     const MultipleOfflinePageItemCallback& callback) {}
+void StubOfflinePageModel::PublishInternalArchive(
+    const OfflinePageItem& offline_page,
+    std::unique_ptr<OfflinePageArchiver> archiver,
+    PublishPageCallback publish_done_callback){};
 const base::FilePath& StubOfflinePageModel::GetInternalArchiveDirectory(
     const std::string& name_space) const {
   return archive_directory_;
diff --git a/components/offline_pages/core/stub_offline_page_model.h b/components/offline_pages/core/stub_offline_page_model.h
index 17cd83c..1d36257 100644
--- a/components/offline_pages/core/stub_offline_page_model.h
+++ b/components/offline_pages/core/stub_offline_page_model.h
@@ -26,6 +26,7 @@
   void RemoveObserver(Observer* observer) override;
   void SavePage(const SavePageParams& save_page_params,
                 std::unique_ptr<OfflinePageArchiver> archiver,
+                content::WebContents* web_contents,
                 const SavePageCallback& callback) override;
   void AddPage(const OfflinePageItem& page,
                const AddPageCallback& callback) override;
@@ -69,6 +70,10 @@
       const MultipleOfflinePageItemCallback& callback) override;
   void GetPagesSupportedByDownloads(
       const MultipleOfflinePageItemCallback& callback) override;
+  void PublishInternalArchive(
+      const OfflinePageItem& offline_page,
+      std::unique_ptr<OfflinePageArchiver> archiver,
+      PublishPageCallback publish_done_callback) override;
   const base::FilePath& GetInternalArchiveDirectory(
       const std::string& name_space) const override;
   bool IsArchiveInInternalDir(const base::FilePath& file_path) const override;
diff --git a/components/printing/service/public/cpp/pdf_service_mojo_utils.cc b/components/printing/service/public/cpp/pdf_service_mojo_utils.cc
index b2224c80..01ec77f7 100644
--- a/components/printing/service/public/cpp/pdf_service_mojo_utils.cc
+++ b/components/printing/service/public/cpp/pdf_service_mojo_utils.cc
@@ -42,9 +42,9 @@
   if (!shm)
     return nullptr;
 
-  return base::MakeRefCounted<base::RefCountedBytes>(
-      reinterpret_cast<const unsigned char*>(shm->memory()),
-      shm->mapped_size());
+  size_t size = shm->mapped_size();
+  return base::MakeRefCounted<base::RefCountedSharedMemory>(std::move(shm),
+                                                            size);
 }
 
 }  // namespace printing
diff --git a/components/viz/common/resources/bitmap_allocation.cc b/components/viz/common/resources/bitmap_allocation.cc
index 4babe5f..d5ac586 100644
--- a/components/viz/common/resources/bitmap_allocation.cc
+++ b/components/viz/common/resources/bitmap_allocation.cc
@@ -91,6 +91,23 @@
       mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
 }
 
+mojo::ScopedSharedBufferHandle DuplicateWithoutClosingMappedBitmap(
+    const base::SharedMemory* memory,
+    const gfx::Size& size,
+    ResourceFormat format) {
+  DCHECK(IsBitmapFormatSupported(format));
+  base::SharedMemoryHandle dupe_handle =
+      base::SharedMemory::DuplicateHandle(memory->handle());
+  if (!base::SharedMemory::IsHandleValid(dupe_handle)) {
+    DLOG(ERROR) << "Failed to duplicate shared memory handle for bitmap.";
+    CollectMemoryUsageAndDie(size, format, memory->requested_size());
+  }
+
+  return mojo::WrapSharedMemoryHandle(
+      dupe_handle, memory->mapped_size(),
+      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
+}
+
 }  // namespace bitmap_allocation
 
 }  // namespace viz
diff --git a/components/viz/common/resources/bitmap_allocation.h b/components/viz/common/resources/bitmap_allocation.h
index 906a6a94..aa8c176 100644
--- a/components/viz/common/resources/bitmap_allocation.h
+++ b/components/viz/common/resources/bitmap_allocation.h
@@ -43,6 +43,17 @@
     const gfx::Size& size,
     ResourceFormat format);
 
+// Similar to DuplicateAndCloseMappedBitmap(), but to be used in cases where the
+// SharedMemory will have to be duplicated more than once. In that case the
+// handle must be kept valid, so it can not be closed. Beware this will keep an
+// open file handle on posix systems, which may contribute to surpassing handle
+// limits.
+VIZ_COMMON_EXPORT
+mojo::ScopedSharedBufferHandle DuplicateWithoutClosingMappedBitmap(
+    const base::SharedMemory* memory,
+    const gfx::Size& size,
+    ResourceFormat format);
+
 }  // namespace bitmap_allocation
 
 }  // namespace viz
diff --git a/components/viz/test/test_layer_tree_frame_sink.cc b/components/viz/test/test_layer_tree_frame_sink.cc
index c3d769f..5035854e 100644
--- a/components/viz/test/test_layer_tree_frame_sink.cc
+++ b/components/viz/test/test_layer_tree_frame_sink.cc
@@ -55,12 +55,6 @@
 
 TestLayerTreeFrameSink::~TestLayerTreeFrameSink() {
   DCHECK(copy_requests_.empty());
-
-  // The shared_bitmap_manager() has ownership of shared memory for each
-  // SharedBitmapId that has been reported from the client. Since the client is
-  // gone that memory can be freed. If we don't then it would leak.
-  for (const auto& id : owned_bitmaps_)
-    shared_bitmap_manager()->ChildDeletedSharedBitmap(id);
 }
 
 void TestLayerTreeFrameSink::SetDisplayColorSpace(
@@ -134,6 +128,13 @@
 }
 
 void TestLayerTreeFrameSink::DetachFromClient() {
+  // The shared_bitmap_manager() has ownership of shared memory for each
+  // SharedBitmapId that has been reported from the client. Since the client is
+  // gone that memory can be freed. If we don't then it would leak.
+  for (const auto& id : owned_bitmaps_)
+    shared_bitmap_manager()->ChildDeletedSharedBitmap(id);
+  owned_bitmaps_.clear();
+
   if (display_begin_frame_source_) {
     frame_sink_manager_->UnregisterBeginFrameSource(
         display_begin_frame_source_);
diff --git a/components/viz/test/test_layer_tree_frame_sink.h b/components/viz/test/test_layer_tree_frame_sink.h
index 46745ce2..24dec75 100644
--- a/components/viz/test/test_layer_tree_frame_sink.h
+++ b/components/viz/test/test_layer_tree_frame_sink.h
@@ -117,6 +117,10 @@
   void DisplayDidReceiveCALayerParams(
       const gfx::CALayerParams& ca_layer_params) override;
 
+  const std::set<SharedBitmapId>& owned_bitmaps() const {
+    return owned_bitmaps_;
+  }
+
  private:
   // ExternalBeginFrameSource implementation.
   void OnNeedsBeginFrames(bool needs_begin_frames) override;
diff --git a/components/viz/test/test_shared_bitmap_manager.cc b/components/viz/test/test_shared_bitmap_manager.cc
index 32f86db..fd49583 100644
--- a/components/viz/test/test_shared_bitmap_manager.cc
+++ b/components/viz/test/test_shared_bitmap_manager.cc
@@ -115,9 +115,6 @@
 
 void TestSharedBitmapManager::ChildDeletedSharedBitmap(
     const SharedBitmapId& id) {
-  // The bitmap id should previously have been given to
-  // ChildAllocatedSharedBitmap().
-  DCHECK_EQ(notified_set_.count(id), 1u);
   notified_set_.erase(id);
   bitmap_map_.erase(id);
   owned_map_.erase(id);
diff --git a/content/browser/compositor/reflector_impl_unittest.cc b/content/browser/compositor/reflector_impl_unittest.cc
index 303535ab..d26b1f58 100644
--- a/content/browser/compositor/reflector_impl_unittest.cc
+++ b/content/browser/compositor/reflector_impl_unittest.cc
@@ -181,7 +181,8 @@
       reflector_->RemoveMirroringLayer(mirroring_layer_.get());
     viz::TransferableResource resource;
     std::unique_ptr<viz::SingleReleaseCallback> release;
-    if (mirroring_layer_->PrepareTransferableResource(&resource, &release)) {
+    if (mirroring_layer_->PrepareTransferableResource(nullptr, &resource,
+                                                      &release)) {
       release->Run(gpu::SyncToken(), false);
     }
     compositor_.reset();
diff --git a/content/browser/frame_host/navigation_request_info.cc b/content/browser/frame_host/navigation_request_info.cc
index 6982bf8..5909229f 100644
--- a/content/browser/frame_host/navigation_request_info.cc
+++ b/content/browser/frame_host/navigation_request_info.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/frame_host/navigation_request_info.h"
 #include "content/common/service_worker/service_worker_types.h"
+#include "mojo/common/values_struct_traits.h"
 
 namespace content {
 
@@ -29,6 +30,18 @@
       report_raw_headers(report_raw_headers),
       is_prerendering(is_prerendering) {}
 
+NavigationRequestInfo::NavigationRequestInfo(const NavigationRequestInfo& other)
+    : common_params(other.common_params),
+      begin_params(other.begin_params.Clone()),
+      site_for_cookies(other.site_for_cookies),
+      is_main_frame(other.is_main_frame),
+      parent_is_main_frame(other.parent_is_main_frame),
+      are_ancestors_secure(other.are_ancestors_secure),
+      frame_tree_node_id(other.frame_tree_node_id),
+      is_for_guests_only(other.is_for_guests_only),
+      report_raw_headers(other.report_raw_headers),
+      is_prerendering(other.is_prerendering) {}
+
 NavigationRequestInfo::~NavigationRequestInfo() {}
 
 }  // namespace content
diff --git a/content/browser/frame_host/navigation_request_info.h b/content/browser/frame_host/navigation_request_info.h
index 65256b5..dc2aba1 100644
--- a/content/browser/frame_host/navigation_request_info.h
+++ b/content/browser/frame_host/navigation_request_info.h
@@ -31,6 +31,7 @@
                         bool is_for_guests_only,
                         bool report_raw_headers,
                         bool is_prerendering);
+  NavigationRequestInfo(const NavigationRequestInfo& other);
   ~NavigationRequestInfo();
 
   const CommonNavigationParams common_params;
diff --git a/content/browser/loader/navigation_url_loader.cc b/content/browser/loader/navigation_url_loader.cc
index 4b1e826..8dd34c2 100644
--- a/content/browser/loader/navigation_url_loader.cc
+++ b/content/browser/loader/navigation_url_loader.cc
@@ -34,8 +34,7 @@
         resource_context, storage_partition, std::move(request_info),
         std::move(navigation_ui_data), service_worker_handle, delegate);
   }
-  if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
-      IsNavigationMojoResponseEnabled()) {
+  if (IsNavigationMojoResponseEnabled()) {
     return std::make_unique<NavigationURLLoaderNetworkService>(
         resource_context, storage_partition, std::move(request_info),
         std::move(navigation_ui_data), service_worker_handle, appcache_handle,
diff --git a/content/browser/loader/navigation_url_loader_network_service.cc b/content/browser/loader/navigation_url_loader_network_service.cc
index dd18c83..4aee09a3 100644
--- a/content/browser/loader/navigation_url_loader_network_service.cc
+++ b/content/browser/loader/navigation_url_loader_network_service.cc
@@ -38,6 +38,7 @@
 #include "content/browser/webui/url_data_manager_backend.h"
 #include "content/browser/webui/web_ui_url_loader_factory_internal.h"
 #include "content/common/navigation_subresource_loader_params.h"
+#include "content/common/service_worker/service_worker_utils.h"
 #include "content/common/throttling_url_loader.h"
 #include "content/common/weak_wrapper_shared_url_loader_factory.h"
 #include "content/common/wrapper_shared_url_loader_factory.h"
@@ -50,10 +51,12 @@
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
 #include "content/public/browser/ssl_status.h"
 #include "content/public/browser/stream_handle.h"
+#include "content/public/common/browser_side_navigation_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/referrer.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/common/url_utils.h"
+#include "mojo/common/values_struct_traits.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_content_disposition.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -75,7 +78,8 @@
 // Returns true if interception by NavigationLoaderInterceptors is enabled.
 bool IsLoaderInterceptionEnabled() {
   return base::FeatureList::IsEnabled(network::features::kNetworkService) ||
-         base::FeatureList::IsEnabled(features::kSignedHTTPExchange);
+         base::FeatureList::IsEnabled(features::kSignedHTTPExchange) ||
+         ServiceWorkerUtils::IsServicificationEnabled();
 }
 
 // Request ID for browser initiated requests. We start at -2 on the same lines
@@ -264,6 +268,41 @@
   return new_request;
 }
 
+// Used only when NetworkService is disabled but IsLoaderInterceptionEnabled()
+// is true.
+std::unique_ptr<NavigationRequestInfo> CreateNavigationRequestInfoForRedirect(
+    const NavigationRequestInfo& previous_request_info,
+    const network::ResourceRequest& updated_resource_request) {
+  DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
+  DCHECK(IsLoaderInterceptionEnabled());
+
+  CommonNavigationParams new_common_params =
+      previous_request_info.common_params;
+  new_common_params.url = updated_resource_request.url;
+  new_common_params.referrer =
+      Referrer(updated_resource_request.url,
+               Referrer::NetReferrerPolicyToBlinkReferrerPolicy(
+                   updated_resource_request.referrer_policy));
+  new_common_params.method = updated_resource_request.method;
+  new_common_params.post_data = updated_resource_request.request_body;
+  // TODO(shimazu): Set correct base url and history url for a data URL.
+
+  mojom::BeginNavigationParamsPtr new_begin_params =
+      previous_request_info.begin_params.Clone();
+  new_begin_params->headers = updated_resource_request.headers.ToString();
+
+  return std::make_unique<NavigationRequestInfo>(
+      std::move(new_common_params), std::move(new_begin_params),
+      updated_resource_request.site_for_cookies,
+      previous_request_info.is_main_frame,
+      previous_request_info.parent_is_main_frame,
+      previous_request_info.are_ancestors_secure,
+      previous_request_info.frame_tree_node_id,
+      previous_request_info.is_for_guests_only,
+      previous_request_info.report_raw_headers,
+      previous_request_info.is_prerendering);
+}
+
 // Called for requests that we don't have a URLLoaderFactory for.
 void UnknownSchemeCallback(bool handled_externally,
                            network::mojom::URLLoaderRequest request,
@@ -332,11 +371,26 @@
     return options;
   }
 
+  SingleRequestURLLoaderFactory::RequestHandler
+  CreateDefaultRequestHandlerForNonNetworkService(
+      net::URLRequestContextGetter* url_request_context_getter,
+      storage::FileSystemContext* upload_file_system_context,
+      ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
+      AppCacheNavigationHandleCore* appcache_handle_core) const {
+    return base::BindOnce(
+        &URLLoaderRequestController::CreateNonNetworkServiceURLLoader,
+        weak_factory_.GetWeakPtr(),
+        base::Unretained(url_request_context_getter),
+        base::Unretained(upload_file_system_context),
+        std::make_unique<NavigationRequestInfo>(*request_info_),
+        base::Unretained(service_worker_navigation_handle_core),
+        base::Unretained(appcache_handle_core));
+  }
+
   void CreateNonNetworkServiceURLLoader(
       net::URLRequestContextGetter* url_request_context_getter,
       storage::FileSystemContext* upload_file_system_context,
       std::unique_ptr<NavigationRequestInfo> request_info,
-      std::unique_ptr<NavigationUIData> navigation_ui_data,
       ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
       AppCacheNavigationHandleCore* appcache_handle_core,
       network::mojom::URLLoaderRequest url_loader,
@@ -368,7 +422,7 @@
       ResourceDispatcherHostImpl::Get()->BeginNavigationRequest(
           resource_context_, url_request_context_getter->GetURLRequestContext(),
           upload_file_system_context, *request_info,
-          std::move(navigation_ui_data), nullptr, std::move(url_loader_client),
+          std::move(navigation_ui_data_), nullptr, std::move(url_loader_client),
           std::move(url_loader), service_worker_navigation_handle_core,
           appcache_handle_core,
           GetURLLoaderOptions(request_info->is_main_frame),
@@ -391,36 +445,68 @@
       AppCacheNavigationHandleCore* appcache_handle_core,
       std::unique_ptr<NavigationRequestInfo> request_info,
       std::unique_ptr<NavigationUIData> navigation_ui_data) {
+    DCHECK(IsNavigationMojoResponseEnabled());
     DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
     DCHECK_CURRENTLY_ON(BrowserThread::IO);
     DCHECK(!started_);
     started_ = true;
-
-    frame_tree_node_id_ = request_info->frame_tree_node_id;
+    request_info_ = std::move(request_info);
+    frame_tree_node_id_ = request_info_->frame_tree_node_id;
     web_contents_getter_ = base::BindRepeating(
         &GetWebContentsFromFrameTreeNodeID, frame_tree_node_id_);
-
-    std::vector<std::unique_ptr<content::URLLoaderThrottle>> throttles =
-        GetContentClient()->browser()->CreateURLLoaderThrottles(
-            *resource_request_, resource_context_, web_contents_getter_,
-            navigation_ui_data.get(), frame_tree_node_id_);
-
-    auto load_single_request = base::BindOnce(
-        &URLLoaderRequestController::CreateNonNetworkServiceURLLoader,
-        weak_factory_.GetWeakPtr(),
-        base::Unretained(url_request_context_getter),
-        base::Unretained(upload_file_system_context), std::move(request_info),
-        std::move(navigation_ui_data),
+    navigation_ui_data_ = std::move(navigation_ui_data);
+    default_request_handler_factory_ = base::BindRepeating(
+        &URLLoaderRequestController::
+            CreateDefaultRequestHandlerForNonNetworkService,
+        // base::Unretained(this) is safe since
+        // |default_request_handler_factory_| could be called only from |this|.
+        base::Unretained(this), base::Unretained(url_request_context_getter),
+        base::Unretained(upload_file_system_context),
         base::Unretained(service_worker_navigation_handle_core),
         base::Unretained(appcache_handle_core));
 
-    url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
-        base::MakeRefCounted<SingleRequestURLLoaderFactory>(
-            std::move(load_single_request)),
-        std::move(throttles), -1 /* routing_id */, 0 /* request_id */,
-        network::mojom::kURLLoadOptionNone, resource_request_.get(),
-        this /* client */, kNavigationUrlLoaderTrafficAnnotation,
-        base::ThreadTaskRunnerHandle::Get());
+    // If S13nServiceWorker is disabled, just use
+    // |default_request_handler_factory_| and return. The non network service
+    // request handling goes through ResourceDispatcherHost which has legacy
+    // hooks for service worker (ServiceWorkerRequestInterceptor), so no service
+    // worker interception is needed here.
+    if (!ServiceWorkerUtils::IsServicificationEnabled() ||
+        !service_worker_navigation_handle_core) {
+      url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
+          base::MakeRefCounted<SingleRequestURLLoaderFactory>(
+              default_request_handler_factory_.Run()),
+          CreateURLLoaderThrottles(), -1 /* routing_id */, 0 /* request_id */,
+          network::mojom::kURLLoadOptionNone, resource_request_.get(),
+          this /* client */, kNavigationUrlLoaderTrafficAnnotation,
+          base::ThreadTaskRunnerHandle::Get());
+      return;
+    }
+
+    // Otherwise, if S13nServiceWorker is enabled, create an interceptor so
+    // S13nServiceWorker has a chance to intercept the request.
+    std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor =
+        CreateServiceWorkerInterceptor(*request_info_,
+                                       service_worker_navigation_handle_core);
+    // If an interceptor is not created for some reasons (e.g. the origin is not
+    // secure), we no longer have to go through the rest of the network service
+    // code.
+    if (!service_worker_interceptor) {
+      url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
+          base::MakeRefCounted<SingleRequestURLLoaderFactory>(
+              default_request_handler_factory_.Run()),
+          CreateURLLoaderThrottles(), -1 /* routing_id */, 0 /* request_id */,
+          network::mojom::kURLLoadOptionNone, resource_request_.get(),
+          this /* client */, kNavigationUrlLoaderTrafficAnnotation,
+          base::ThreadTaskRunnerHandle::Get());
+      return;
+    }
+
+    interceptors_.push_back(std::move(service_worker_interceptor));
+
+    // TODO(shimazu): Make sure we have a consistent global id for the
+    // navigation request.
+    global_request_id_ = MakeGlobalRequestID();
+    Restart();
   }
 
   void Start(
@@ -441,9 +527,6 @@
     web_contents_getter_ =
         base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id);
     navigation_ui_data_ = std::move(navigation_ui_data);
-    const ResourceType resource_type = request_info->is_main_frame
-                                           ? RESOURCE_TYPE_MAIN_FRAME
-                                           : RESOURCE_TYPE_SUB_FRAME;
 
     if (resource_request_->request_body) {
       GetBodyBlobDataHandles(resource_request_->request_body.get(),
@@ -464,21 +547,9 @@
     }
 
     if (service_worker_navigation_handle_core) {
-      network::mojom::RequestContextFrameType frame_type =
-          request_info->is_main_frame
-              ? network::mojom::RequestContextFrameType::kTopLevel
-              : network::mojom::RequestContextFrameType::kNested;
-
-      storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext(
-          GetChromeBlobStorageContextForResourceContext(resource_context_));
       std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor =
-          ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
-              *resource_request_, resource_context_,
-              service_worker_navigation_handle_core, blob_storage_context,
-              request_info->begin_params->skip_service_worker, resource_type,
-              request_info->begin_params->request_context_type, frame_type,
-              request_info->are_ancestors_secure,
-              request_info->common_params.post_data, web_contents_getter_);
+          CreateServiceWorkerInterceptor(*request_info,
+                                         service_worker_navigation_handle_core);
       if (service_worker_interceptor)
         interceptors_.push_back(std::move(service_worker_interceptor));
     }
@@ -595,6 +666,29 @@
     // further refactor the factory getters to avoid this.
     scoped_refptr<network::SharedURLLoaderFactory> factory;
     DCHECK_EQ(interceptors_.size(), interceptor_index_);
+
+    // If NetworkService is not enabled (which means we come here because one of
+    // the loader interceptors is enabled), use the default request handler
+    // instead of going through the NetworkService path.
+    if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+      DCHECK(!interceptors_.empty());
+      DCHECK(default_request_handler_factory_);
+      default_loader_used_ = true;
+      // Update |request_info_| when following a redirect.
+      if (url_chain_.size() > 0) {
+        request_info_ = CreateNavigationRequestInfoForRedirect(
+            *request_info_, *resource_request_);
+      }
+      url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
+          base::MakeRefCounted<SingleRequestURLLoaderFactory>(
+              default_request_handler_factory_.Run()),
+          CreateURLLoaderThrottles(), frame_tree_node_id_, 0 /* request_id */,
+          network::mojom::kURLLoadOptionNone, resource_request_.get(),
+          this /* client */, kNavigationUrlLoaderTrafficAnnotation,
+          base::ThreadTaskRunnerHandle::Get());
+      return;
+    }
+
     if (resource_request_->url.SchemeIs(url::kBlobScheme)) {
       factory = base::MakeRefCounted<WeakWrapperSharedURLLoaderFactory>(
           default_url_loader_factory_getter_->GetBlobFactory());
@@ -890,10 +984,35 @@
         navigation_ui_data_.get(), frame_tree_node_id_);
   }
 
+  std::unique_ptr<NavigationLoaderInterceptor> CreateServiceWorkerInterceptor(
+      const NavigationRequestInfo& request_info,
+      ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core)
+      const {
+    const ResourceType resource_type = request_info.is_main_frame
+                                           ? RESOURCE_TYPE_MAIN_FRAME
+                                           : RESOURCE_TYPE_SUB_FRAME;
+    network::mojom::RequestContextFrameType frame_type =
+        request_info.is_main_frame
+            ? network::mojom::RequestContextFrameType::kTopLevel
+            : network::mojom::RequestContextFrameType::kNested;
+    storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext(
+        GetChromeBlobStorageContextForResourceContext(resource_context_));
+    return ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
+        *resource_request_, resource_context_,
+        service_worker_navigation_handle_core, blob_storage_context,
+        request_info.begin_params->skip_service_worker, resource_type,
+        request_info.begin_params->request_context_type, frame_type,
+        request_info.are_ancestors_secure, request_info.common_params.post_data,
+        web_contents_getter_);
+  }
+
   std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors_;
   size_t interceptor_index_ = 0;
 
   std::unique_ptr<network::ResourceRequest> resource_request_;
+  // Non-NetworkService: |request_info_| is updated along with
+  // |resource_request_| on redirects.
+  std::unique_ptr<NavigationRequestInfo> request_info_;
   int frame_tree_node_id_ = 0;
   GlobalRequestID global_request_id_;
   net::RedirectInfo redirect_info_;
@@ -948,6 +1067,14 @@
   std::map<std::string, network::mojom::URLLoaderFactoryPtr>
       non_network_url_loader_factories_;
 
+  // Non-NetworkService:
+  // Generator of a request handler for sending request to the network. This
+  // captures all of parameters to create a
+  // SingleRequestURLLoaderFactory::RequestHandler. Used only when
+  // NetworkService is disabled but IsLoaderInterceptionEnabled() is true.
+  base::RepeatingCallback<SingleRequestURLLoaderFactory::RequestHandler()>
+      default_request_handler_factory_;
+
   // The completion status if it has been received. This is needed to handle
   // the case that the response is intercepted by download, and OnComplete() is
   // already called while we are transferring the |url_loader_| and response
@@ -968,7 +1095,7 @@
   // protocol handlers.
   std::set<std::string> known_schemes_;
 
-  base::WeakPtrFactory<URLLoaderRequestController> weak_factory_;
+  mutable base::WeakPtrFactory<URLLoaderRequestController> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController);
 };
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index a0759cac..6e58bdc 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -419,7 +419,14 @@
       if (event.params.HasKey("pipeline_buffering_state")) {
         std::string buffering_state;
         event.params.GetString("pipeline_buffering_state", &buffering_state);
-        if (buffering_state == "BUFFERING_HAVE_ENOUGH")
+
+        bool for_suspended_start;
+        event.params.GetBoolean("for_suspended_start", &for_suspended_start);
+
+        // Ignore the BUFFERING_HAVE_ENOUGH event if it was for a suspended
+        // start. Otherwise we won't reflect reductions to the HasEverPlayed
+        // statistic.
+        if (buffering_state == "BUFFERING_HAVE_ENOUGH" && !for_suspended_start)
           player_info.has_reached_have_enough = true;
       }
       break;
diff --git a/content/browser/renderer_host/delegated_frame_host.cc b/content/browser/renderer_host/delegated_frame_host.cc
index 513aa14..cfdb693 100644
--- a/content/browser/renderer_host/delegated_frame_host.cc
+++ b/content/browser/renderer_host/delegated_frame_host.cc
@@ -719,4 +719,10 @@
          !HasSavedFrame();
 }
 
+void DelegatedFrameHost::WindowTitleChanged(const std::string& title) {
+  auto* host_frame_sink_manager = GetHostFrameSinkManager();
+  if (host_frame_sink_manager)
+    host_frame_sink_manager->SetFrameSinkDebugLabel(frame_sink_id_, title);
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/delegated_frame_host.h b/content/browser/renderer_host/delegated_frame_host.h
index 6009c04..5e6c05d 100644
--- a/content/browser/renderer_host/delegated_frame_host.h
+++ b/content/browser/renderer_host/delegated_frame_host.h
@@ -199,6 +199,8 @@
 
   bool IsPrimarySurfaceEvicted() const;
 
+  void WindowTitleChanged(const std::string& title);
+
  private:
   friend class DelegatedFrameHostClient;
   FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAuraTest,
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index 5546524..a892fe9 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -1192,22 +1192,11 @@
   event.SetPositionInWidget(event.PositionInWidget() +
                             touchscreen_gesture_target_.delta);
   // Temporary logging for https://crbug.com/824774.
-  // TODO(wjmaclean): Remove all this logging code once the issue is resolved.
   static auto* target_ptr_key = base::debug::AllocateCrashKeyString(
       "touchscreen-gesture-target-ptr", base::debug::CrashKeySize::Size64);
   base::debug::SetCrashKeyString(
       target_ptr_key,
       base::StringPrintf("%p", touchscreen_gesture_target_.target));
-  // Issue 824772 is a potential cause for issue 824774. Report whether
-  // devtools is in use to investigate this possibility.
-  auto* contents = root_view->host()->delegate()->GetAsWebContents();
-  const bool have_devtools =
-      contents && DevToolsAgentHost::IsDebuggerAttached(contents);
-  static auto* devtools_key = base::debug::AllocateCrashKeyString(
-      "touchscreen-gesture-target-have-devtools",
-      base::debug::CrashKeySize::Size32);
-  base::debug::SetCrashKeyString(devtools_key, std::to_string(have_devtools));
-  // Crash in 824774 appears to happen on next line.
   touchscreen_gesture_target_.target->ProcessGestureEvent(event, latency);
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_ns_view_client.h b/content/browser/renderer_host/render_widget_host_ns_view_client.h
index 439da91..a862782 100644
--- a/content/browser/renderer_host/render_widget_host_ns_view_client.h
+++ b/content/browser/renderer_host/render_widget_host_ns_view_client.h
@@ -49,6 +49,18 @@
   // Indicate the NSView's NSScreen's properties.
   virtual void OnNSViewDisplayChanged(const display::Display& display) = 0;
 
+  // Forward events to the renderer or the input router, as appropriate.
+  virtual void OnNSViewRouteOrProcessMouseEvent(
+      const blink::WebMouseEvent& web_event) = 0;
+  virtual void OnNSViewRouteOrProcessWheelEvent(
+      const blink::WebMouseWheelEvent& web_event) = 0;
+
+  // Special case forwarding of synthetic events to the renderer.
+  virtual void OnNSViewForwardMouseEvent(
+      const blink::WebMouseEvent& web_event) = 0;
+  virtual void OnNSViewForwardWheelEvent(
+      const blink::WebMouseWheelEvent& web_event) = 0;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostNSViewClient);
 };
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 7b121a31..5d7f4663 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -273,6 +273,11 @@
     view_->ParentHierarchyChanged();
   }
 
+  void OnWindowTitleChanged(aura::Window* window) override {
+    if (window == view_->window_)
+      view_->WindowTitleChanged();
+  }
+
  private:
   RenderWidgetHostViewAura* view_;
 
@@ -734,6 +739,13 @@
   window_->layer()->SetColor(color);
 }
 
+void RenderWidgetHostViewAura::WindowTitleChanged() {
+  if (delegated_frame_host_) {
+    delegated_frame_host_->WindowTitleChanged(
+        base::UTF16ToUTF8(window_->GetTitle()));
+  }
+}
+
 bool RenderWidgetHostViewAura::IsMouseLocked() {
   return event_handler_->mouse_locked();
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index 539e682..06484c45d 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -532,6 +532,9 @@
   // color to new views on navigation.
   void UpdateBackgroundColorFromRenderer(SkColor color);
 
+  // Called when the window title is changed.
+  void WindowTitleChanged();
+
   const bool is_mus_browser_plugin_guest_;
 
   // NOTE: this is null if |is_mus_browser_plugin_guest_| is true.
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.cc b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
index 38da5e4..1a459387 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
@@ -315,13 +315,22 @@
     if (parent_view)
       return parent_view->GetVisibleViewportSize();
   }
-  return GetViewBounds().size();
+
+  gfx::Rect bounds = GetViewBounds();
+
+  // It doesn't make sense to set insets on an OOP iframe. The only time this
+  // should happen is when the virtual keyboard comes up on a <webview>.
+  if (is_guest)
+    bounds.Inset(insets_);
+
+  return bounds.size();
 }
 
 void RenderWidgetHostViewChildFrame::SetInsets(const gfx::Insets& insets) {
-  // Do nothing here. For subframes, the visual viewport corresponds to the main
-  // frame viewport size, so this request will be handled in SetInsets of the
-  // main frame's RenderWidgetHostView.
+  // Insets are used only for <webview> and are used to let the UI know it's
+  // being obscured (for e.g. by the virtual keyboard).
+  insets_ = insets;
+  host()->WasResized(!insets_.IsEmpty());
 }
 
 gfx::NativeView RenderWidgetHostViewChildFrame::GetNativeView() const {
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.h b/content/browser/renderer_host/render_widget_host_view_child_frame.h
index c656b91..5d6cc9e8 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.h
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.h
@@ -306,6 +306,8 @@
   // The background color of the widget.
   SkColor background_color_;
 
+  gfx::Insets insets_;
+
   std::unique_ptr<TouchSelectionControllerClientChildFrame>
       selection_controller_client_;
 
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 a1aef875..e7c7f25a 100644
--- a/content/browser/renderer_host/render_widget_host_view_cocoa.mm
+++ b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
@@ -327,7 +327,7 @@
   WebMouseEvent web_event = WebMouseEventBuilder::Build(event, self);
   web_event.SetModifiers(web_event.GetModifiers() |
                          WebInputEvent::kRelativeMotionEvent);
-  renderWidgetHostView_->ForwardMouseEvent(web_event);
+  client_->OnNSViewForwardMouseEvent(web_event);
 }
 
 - (BOOL)shouldIgnoreMouseEvent:(NSEvent*)theEvent {
@@ -400,12 +400,12 @@
 
   if ([self shouldIgnoreMouseEvent:theEvent]) {
     // If this is the first such event, send a mouse exit to the host view.
-    if (!mouseEventWasIgnored_ && renderWidgetHostView_->host()) {
+    if (!mouseEventWasIgnored_ && !clientWasDestroyed_) {
       WebMouseEvent exitEvent =
           WebMouseEventBuilder::Build(theEvent, self, pointerType_);
       exitEvent.SetType(WebInputEvent::kMouseLeave);
       exitEvent.button = WebMouseEvent::Button::kNoButton;
-      renderWidgetHostView_->ForwardMouseEvent(exitEvent);
+      client_->OnNSViewForwardMouseEvent(exitEvent);
     }
     mouseEventWasIgnored_ = YES;
     return;
@@ -414,21 +414,12 @@
   if (mouseEventWasIgnored_) {
     // If this is the first mouse event after a previous event that was ignored
     // due to the hitTest, send a mouse enter event to the host view.
-    if (renderWidgetHostView_->host()) {
+    if (!clientWasDestroyed_) {
       WebMouseEvent enterEvent =
           WebMouseEventBuilder::Build(theEvent, self, pointerType_);
       enterEvent.SetType(WebInputEvent::kMouseMove);
       enterEvent.button = WebMouseEvent::Button::kNoButton;
-      ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
-      latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
-      if (renderWidgetHostView_->ShouldRouteEvent(enterEvent)) {
-        renderWidgetHostView_->host()
-            ->delegate()
-            ->GetInputEventRouter()
-            ->RouteMouseEvent(renderWidgetHostView_, &enterEvent, latency_info);
-      } else {
-        renderWidgetHostView_->ProcessMouseEvent(enterEvent, latency_info);
-      }
+      client_->OnNSViewRouteOrProcessMouseEvent(enterEvent);
     }
   }
   mouseEventWasIgnored_ = NO;
@@ -459,16 +450,7 @@
 
   WebMouseEvent event =
       WebMouseEventBuilder::Build(theEvent, self, pointerType_);
-  ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
-  latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
-  if (renderWidgetHostView_->ShouldRouteEvent(event)) {
-    renderWidgetHostView_->host()
-        ->delegate()
-        ->GetInputEventRouter()
-        ->RouteMouseEvent(renderWidgetHostView_, &event, latency_info);
-  } else {
-    renderWidgetHostView_->ProcessMouseEvent(event, latency_info);
-  }
+  client_->OnNSViewRouteOrProcessMouseEvent(event);
 }
 
 - (void)tabletEvent:(NSEvent*)theEvent {
@@ -816,19 +798,11 @@
     return;
   }
 
-  if (renderWidgetHostView_->host()) {
+  if (!clientWasDestroyed_) {
     // History-swiping is not possible if the logic reaches this point.
     WebMouseWheelEvent webEvent = WebMouseWheelEventBuilder::Build(event, self);
     webEvent.rails_mode = mouseWheelFilter_.UpdateRailsMode(webEvent);
-    if (renderWidgetHostView_->wheel_scroll_latching_enabled()) {
-      renderWidgetHostView_->mouse_wheel_phase_handler_
-          .AddPhaseIfNeededAndScheduleEndEvent(webEvent, false);
-    } else {
-      ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
-      latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
-      renderWidgetHostView_->host()->ForwardWheelEventWithLatencyInfo(
-          webEvent, latency_info);
-    }
+    client_->OnNSViewForwardWheelEvent(webEvent);
   }
 
   if (endWheelMonitor_) {
@@ -1105,32 +1079,10 @@
   }
 
   // This is responsible for content scrolling!
-  if (renderWidgetHostView_->host()) {
+  if (!clientWasDestroyed_) {
     WebMouseWheelEvent webEvent = WebMouseWheelEventBuilder::Build(event, self);
     webEvent.rails_mode = mouseWheelFilter_.UpdateRailsMode(webEvent);
-    ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
-    latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
-    if (renderWidgetHostView_->wheel_scroll_latching_enabled()) {
-      renderWidgetHostView_->mouse_wheel_phase_handler_
-          .AddPhaseIfNeededAndScheduleEndEvent(
-              webEvent, renderWidgetHostView_->ShouldRouteEvent(webEvent));
-      if (webEvent.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
-        // A wheel end event is scheduled and will get dispatched if momentum
-        // phase doesn't start in 100ms. Don't sent the wheel end event
-        // immediately.
-        return;
-      }
-    }
-
-    if (renderWidgetHostView_->ShouldRouteEvent(webEvent)) {
-      renderWidgetHostView_->host()
-          ->delegate()
-          ->GetInputEventRouter()
-          ->RouteMouseWheelEvent(renderWidgetHostView_, &webEvent,
-                                 latency_info);
-    } else {
-      renderWidgetHostView_->ProcessMouseWheelEvent(webEvent, latency_info);
-    }
+    client_->OnNSViewRouteOrProcessWheelEvent(webEvent);
   }
 }
 
@@ -1919,8 +1871,7 @@
     WebMouseEvent event(WebInputEvent::kMouseUp, WebInputEvent::kNoModifiers,
                         ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
     event.button = WebMouseEvent::Button::kLeft;
-    renderWidgetHostView_->ForwardMouseEvent(event);
-
+    client_->OnNSViewForwardMouseEvent(event);
     hasOpenMouseDown_ = NO;
   }
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index a2207c69..37b77e20 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -290,6 +290,14 @@
   void OnNSViewWindowFrameInScreenChanged(
       const gfx::Rect& window_frame_in_screen_dip) override;
   void OnNSViewDisplayChanged(const display::Display& display) override;
+  void OnNSViewRouteOrProcessMouseEvent(
+      const blink::WebMouseEvent& web_event) override;
+  void OnNSViewRouteOrProcessWheelEvent(
+      const blink::WebMouseWheelEvent& web_event) override;
+  void OnNSViewForwardMouseEvent(
+      const blink::WebMouseEvent& web_event) override;
+  void OnNSViewForwardWheelEvent(
+      const blink::WebMouseWheelEvent& web_event) override;
 
   // BrowserCompositorMacClient implementation.
   SkColor BrowserCompositorMacGetGutterColor() const override;
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 3d01effe..d390e284 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -794,14 +794,6 @@
       src_subrect, dst_size, std::move(callback));
 }
 
-void RenderWidgetHostViewMac::ForwardMouseEvent(const WebMouseEvent& event) {
-  if (host())
-    host()->ForwardMouseEvent(event);
-
-  if (event.GetType() == WebInputEvent::kMouseLeave)
-    ns_view_bridge_->SetTooltipText(base::string16());
-}
-
 void RenderWidgetHostViewMac::SetNeedsBeginFrames(bool needs_begin_frames) {
   needs_begin_frames_ = needs_begin_frames;
   UpdateNeedsBeginFramesInternal();
@@ -1374,6 +1366,64 @@
   UpdateNSViewAndDisplayProperties();
 }
 
+void RenderWidgetHostViewMac::OnNSViewRouteOrProcessMouseEvent(
+    const blink::WebMouseEvent& const_web_event) {
+  blink::WebMouseEvent web_event = const_web_event;
+  ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
+  latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+  if (ShouldRouteEvent(web_event)) {
+    host()->delegate()->GetInputEventRouter()->RouteMouseEvent(this, &web_event,
+                                                               latency_info);
+  } else {
+    ProcessMouseEvent(web_event, latency_info);
+  }
+}
+
+void RenderWidgetHostViewMac::OnNSViewRouteOrProcessWheelEvent(
+    const blink::WebMouseWheelEvent& const_web_event) {
+  blink::WebMouseWheelEvent web_event = const_web_event;
+  ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
+  latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+  if (wheel_scroll_latching_enabled()) {
+    mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(
+        web_event, ShouldRouteEvent(web_event));
+    if (web_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
+      // A wheel end event is scheduled and will get dispatched if momentum
+      // phase doesn't start in 100ms. Don't sent the wheel end event
+      // immediately.
+      return;
+    }
+  }
+  if (ShouldRouteEvent(web_event)) {
+    host()->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
+        this, &web_event, latency_info);
+  } else {
+    ProcessMouseWheelEvent(web_event, latency_info);
+  }
+}
+
+void RenderWidgetHostViewMac::OnNSViewForwardMouseEvent(
+    const blink::WebMouseEvent& web_event) {
+  if (host())
+    host()->ForwardMouseEvent(web_event);
+
+  if (web_event.GetType() == WebInputEvent::kMouseLeave)
+    ns_view_bridge_->SetTooltipText(base::string16());
+}
+
+void RenderWidgetHostViewMac::OnNSViewForwardWheelEvent(
+    const blink::WebMouseWheelEvent& const_web_event) {
+  blink::WebMouseWheelEvent web_event = const_web_event;
+  if (wheel_scroll_latching_enabled()) {
+    mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(web_event,
+                                                                   false);
+  } else {
+    ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
+    latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+    host()->ForwardWheelEventWithLatencyInfo(web_event, latency_info);
+  }
+}
+
 Class GetRenderWidgetHostViewCocoaClassForTesting() {
   return [RenderWidgetHostViewCocoa class];
 }
diff --git a/content/browser/service_worker/embedded_worker_instance_unittest.cc b/content/browser/service_worker/embedded_worker_instance_unittest.cc
index fee9080..08df5bf 100644
--- a/content/browser/service_worker/embedded_worker_instance_unittest.cc
+++ b/content/browser/service_worker/embedded_worker_instance_unittest.cc
@@ -87,8 +87,9 @@
       GetRegistrationForReadyCallback callback) override {
     NOTIMPLEMENTED();
   }
-  void GetControllerServiceWorker(
-      mojom::ControllerServiceWorkerRequest request) override {
+  void EnsureControllerServiceWorker(
+      mojom::ControllerServiceWorkerRequest request,
+      mojom::ControllerServiceWorkerPurpose purpose) override {
     NOTIMPLEMENTED();
   }
   void CloneForWorker(
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 9b7d5a88..136fca3 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -321,6 +321,9 @@
   // We have a non-redirect response. Send the headers to the client.
   CommitResponseHeaders();
 
+  // S13nServiceWorker without NetworkService:
+  // TODO(shimazu): Wait to respond body until ProceedWithResponse().
+
   // Handle a stream response body.
   if (!body_as_stream.is_null() && body_as_stream->stream.is_valid()) {
     stream_waiter_ = std::make_unique<StreamWaiter>(
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index cc449730..9cfd493 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -136,6 +136,16 @@
                       origin);
 }
 
+ServiceWorkerMetrics::EventType PurposeToEventType(
+    mojom::ControllerServiceWorkerPurpose purpose) {
+  switch (purpose) {
+    case mojom::ControllerServiceWorkerPurpose::FETCH_SUB_RESOURCE:
+      return ServiceWorkerMetrics::EventType::FETCH_SUB_RESOURCE;
+  }
+  NOTREACHED();
+  return ServiceWorkerMetrics::EventType::UNKNOWN;
+}
+
 }  // anonymous namespace
 
 // static
@@ -309,6 +319,8 @@
       ServiceWorkerVersion::FetchHandlerExistence::DOES_NOT_EXIST) {
     return nullptr;
   }
+  // TODO(bashi): Make sure the worker is running by calling
+  // controller_->RunAfterStartWorker().
   mojom::ControllerServiceWorkerPtr controller_ptr;
   controller_->controller()->Clone(mojo::MakeRequest(&controller_ptr));
   return controller_ptr;
@@ -1009,16 +1021,26 @@
   ReturnRegistrationForReadyIfNeeded();
 }
 
-void ServiceWorkerProviderHost::GetControllerServiceWorker(
-    mojom::ControllerServiceWorkerRequest controller_request) {
+void ServiceWorkerProviderHost::StartControllerComplete(
+    mojom::ControllerServiceWorkerRequest controller_request,
+    ServiceWorkerStatusCode status) {
+  DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
+  if (status == SERVICE_WORKER_OK)
+    controller_->controller()->Clone(std::move(controller_request));
+}
+
+void ServiceWorkerProviderHost::EnsureControllerServiceWorker(
+    mojom::ControllerServiceWorkerRequest controller_request,
+    mojom::ControllerServiceWorkerPurpose purpose) {
   // TODO(kinuko): Log the reasons we drop the request.
   if (!dispatcher_host_ || !IsContextAlive() || !controller_)
     return;
 
-  // TODO(kinuko): Call version_->StartWorker() here if the service
-  // is not starting or running. https://crbug.com/797222
   DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
-  controller_->controller()->Clone(std::move(controller_request));
+  controller_->RunAfterStartWorker(
+      PurposeToEventType(purpose),
+      base::BindOnce(&ServiceWorkerProviderHost::StartControllerComplete,
+                     AsWeakPtr(), std::move(controller_request)));
 }
 
 void ServiceWorkerProviderHost::CloneForWorker(
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 8ba983d..0bb4b7dd 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -443,8 +443,9 @@
   void GetRegistrations(GetRegistrationsCallback callback) override;
   void GetRegistrationForReady(
       GetRegistrationForReadyCallback callback) override;
-  void GetControllerServiceWorker(
-      mojom::ControllerServiceWorkerRequest controller_request) override;
+  void EnsureControllerServiceWorker(
+      mojom::ControllerServiceWorkerRequest controller_request,
+      mojom::ControllerServiceWorkerPurpose purpose) override;
   void CloneForWorker(
       mojom::ServiceWorkerContainerHostRequest container_host_request) override;
 
@@ -468,6 +469,11 @@
       const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
           registrations);
 
+  // Callback for ServiceWorkerVersion::RunAfterStartWorker()
+  void StartControllerComplete(
+      mojom::ControllerServiceWorkerRequest controller_request,
+      ServiceWorkerStatusCode status);
+
   bool IsValidRegisterMessage(
       const GURL& script_url,
       const blink::mojom::ServiceWorkerRegistrationOptions& options,
diff --git a/content/browser/service_worker/service_worker_request_handler.cc b/content/browser/service_worker/service_worker_request_handler.cc
index 65eeb33..bb16c71 100644
--- a/content/browser/service_worker/service_worker_request_handler.cc
+++ b/content/browser/service_worker/service_worker_request_handler.cc
@@ -86,6 +86,15 @@
     const base::Callback<WebContents*(void)>& web_contents_getter) {
   CHECK(IsBrowserSideNavigationEnabled());
 
+  // S13nServiceWorker enabled, NetworkService disabled:
+  // To start the navigation, InitializeForNavigationNetworkService() is called
+  // instead of this, but when that request handler falls back to network,
+  // InitializeForNavigation() is called.
+  // Since we already determined to fall back to network, don't create another
+  // handler.
+  if (ServiceWorkerUtils::IsServicificationEnabled())
+    return;
+
   // Only create a handler when there is a ServiceWorkerNavigationHandlerCore
   // to take ownership of a pre-created SeviceWorkerProviderHost.
   if (!navigation_handle_core)
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index e46d26e..ff32e2cb 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -28,6 +28,7 @@
 #include "content/browser/loader/prefetch_url_loader_service.h"
 #include "content/browser/notifications/platform_notification_context_impl.h"
 #include "content/common/dom_storage/dom_storage_types.h"
+#include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
@@ -667,7 +668,8 @@
   scoped_refptr<ChromeBlobStorageContext> blob_context =
       ChromeBlobStorageContext::GetFor(context);
 
-  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
+      ServiceWorkerUtils::IsServicificationEnabled()) {
     BlobURLLoaderFactory::BlobContextGetter blob_getter =
         base::BindOnce(&BlobStorageContextGetterForStorage, blob_context);
     partition->blob_url_loader_factory_ =
diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc
index 2098c8f..f544135 100644
--- a/content/browser/web_contents/web_contents_view_aura.cc
+++ b/content/browser/web_contents/web_contents_view_aura.cc
@@ -832,8 +832,12 @@
 }
 
 void WebContentsViewAura::SetPageTitle(const base::string16& title) {
-  if (!is_mus_browser_plugin_guest_)
+  if (!is_mus_browser_plugin_guest_) {
     window_->SetTitle(title);
+    aura::Window* child_window = GetContentNativeView();
+    if (child_window)
+      child_window->SetTitle(title);
+  }
 }
 
 void WebContentsViewAura::RenderViewCreated(RenderViewHost* host) {
diff --git a/content/common/service_worker/service_worker_container.mojom b/content/common/service_worker/service_worker_container.mojom
index 055be4d..86739ba0 100644
--- a/content/common/service_worker/service_worker_container.mojom
+++ b/content/common/service_worker/service_worker_container.mojom
@@ -13,6 +13,12 @@
 import "third_party/WebKit/public/platform/web_feature.mojom";
 import "url/mojom/url.mojom";
 
+// Used for EnsureControllerServiceWorker() to indicate why a controllee needs
+// a controller ServiceWorker.
+enum ControllerServiceWorkerPurpose {
+  FETCH_SUB_RESOURCE
+};
+
 // mojom::ServiceWorkerContainerHost is a browser-side interface. The renderer
 // process uses this interface to request the browser process to do operations
 // involving service worker registrations.
@@ -56,7 +62,7 @@
     => (blink.mojom.ServiceWorkerRegistrationObjectInfo? registration);
 
   // S13nServiceWorker:
-  // Gets a Mojo end point to the controller ServiceWorker. This may start a
+  // Returns a Mojo end point to the controller ServiceWorker. This may start a
   // service worker instance in a renderer process if the corresponding
   // instance is not alive.
   // This method must be called only by the controllees.
@@ -64,7 +70,9 @@
   // connection error of the returned pipe. The detailed error reasons are not
   // reported to the controllees, but the browser process is responsible for
   // properly handling the failure and recording the reasons.
-  GetControllerServiceWorker(ControllerServiceWorker& controller);
+  // |purpose| is used for UMA.
+  EnsureControllerServiceWorker(ControllerServiceWorker& controller,
+                                ControllerServiceWorkerPurpose purpose);
 
   // S13nServiceWorker:
   // Clones the Mojo end point to the ServiceWorker container host. This is
diff --git a/content/gpu/gpu_sandbox_hook_linux.cc b/content/gpu/gpu_sandbox_hook_linux.cc
index ddd7b99..cd914e2 100644
--- a/content/gpu/gpu_sandbox_hook_linux.cc
+++ b/content/gpu/gpu_sandbox_hook_linux.cc
@@ -153,6 +153,7 @@
   static const char kDriCardBasePath[] = "/dev/dri/card";
   static const char kNvidiaCtlPath[] = "/dev/nvidiactl";
   static const char kNvidiaDeviceBasePath[] = "/dev/nvidia";
+  static const char kNvidiaDeviceModeSetPath[] = "/dev/nvidia-modeset";
   static const char kNvidiaParamsPath[] = "/proc/driver/nvidia/params";
   static const char kDevShm[] = "/dev/shm/";
 
@@ -172,6 +173,8 @@
     permissions->push_back(BrokerFilePermission::ReadWrite(
         base::StringPrintf("%s%d", kNvidiaDeviceBasePath, i)));
   }
+  permissions->push_back(
+      BrokerFilePermission::ReadWrite(kNvidiaDeviceModeSetPath));
   permissions->push_back(BrokerFilePermission::ReadOnly(kNvidiaParamsPath));
 }
 
@@ -262,9 +265,9 @@
   sandbox::syscall_broker::BrokerCommandSet command_set;
   command_set.set(sandbox::syscall_broker::COMMAND_ACCESS);
   command_set.set(sandbox::syscall_broker::COMMAND_OPEN);
+  command_set.set(sandbox::syscall_broker::COMMAND_STAT);
   if (IsChromeOS() && options.use_amd_specific_policies) {
     command_set.set(sandbox::syscall_broker::COMMAND_READLINK);
-    command_set.set(sandbox::syscall_broker::COMMAND_STAT);
   }
   return command_set;
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCoreImpl.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCoreImpl.java
index 4196083a..da59b098 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCoreImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCoreImpl.java
@@ -74,8 +74,15 @@
     // A ViewAndroidDelegate that delegates to the current container view.
     private ViewAndroidDelegate mViewAndroidDelegate;
 
+    // TODO(mthiesse): Clean up the focus code here, this boolean doesn't actually reflect view
+    // focus. It reflects a combination of both view focus and resumed state.
     private Boolean mHasViewFocus;
 
+    // This is used in place of window focus, as we can't actually use window focus due to issues
+    // where content expects to be focused while a popup steals window focus.
+    // See https://crbug.com/686232 for more context.
+    private boolean mPaused;
+
     // The list of observers that are notified when ContentViewCore changes its WindowAndroid.
     private final ObserverList<WindowAndroidChangedObserver> mWindowAndroidChangedObservers =
             new ObserverList<>();
@@ -383,11 +390,13 @@
 
     @Override
     public void onPause() {
+        mPaused = true;
         onFocusChanged(false, true);
     }
 
     @Override
     public void onResume() {
+        mPaused = false;
         onFocusChanged(ViewUtils.hasFocus(getContainerView()), true);
     }
 
@@ -402,6 +411,9 @@
     @Override
     public void onFocusChanged(boolean gainFocus, boolean hideKeyboardOnBlur) {
         if (mHasViewFocus != null && mHasViewFocus == gainFocus) return;
+        // TODO(mthiesse): Clean this up. mHasViewFocus isn't view focus really, it's a combination
+        // of view focus and resumed state.
+        if (gainFocus && mPaused) return;
         mHasViewFocus = gainFocus;
 
         if (mWebContents == null) {
diff --git a/content/public/common/browser_side_navigation_policy.cc b/content/public/common/browser_side_navigation_policy.cc
index defb585..c712061 100644
--- a/content/public/common/browser_side_navigation_policy.cc
+++ b/content/public/common/browser_side_navigation_policy.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "services/network/public/cpp/features.h"
 
 namespace content {
 
@@ -23,7 +24,10 @@
     return false;
 
   return base::FeatureList::IsEnabled(features::kNavigationMojoResponse) ||
-         base::FeatureList::IsEnabled(features::kSignedHTTPExchange);
+         base::FeatureList::IsEnabled(
+             features::kServiceWorkerServicification) ||
+         base::FeatureList::IsEnabled(features::kSignedHTTPExchange) ||
+         base::FeatureList::IsEnabled(network::features::kNetworkService);
 }
 
 }  // namespace content
diff --git a/content/renderer/media/webrtc/rtc_rtp_receiver_unittest.cc b/content/renderer/media/webrtc/rtc_rtp_receiver_unittest.cc
index f9727113..f4b91bb 100644
--- a/content/renderer/media/webrtc/rtc_rtp_receiver_unittest.cc
+++ b/content/renderer/media/webrtc/rtc_rtp_receiver_unittest.cc
@@ -127,7 +127,8 @@
   EXPECT_EQ(copy->Track().UniqueId(), web_track_unique_id);
 }
 
-TEST_F(RTCRtpReceiverTest, GetStats) {
+// Test is flaky (https://crbug.com/827450).
+TEST_F(RTCRtpReceiverTest, DISABLED_GetStats) {
   scoped_refptr<MockWebRtcAudioTrack> webrtc_track =
       MockWebRtcAudioTrack::Create("webrtc_track");
   receiver_ = CreateReceiver(webrtc_track);
diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.cc b/content/renderer/pepper/pepper_plugin_instance_impl.cc
index fa0e09e..8b7ecb9 100644
--- a/content/renderer/pepper/pepper_plugin_instance_impl.cc
+++ b/content/renderer/pepper/pepper_plugin_instance_impl.cc
@@ -2201,6 +2201,7 @@
 }
 
 bool PepperPluginInstanceImpl::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* transferable_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* release_callback) {
   if (!bound_graphics_2d_platform_)
diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.h b/content/renderer/pepper/pepper_plugin_instance_impl.h
index 313ee60..2e34f02 100644
--- a/content/renderer/pepper/pepper_plugin_instance_impl.h
+++ b/content/renderer/pepper/pepper_plugin_instance_impl.h
@@ -556,6 +556,7 @@
 
   // cc::TextureLayerClient implementation.
   bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* transferable_resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override;
 
diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc
index c6522e4..4fae6e2 100644
--- a/content/renderer/pepper/video_decoder_shim.cc
+++ b/content/renderer/pepper/video_decoder_shim.cc
@@ -37,6 +37,7 @@
 #include "ppapi/c/pp_errors.h"
 #include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
 #include "third_party/skia/include/gpu/GrTypes.h"
+#include "ui/gfx/color_transform.h"
 
 namespace content {
 
@@ -66,7 +67,8 @@
   void Convert(const scoped_refptr<media::VideoFrame>& frame, GLuint tex_out);
 
  private:
-  GLuint CreateShader();
+  GLuint CreateShader(const gfx::ColorSpace& from_colorspace,
+                      const gfx::ColorSpace& to_colorspace);
   GLuint CompileShader(const char* name, GLuint type, const char* code);
   GLuint CreateProgram(const char* name, GLuint vshader, GLuint fshader);
   GLuint CreateTexture();
@@ -76,6 +78,7 @@
   GLuint frame_buffer_;
   GLuint vertex_buffer_;
   GLuint program_;
+  gfx::ColorSpace last_color_space_;
 
   GLuint y_texture_;
   GLuint u_texture_;
@@ -94,9 +97,6 @@
   uint32_t uv_height_divisor_;
   uint32_t uv_width_divisor_;
 
-  GLint yuv_matrix_loc_;
-  GLint yuv_adjust_loc_;
-
   DISALLOW_COPY_AND_ASSIGN(YUVConverter);
 };
 
@@ -119,9 +119,7 @@
       uv_width_(2),
       uv_height_(2),
       uv_height_divisor_(1),
-      uv_width_divisor_(1),
-      yuv_matrix_loc_(0),
-      yuv_adjust_loc_(0) {
+      uv_width_divisor_(1) {
   DCHECK(gl_);
 }
 
@@ -230,7 +228,9 @@
   return program;
 }
 
-GLuint VideoDecoderShim::YUVConverter::CreateShader() {
+GLuint VideoDecoderShim::YUVConverter::CreateShader(
+    const gfx::ColorSpace& from_colorspace,
+    const gfx::ColorSpace& to_colorspace) {
   const char* vert_shader =
       "precision mediump float;\n"
       "attribute vec2 position;\n"
@@ -241,22 +241,31 @@
       "    texcoord = position*0.5+0.5;\n"
       "}";
 
-  const char* frag_shader =
+  std::string frag_shader =
       "precision mediump float;\n"
       "varying vec2 texcoord;\n"
       "uniform sampler2D y_sampler;\n"
       "uniform sampler2D u_sampler;\n"
       "uniform sampler2D v_sampler;\n"
-      "uniform sampler2D a_sampler;\n"
-      "uniform mat3 yuv_matrix;\n"
-      "uniform vec3 yuv_adjust;\n"
+      "uniform sampler2D a_sampler;\n";
+
+  std::unique_ptr<gfx::ColorTransform> transform(
+      gfx::ColorTransform::NewColorTransform(
+          from_colorspace, to_colorspace,
+          gfx::ColorTransform::Intent::INTENT_PERCEPTUAL));
+  if (!transform->CanGetShaderSource()) {
+    transform = gfx::ColorTransform::NewColorTransform(
+        gfx::ColorSpace::CreateREC709(), gfx::ColorSpace::CreateSRGB(),
+        gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
+  }
+  frag_shader += transform->GetShaderSource();
+  frag_shader +=
       "void main()\n"
       "{\n"
       "  vec3 yuv = vec3(texture2D(y_sampler, texcoord).x,\n"
       "                  texture2D(u_sampler, texcoord).x,\n"
-      "                  texture2D(v_sampler, texcoord).x) +\n"
-      "                  yuv_adjust;\n"
-      "  gl_FragColor = vec4(yuv_matrix * yuv, texture2D(a_sampler, "
+      "                  texture2D(v_sampler, texcoord).x);\n"
+      "  gl_FragColor = vec4(DoColorConversion(yuv), texture2D(a_sampler, "
       "texcoord).x);\n"
       "}";
 
@@ -267,7 +276,7 @@
   }
 
   GLuint fragment_shader =
-      CompileShader("Fragment Shader", GL_FRAGMENT_SHADER, frag_shader);
+      CompileShader("Fragment Shader", GL_FRAGMENT_SHADER, frag_shader.c_str());
   if (!fragment_shader) {
     gl_->DeleteShader(vertex_shader);
     return 0;
@@ -304,12 +313,6 @@
 
   gl_->UseProgram(0);
 
-  yuv_matrix_loc_ = gl_->GetUniformLocation(program, "yuv_matrix");
-  DCHECK(yuv_matrix_loc_ != -1);
-
-  yuv_adjust_loc_ = gl_->GetUniformLocation(program, "yuv_adjust");
-  DCHECK(yuv_adjust_loc_ != -1);
-
   return program;
 }
 
@@ -346,63 +349,22 @@
                   GL_STATIC_DRAW);
   gl_->BindBuffer(GL_ARRAY_BUFFER, 0);
 
-  program_ = CreateShader();
-
   gl_->TraceEndCHROMIUM();
 
-  return (program_ != 0);
+  return true;
 }
 
 void VideoDecoderShim::YUVConverter::Convert(
     const scoped_refptr<media::VideoFrame>& frame,
     GLuint tex_out) {
-  const float* yuv_matrix = nullptr;
-  const float* yuv_adjust = nullptr;
+  if (frame->ColorSpace() != last_color_space_ || !program_) {
+    last_color_space_ = frame->ColorSpace();
+    if (program_)
+      gl_->DeleteProgram(program_);
+    program_ = CreateShader(last_color_space_, gfx::ColorSpace::CreateSRGB());
+  }
 
   if (video_format_ != frame->format()) {
-    // The constants below were taken from
-    // components/viz/service/display/gl_renderer.cc. These values are magic
-    // numbers that are used in the transformation from YUV to RGB color values.
-    // They are taken from the following webpage:
-    // http://www.fourcc.org/fccyvrgb.php
-    const float yuv_to_rgb_rec601[9] = {
-        1.164f, 1.164f, 1.164f, 0.0f, -.391f, 2.018f, 1.596f, -.813f, 0.0f,
-    };
-    const float yuv_to_rgb_jpeg[9] = {
-        1.f, 1.f, 1.f, 0.0f, -.34414f, 1.772f, 1.402f, -.71414f, 0.0f,
-    };
-    const float yuv_to_rgb_rec709[9] = {
-        1.164f, 1.164f, 1.164f, 0.0f, -0.213f, 2.112f, 1.793f, -0.533f, 0.0f,
-    };
-
-    // These values map to 16, 128, and 128 respectively, and are computed
-    // as a fraction over 256 (e.g. 16 / 256 = 0.0625).
-    // They are used in the YUV to RGBA conversion formula:
-    //   Y - 16   : Gives 16 values of head and footroom for overshooting
-    //   U - 128  : Turns unsigned U into signed U [-128,127]
-    //   V - 128  : Turns unsigned V into signed V [-128,127]
-    const float yuv_adjust_constrained[3] = {
-        -0.0625f, -0.5f, -0.5f,
-    };
-    // Same as above, but without the head and footroom.
-    const float yuv_adjust_full[3] = {
-        0.0f, -0.5f, -0.5f,
-    };
-
-    yuv_adjust = yuv_adjust_constrained;
-    yuv_matrix = yuv_to_rgb_rec601;
-
-    int result;
-    if (frame->metadata()->GetInteger(media::VideoFrameMetadata::COLOR_SPACE,
-                                      &result)) {
-      if (result == media::COLOR_SPACE_JPEG) {
-        yuv_matrix = yuv_to_rgb_jpeg;
-        yuv_adjust = yuv_adjust_full;
-      } else if (result == media::COLOR_SPACE_HD_REC709) {
-        yuv_matrix = yuv_to_rgb_rec709;
-      }
-    }
-
     switch (frame->format()) {
       case media::PIXEL_FORMAT_I420A:
       case media::PIXEL_FORMAT_I420:
@@ -542,11 +504,6 @@
 
   gl_->UseProgram(program_);
 
-  if (yuv_matrix) {
-    gl_->UniformMatrix3fv(yuv_matrix_loc_, 1, 0, yuv_matrix);
-    gl_->Uniform3fv(yuv_adjust_loc_, 1, yuv_adjust);
-  }
-
   gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
   gl_->EnableVertexAttribArray(0);
   gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat),
diff --git a/content/renderer/service_worker/controller_service_worker_connector.cc b/content/renderer/service_worker/controller_service_worker_connector.cc
index beff4be..b8bc7f4 100644
--- a/content/renderer/service_worker/controller_service_worker_connector.cc
+++ b/content/renderer/service_worker/controller_service_worker_connector.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "content/common/service_worker/service_worker_container.mojom.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 
 namespace content {
@@ -24,13 +23,14 @@
 }
 
 mojom::ControllerServiceWorker*
-ControllerServiceWorkerConnector::GetControllerServiceWorker() {
+ControllerServiceWorkerConnector::GetControllerServiceWorker(
+    mojom::ControllerServiceWorkerPurpose purpose) {
   switch (state_) {
     case State::kDisconnected:
       DCHECK(!controller_service_worker_);
       DCHECK(container_host_);
-      container_host_->GetControllerServiceWorker(
-          mojo::MakeRequest(&controller_service_worker_));
+      container_host_->EnsureControllerServiceWorker(
+          mojo::MakeRequest(&controller_service_worker_), purpose);
       controller_service_worker_.set_connection_error_handler(base::BindOnce(
           &ControllerServiceWorkerConnector::OnControllerConnectionClosed,
           base::Unretained(this)));
diff --git a/content/renderer/service_worker/controller_service_worker_connector.h b/content/renderer/service_worker/controller_service_worker_connector.h
index 05b72e1..dbab8d8 100644
--- a/content/renderer/service_worker/controller_service_worker_connector.h
+++ b/content/renderer/service_worker/controller_service_worker_connector.h
@@ -10,6 +10,7 @@
 #include "base/observer_list.h"
 #include "content/common/content_export.h"
 #include "content/common/service_worker/controller_service_worker.mojom.h"
+#include "content/common/service_worker/service_worker_container.mojom.h"
 
 namespace content {
 
@@ -66,7 +67,8 @@
 
   // This may return nullptr if the connection to the ContainerHost (in the
   // browser process) is already terminated.
-  mojom::ControllerServiceWorker* GetControllerServiceWorker();
+  mojom::ControllerServiceWorker* GetControllerServiceWorker(
+      mojom::ControllerServiceWorkerPurpose purpose);
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index c578eb7..05a2446 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -231,7 +231,8 @@
   mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_ptr;
   response_callback_binding_.Bind(mojo::MakeRequest(&response_callback_ptr));
   mojom::ControllerServiceWorker* controller =
-      controller_connector_->GetControllerServiceWorker();
+      controller_connector_->GetControllerServiceWorker(
+          mojom::ControllerServiceWorkerPurpose::FETCH_SUB_RESOURCE);
   // When |controller| is null, the network request will be aborted soon since
   // the network provider has already been discarded. In that case, We don't
   // need to return an error as the client must be shutting down.
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 b6e74b89..023895cc 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
@@ -302,8 +302,9 @@
       GetRegistrationForReadyCallback callback) override {
     NOTIMPLEMENTED();
   }
-  void GetControllerServiceWorker(
-      mojom::ControllerServiceWorkerRequest request) override {
+  void EnsureControllerServiceWorker(
+      mojom::ControllerServiceWorkerRequest request,
+      mojom::ControllerServiceWorkerPurpose purpose) override {
     get_controller_service_worker_count_++;
     if (!fake_controller_)
       return;
diff --git a/content/shell/browser/layout_test/blink_test_controller.cc b/content/shell/browser/layout_test/blink_test_controller.cc
index 31db9799..48fcafe 100644
--- a/content/shell/browser/layout_test/blink_test_controller.cc
+++ b/content/shell/browser/layout_test/blink_test_controller.cc
@@ -554,6 +554,9 @@
 
 void BlinkTestController::OnInitiateCaptureDump(
     bool capture_navigation_history) {
+  if (test_phase_ != DURING_TEST)
+    return;
+
   if (capture_navigation_history) {
     RenderFrameHost* main_rfh = main_window_->web_contents()->GetMainFrame();
     for (auto* window : Shell::windows()) {
diff --git a/content/shell/test_runner/test_plugin.cc b/content/shell/test_runner/test_plugin.cc
index a871303..87a9b837 100644
--- a/content/shell/test_runner/test_plugin.cc
+++ b/content/shell/test_runner/test_plugin.cc
@@ -288,6 +288,7 @@
                                 bool lost) {}
 
 bool TestPlugin::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* resource,
     std::unique_ptr<viz::SingleReleaseCallback>* release_callback) {
   if (!content_changed_)
diff --git a/content/shell/test_runner/test_plugin.h b/content/shell/test_runner/test_plugin.h
index b33f50c..9445770 100644
--- a/content/shell/test_runner/test_plugin.h
+++ b/content/shell/test_runner/test_plugin.h
@@ -100,6 +100,7 @@
 
   // cc::TextureLayerClient methods:
   bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override;
 
diff --git a/content/test/data/android/authenticator.html b/content/test/data/android/authenticator.html
new file mode 100644
index 0000000..f30e13c
--- /dev/null
+++ b/content/test/data/android/authenticator.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>TestCredentials</title>
+        <script>
+            var CHALLENGE = new TextEncoder().encode("climb a mountain");
+            var PUBLIC_KEY_RP = {
+                id: "subdomain.example.test",
+                name: "Acme"
+            };
+
+            var PUBLIC_KEY_USER = {
+                id: new TextEncoder().encode("1098237235409872"),
+                name: "avery.a.jones@example.com",
+                displayName: "Avery A. Jones",
+                icon: "https://pics.acme.com/00/p/aBjjjpqPb.png"
+            };
+
+            var PUBLIC_KEY_PARAMETERS =  [{
+                type: "public-key",
+                alg: -7,
+            },];
+
+            var ACCEPTABLE_CREDENTIAL = {
+                type: "public-key",
+                id: new TextEncoder().encode("acceptableCredential"),
+                transports: ["usb", "nfc", "ble"]
+            };
+
+            function doCreatePublicKeyCredential() {
+                if (window.PublicKeyCredential === undefined) {
+                    window.document.title = 'Fail: navigator.credentials.create({publicKey}) === undefined';
+                    return;
+                }
+
+                var customPublicKey = {
+                    challenge: CHALLENGE,
+                    rp: PUBLIC_KEY_RP,
+                    user: PUBLIC_KEY_USER,
+                    pubKeyCredParams: PUBLIC_KEY_PARAMETERS,
+                };
+
+                navigator.credentials.create({publicKey : customPublicKey})
+                    .then(r => { window.document.title = 'Success';
+                }).catch(e => {
+                    if (e.message == "Not implemented") {
+                        window.document.title = 'Success';
+                    } else {
+                        window.document.title = 'Fail: ' + e.name + e.message;
+                    }
+                });
+            }
+
+            function doGetPublicKeyCredential() {
+                if (window.PublicKeyCredential === undefined) {
+                    window.document.title = 'Fail: navigator.credentials.get({publicKey}) === undefined';
+                    return;
+                }
+
+                var customPublicKey = {
+                    challenge: CHALLENGE,
+                    rpId: "subdomain.example.test",
+                    allowCredentials: [ACCEPTABLE_CREDENTIAL]
+                };
+
+                navigator.credentials.get({publicKey : customPublicKey})
+                    .then(r => { window.document.title = 'Success';
+                }).catch(e => {
+                    if (e.message == "Not implemented") {
+                        window.document.title = 'Success';
+                    } else {
+                        window.document.title = 'Fail: ' + e.name + e.message;
+                    }
+                });
+            }
+        </script>
+    </head>
+    <body>
+    </body>
+</html>
diff --git a/docs/building_old_revisions.md b/docs/building_old_revisions.md
new file mode 100644
index 0000000..293247f
--- /dev/null
+++ b/docs/building_old_revisions.md
@@ -0,0 +1,85 @@
+# Building old revisions
+
+Occasionally you may want to check out and build old versions of Chromium, such
+as when bisecting a regression or simply building an older release tag. Though
+this is not officially supported, these tips address some common complications.
+
+This process may be easier if you copy your checkout (starting from the
+directory containing `.gclient`) to a new location, so you can just delete the
+checkout when finished instead of having to undo changes to your primary working
+directory.
+
+## Get compatible depot_tools
+
+Check out a version of depot_tools from around the same time as the target
+revision. Since `gclient` auto-updates depot_tools, be sure to
+[disable depot_tools auto-update](https://dev.chromium.org/developers/how-tos/depottools#TOC-Disabling-auto-update)
+before continuing by setting the environment variable `DEPOT_TOOLS_UPDATE=0`.
+
+```shell
+# Get date of current revision:
+~/chrome/src $ COMMIT_DATE=$(git log -n 1 --pretty=format:%ci)
+
+# Check out depot_tools revision from the same time:
+~/depot_tools $ git checkout $(git rev-list -n 1 --before="$COMMIT_DATE" master)
+~/depot_tools $ export DEPOT_TOOLS_UPDATE=0
+```
+
+## Clean your working directory
+
+To avoid unexpected gclient behavior and conflicts between revisions, remove any
+directories that aren't part of the revision you've checked out. By default, Git
+will preserve directories with their own Git repositories; bypass this by
+passing the `--force` option twice to `git clean`.
+
+```shell
+$ git clean -ffd
+```
+
+Repeat this command until it doesn't find anything to remove.
+
+## Sync dependencies
+
+When running `gclient sync`, also remove any dependencies that are no longer
+required:
+
+```shell
+$ gclient sync -D --force --reset
+```
+
+**Warning: `gclient sync` may overwrite the URL of your `origin` remote** if it
+encounters problems. You'll notice this when Git starts thinking everything is
+"untracked" or "deleted". If this happens, fix and fetch the remote before
+continuing:
+
+```shell
+$ git remote get-url origin
+https://chromium.googlesource.com/chromium/deps/opus.git
+$ git remote set-url origin https://chromium.googlesource.com/chromium/src.git
+$ git fetch origin
+```
+
+It may also be necessary to run the revision's version of
+[build/install-build-deps.sh](/build/install-build-deps.sh).
+
+## Build
+
+Since build tools change over time, you may need to build using older versions
+of tools like Visual Studio.
+
+You may also need to disable goma (if enabled).
+
+## Get back to trunk
+
+When returning to a normal checkout, you may need to undo some of the changes
+above:
+
+*   Restore `depot_tools` to the `master` branch.
+*   Clean up any `_bad_scm/` directories in the directory containing `.gclient`.
+*   Revert your `.gclient` file if `gclient` changed it:
+
+    ```
+    WARNING: gclient detected an obsolete setting in your .gclient file.  The
+    file has been automagically updated.  The previous version is available at
+    .gclient.old.
+    ```
diff --git a/docs/gpu/debugging_gpu_related_code.md b/docs/gpu/debugging_gpu_related_code.md
new file mode 100644
index 0000000..1d5f57e
--- /dev/null
+++ b/docs/gpu/debugging_gpu_related_code.md
@@ -0,0 +1,235 @@
+# Debugging GPU related code
+
+Chromium's GPU system is multi-process, which can make debugging it rather
+difficult. See [GPU Command Buffer] for some of the nitty gitty. These are just
+a few notes to help with debugging.
+
+[TOC]
+
+<!-- TODO(kainino): update link if the page moves -->
+[GPU Command Buffer]: https://sites.google.com/a/chromium.org/dev/developers/design-documents/gpu-command-buffer
+
+## Renderer Process Code
+
+### `--enable-gpu-client-logging`
+
+If you are trying to track down a bug in a GPU client process (compositing,
+WebGL, Skia/Ganesh, Aura), then in a debug build you can use the
+`--enable-gpu-client-logging` flag, which will show every GL call sent to the
+GPU service process. (From the point of view of a GPU client, it's calling
+OpenGL ES functions - but the real driver calls are made in the GPU process.)
+
+```
+[4782:4782:1219/141706:INFO:gles2_implementation.cc(1026)] [.WebGLRenderingContext] glUseProgram(3)
+[4782:4782:1219/141706:INFO:gles2_implementation_impl_autogen.h(401)] [.WebGLRenderingContext] glGenBuffers(1, 0x7fffc9e1269c)
+[4782:4782:1219/141706:INFO:gles2_implementation_impl_autogen.h(416)]   0: 1
+[4782:4782:1219/141706:INFO:gles2_implementation_impl_autogen.h(23)] [.WebGLRenderingContext] glBindBuffer(GL_ARRAY_BUFFER, 1)
+[4782:4782:1219/141706:INFO:gles2_implementation.cc(1313)] [.WebGLRenderingContext] glBufferData(GL_ARRAY_BUFFER, 36, 0x7fd268580120, GL_STATIC_DRAW)
+[4782:4782:1219/141706:INFO:gles2_implementation.cc(2480)] [.WebGLRenderingContext] glEnableVertexAttribArray(0)
+[4782:4782:1219/141706:INFO:gles2_implementation.cc(1140)] [.WebGLRenderingContext] glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0)
+[4782:4782:1219/141706:INFO:gles2_implementation_impl_autogen.h(135)] [.WebGLRenderingContext] glClear(16640)
+[4782:4782:1219/141706:INFO:gles2_implementation.cc(2490)] [.WebGLRenderingContext] glDrawArrays(GL_TRIANGLES, 0, 3)
+```
+
+### Checking about:gpu
+
+The GPU process logs many errors and warnings. You can see these by navigating
+to `about:gpu`. Logs appear at the bottom of the page. You can also see them
+on standard output if Chromium is run from the command line on Linux/Mac.
+On Windows, you need debugging tools (like VS, WinDbg, etc.) to connect to the
+debug output stream.
+
+**Note:** If `about:gpu` is telling you that your GPU is disabled and
+hardware acceleration is unavailable, it might be a problem with your GPU being
+unsupported. To override this and turn on hardware acceleration anyway, you can
+use the `--ignore-gpu-blacklist` command line option when starting Chromium.
+
+### Breaking on GL Error
+
+In <code>[gles2_implementation.h]</code>, there is some code like this:
+
+```cpp
+// Set to 1 to have the client fail when a GL error is generated.
+// This helps find bugs in the renderer since the debugger stops on the error.
+#if DCHECK_IS_ON()
+#if 0
+#define GL_CLIENT_FAIL_GL_ERRORS
+#endif
+#endif
+```
+
+Change that `#if 0` to `#if 1`, build a debug build, then run in a debugger.
+The debugger will break when any renderer code sees a GL error, and you should
+be able to examine the call stack to find the issue.
+
+[gles2_implementation.h]: https://chromium.googlesource.com/chromium/src/+/master/gpu/command_buffer/client/gles2_implementation.h
+
+### Labeling your calls
+
+The output of all of the errors, warnings and debug logs are prefixed. You can
+set this prefix by calling `glPushGroupMarkerEXT`, `glPopGroupMarkerEXT` and
+`glInsertEventMarkerEXT`. `glPushGroupMarkerEXT` appends a string to the end of
+the current log prefix (think namespace in C++). `glPopGroupmarkerEXT` pops off
+the last string appended. `glInsertEventMarkerEXT` sets a suffix for the
+current string. Example:
+
+```cpp
+glPushGroupMarkerEXT(0, "Foo");        // -> log prefix = "Foo"
+glInsertEventMarkerEXT(0, "This");     // -> log prefix = "Foo.This"
+glInsertEventMarkerEXT(0, "That");     // -> log prefix = "Foo.That"
+glPushGroupMarkerEXT(0, "Bar");        // -> log prefix = "Foo.Bar"
+glInsertEventMarkerEXT(0, "Orange");   // -> log prefix = "Foo.Bar.Orange"
+glInsertEventMarkerEXT(0, "Banana");   // -> log prefix = "Foo.Bar.Banana"
+glPopGroupMarkerEXT();                 // -> log prefix = "Foo.That"
+```
+
+### Making a reduced test case.
+
+You can often make a simple OpenGL-ES-2.0-only C++ reduced test case that is
+relatively quick to compile and test, by adding tests to the `gl_tests` target.
+Those tests exist in `src/gpu/command_buffer/tests` and are made part of the
+build in `src/gpu/gpu.gyp`. Build with `ninja -C out/Debug gl_tests`. All the
+same command line options listed on this page will work with the `gl_tests`,
+plus `--gtest_filter=NameOfTest` to run a specific test. Note the `gl_tests`
+are not multi-process, so they probably won't help with race conditions, but
+they do go through most of the same code and are much easier to debug.
+
+### Debugging the renderer process
+
+Given that Chrome starts many renderer processes I find it's easier if I either
+have a remote webpage I can access or I make one locally and then use a local
+server to serve it like `python -m SimpleHTTPServer`. Then
+
+On Linux this works for me:
+
+*   `out/Debug/chromium --no-sandbox --renderer-cmd-prefix="xterm -e gdb
+    --args" http://localhost:8000/page-to-repro.html`
+
+On OSX this works for me:
+
+*   `out/Debug/Chromium.app/Contents/MacOSX/Chromium --no-sandbox
+    --renderer-cmd-prefix="xterm -e gdb --args"
+    http://localhost:8000/page-to-repro.html`
+
+On Windows I use `--renderer-startup-dialog` and then connect to the listed process.
+
+Note 1: On Linux and OSX I use `cgdb` instead of `gdb`.
+
+Note 2: GDB can take minutes to index symbol. To save time, you can precache
+that computation by running `build/gdb-add-index out/Debug/chrome`.
+
+## GPU Process Code
+
+### `--enable-gpu-service-logging`
+
+In a debug build, this will print all actual calls into the GL driver.
+
+```
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kEnableVertexAttribArray
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(905)] glEnableVertexAttribArray(0)
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kVertexAttribPointer
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(1573)] glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0)
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kClear
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(746)] glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(840)] glDepthMask(GL_TRUE)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(900)] glEnable(GL_DEPTH_TEST)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(1371)] glStencilMaskSeparate(GL_FRONT, 4294967295)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(1371)] glStencilMaskSeparate(GL_BACK, 4294967295)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(860)] glDisable(GL_STENCIL_TEST)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(860)] glDisable(GL_CULL_FACE)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(860)] glDisable(GL_SCISSOR_TEST)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(900)] glEnable(GL_BLEND)
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(721)] glClear(16640)
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kDrawArrays
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(870)] glDrawArrays(GL_TRIANGLES, 0, 3)
+```
+
+Note that GL calls into the driver are not currently prefixed (todo?). But, you
+can tell from the commands logged which command, from which context caused the
+following GL calls to be made.
+
+Also note that client resource IDs are virtual IDs, so calls into the real GL
+driver will not match (though some commands print the mapping). Examples:
+
+```
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kBindTexture
+[5497:5497:1219/142413:INFO:gles2_cmd_decoder.cc(837)] [.WebGLRenderingContext] glBindTexture: client_id = 2, service_id = 10
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(662)] glBindTexture(GL_TEXTURE_2D, 10)
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [0052064A367F0000]cmd: kBindBuffer
+[5497:5497:1219/142413:INFO:gles2_cmd_decoder.cc(837)] [0052064A367F0000] glBindBuffer: client_id = 2, service_id = 6
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(637)] glBindBuffer(GL_ARRAY_BUFFER, 6)
+[5497:5497:1219/142413:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kBindFramebuffer
+[5497:5497:1219/142413:INFO:gles2_cmd_decoder.cc(837)] [.WebGLRenderingContext] glBindFramebuffer: client_id = 1, service_id = 3
+[5497:5497:1219/142413:INFO:gl_bindings_autogen_gl.cc(652)] glBindFramebufferEXT(GL_FRAMEBUFFER, 3)
+```
+
+etc... so that you can see renderer process code would be using the client IDs
+where as the gpu process is using the service IDs. This is useful for matching
+up calls if you're dumping both client and service GL logs.
+
+### `--enable-gpu-debugging`
+
+In any build, this will call glGetError after each command
+
+### `--enable-gpu-command-logging`
+
+This will print the name of each GPU command before it is executed.
+
+```
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kBindBuffer
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kBufferData
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: SetToken
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kEnableVertexAttribArray
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kVertexAttribPointer
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kClear
+[5234:5234:1219/052139:ERROR:gles2_cmd_decoder.cc(3301)] [.WebGLRenderingContext]cmd: kDrawArrays
+```
+
+### Debugging in the GPU Process
+
+Given the multi-processness of chromium it can be hard to debug both sides.
+Turing on all the logging and having a small test case is useful. One minor
+suggestion, if you have some idea where the bug is happening a call to some
+obscure gl function like `glHint()` can give you a place to catch a command
+being processed in the GPU process (put a break point on
+`gpu::gles2::GLES2DecoderImpl::HandleHint`. Once in you can follow the commands
+after that. All of them go through `gpu::gles2::GLES2DecoderImpl::DoCommand`.
+
+To actually debug the GPU process:
+
+On Linux this works for me:
+
+*   `out/Debug/chromium --no-sandbox --gpu-launcher="xterm -e gdb --args"
+    http://localhost:8000/page-to-repro.html`
+
+On OSX this works for me:
+
+*   `out/Debug/Chromium.app/Contents/MacOSX/Chromium --no-sandbox
+    --gpu-launcher="xterm -e gdb --args"
+    http://localhost:8000/page-to-repro.html`
+
+On Windows I use `--gpu-startup-dialog` and then connect to the listed process.
+
+### `GPU PARSE ERROR`
+
+If you see this message in `about:gpu` or your console and you didn't cause it
+directly (by calling `glLoseContextCHROMIUM`) and it's something other than 5
+that means there's likely a bug. Please file an issue at <http://crbug.com/new>.
+
+## Debugging Performance
+
+If you have something to add here please add it. Most perf debugging is done
+using `about:tracing` (see [Trace Event Profiling] for details). Otherwise,
+be aware that, since the system is multi-process, calling:
+
+```
+start = GetTime()
+DoSomething()
+glFinish()
+end = GetTime
+printf("elapsedTime = %f\n", end - start);
+```
+
+**will not** give you meaningful results.
+
+[See Trace Event Profiling for details]: https://sites.google.com/a/chromium.org/dev/developers/how-tos/trace-event-profiling-tool
diff --git a/docs/gpu/gpu_testing.md b/docs/gpu/gpu_testing.md
new file mode 100644
index 0000000..9c401f3
--- /dev/null
+++ b/docs/gpu/gpu_testing.md
@@ -0,0 +1,571 @@
+# GPU Testing
+
+This set of pages documents the setup and operation of the GPU bots and try
+servers, which verify the correctness of Chrome's graphically accelerated
+rendering pipeline.
+
+[TOC]
+
+## Overview
+
+The GPU bots run a different set of tests than the majority of the Chromium
+test machines. The GPU bots specifically focus on tests which exercise the
+graphics processor, and whose results are likely to vary between graphics card
+vendors.
+
+Most of the tests on the GPU bots are run via the [Telemetry framework].
+Telemetry was originally conceived as a performance testing framework, but has
+proven valuable for correctness testing as well. Telemetry directs the browser
+to perform various operations, like page navigation and test execution, from
+external scripts written in Python. The GPU bots launch the full Chromium
+browser via Telemetry for the majority of the tests. Using the full browser to
+execute tests, rather than smaller test harnesses, has yielded several
+advantages: testing what is shipped, improved reliability, and improved
+performance.
+
+[Telemetry framework]: https://github.com/catapult-project/catapult/tree/master/telemetry
+
+A subset of the tests, called "pixel tests", grab screen snapshots of the web
+page in order to validate Chromium's rendering architecture end-to-end. Where
+necessary, GPU-specific results are maintained for these tests. Some of these
+tests verify just a few pixels, using handwritten code, in order to use the
+same validation for all brands of GPUs.
+
+The GPU bots use the Chrome infrastructure team's [recipe framework], and
+specifically the [`chromium`][recipes/chromium] and
+[`chromium_trybot`][recipes/chromium_trybot] recipes, to describe what tests to
+execute. Compared to the legacy master-side buildbot scripts, recipes make it
+easy to add new steps to the bots, change the bots' configuration, and run the
+tests locally in the same way that they are run on the bots. Additionally, the
+`chromium` and `chromium_trybot` recipes make it possible to send try jobs which
+add new steps to the bots. This single capability is a huge step forward from
+the previous configuration where new steps were added blindly, and could cause
+failures on the tryservers. For more details about the configuration of the
+bots, see the [GPU bot details].
+
+[recipe framework]: https://chromium.googlesource.com/external/github.com/luci/recipes-py/+/master/doc/user_guide.md
+[recipes/chromium]:        https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipes/chromium.py
+[recipes/chromium_trybot]: https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipes/chromium_trybot.py
+[GPU bot details]: gpu_testing_bot_details.md
+
+The physical hardware for the GPU bots lives in the Swarming pool\*. The
+Swarming infrastructure ([new docs][new-testing-infra], [older but currently
+more complete docs][isolated-testing-infra]) provides many benefits:
+
+*   Increased parallelism for the tests; all steps for a given tryjob or
+    waterfall build run in parallel.
+*   Simpler scaling: just add more hardware in order to get more capacity. No
+    manual configuration or distribution of hardware needed.
+*   Easier to run certain tests only on certain operating systems or types of
+    GPUs.
+*   Easier to add new operating systems or types of GPUs.
+*   Clearer description of the binary and data dependencies of the tests. If
+    they run successfully locally, they'll run successfully on the bots.
+
+(\* All but a few one-off GPU bots are in the swarming pool. The exceptions to
+the rule are described in the [GPU bot details].)
+
+The bots on the [chromium.gpu.fyi] waterfall are configured to always test
+top-of-tree ANGLE. This setup is done with a few lines of code in the
+[tools/build workspace]; search the code for "angle".
+
+These aspects of the bots are described in more detail below, and in linked
+pages. There is a [presentation][bots-presentation] which gives a brief
+overview of this documentation and links back to various portions.
+
+<!-- XXX: broken link -->
+[new-testing-infra]: https://github.com/luci/luci-py/wiki
+[isolated-testing-infra]: https://www.chromium.org/developers/testing/isolated-testing/infrastructure
+[chromium.gpu]: https://build.chromium.org/p/chromium.gpu/console
+[chromium.gpu.fyi]: https://build.chromium.org/p/chromium.gpu.fyi/console
+[tools/build workspace]: https://code.google.com/p/chromium/codesearch#chromium/build/scripts/slave/recipe_modules/chromium_tests/chromium_gpu_fyi.py
+[bots-presentation]: https://docs.google.com/presentation/d/1BC6T7pndSqPFnituR7ceG7fMY7WaGqYHhx5i9ECa8EI/edit?usp=sharing
+
+## Fleet Status
+
+Please see the [GPU Pixel Wrangling instructions] for links to dashboards
+showing the status of various bots in the GPU fleet.
+
+[GPU Pixel Wrangling instructions]: pixel_wrangling.md#Fleet-Status
+
+## Using the GPU Bots
+
+Most Chromium developers interact with the GPU bots in two ways:
+
+1.  Observing the bots on the waterfalls.
+2.  Sending try jobs to them.
+
+The GPU bots are grouped on the [chromium.gpu] and [chromium.gpu.fyi]
+waterfalls. Their current status can be easily observed there.
+
+To send try jobs, you must first upload your CL to the codereview server. Then,
+either clicking the "CQ dry run" link or running from the command line:
+
+```sh
+git cl try
+```
+
+Sends your job to the default set of try servers.
+
+The GPU tests are part of the default set for Chromium CLs, and are run as part
+of the following tryservers' jobs:
+
+*   [linux_chromium_rel_ng] on the [tryserver.chromium.linux] waterfall
+*   [mac_chromium_rel_ng]   on the [tryserver.chromium.mac]   waterfall
+*   [win_chromium_rel_ng]   on the [tryserver.chromium.win]   waterfall
+
+[linux_chromium_rel_ng]:    http://build.chromium.org/p/tryserver.chromium.linux/builders/linux_chromium_rel_ng?numbuilds=100
+[mac_chromium_rel_ng]:      http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_rel_ng?numbuilds=100
+[win_chromium_rel_ng]:      http://build.chromium.org/p/tryserver.chromium.win/builders/win_chromium_rel_ng?numbuilds=100
+[tryserver.chromium.linux]: http://build.chromium.org/p/tryserver.chromium.linux/waterfall?numbuilds=100
+[tryserver.chromium.mac]:   http://build.chromium.org/p/tryserver.chromium.mac/waterfall?numbuilds=100
+[tryserver.chromium.win]:   http://build.chromium.org/p/tryserver.chromium.win/waterfall?numbuilds=100
+
+Scan down through the steps looking for the text "GPU"; that identifies those
+tests run on the GPU bots. For each test the "trigger" step can be ignored; the
+step further down for the test of the same name contains the results.
+
+It's usually not necessary to explicitly send try jobs just for verifying GPU
+tests. If you want to, you must invoke "git cl try" separately for each
+tryserver master you want to reference, for example:
+
+```sh
+git cl try -b linux_chromium_rel_ng
+git cl try -b mac_chromium_rel_ng
+git cl try -b win_chromium_rel_ng
+```
+
+Alternatively, the Gerrit UI can be used to send a patch set to these try
+servers.
+
+Three optional tryservers are also available which run additional tests. As of
+this writing, they ran longer-running tests that can't run against all Chromium
+CLs due to lack of hardware capacity. They are added as part of the included
+tryservers for code changes to certain sub-directories.
+
+*   [linux_optional_gpu_tests_rel] on the [tryserver.chromium.linux] waterfall
+*   [mac_optional_gpu_tests_rel]   on the [tryserver.chromium.mac]   waterfall
+*   [win_optional_gpu_tests_rel]   on the [tryserver.chromium.win]   waterfall
+
+[linux_optional_gpu_tests_rel]: https://build.chromium.org/p/tryserver.chromium.linux/builders/linux_optional_gpu_tests_rel?numbuilds=200
+[mac_optional_gpu_tests_rel]:   https://build.chromium.org/p/tryserver.chromium.mac/builders/mac_optional_gpu_tests_rel?numbuilds=200
+[win_optional_gpu_tests_rel]:   https://build.chromium.org/p/tryserver.chromium.win/builders/win_optional_gpu_tests_rel?numbuilds=200
+
+Tryservers for the [ANGLE project] are also present on the
+[tryserver.chromium.angle] waterfall. These are invoked from the Gerrit user
+interface. They are configured similarly to the tryservers for regular Chromium
+patches, and run the same tests that are run on the [chromium.gpu.fyi]
+waterfall, in the same way (e.g., against ToT ANGLE).
+
+If you find it necessary to try patches against other sub-repositories than
+Chromium (`src/`) and ANGLE (`src/third_party/angle/`), please
+[file a bug](http://crbug.com/new) with component Internals\>GPU\>Testing.
+
+[ANGLE project]: https://chromium.googlesource.com/angle/angle/+/master/README.md
+[tryserver.chromium.angle]: https://build.chromium.org/p/tryserver.chromium.angle/waterfall
+[file a bug]: http://crbug.com/new
+
+## Running the GPU Tests Locally
+
+All of the GPU tests running on the bots can be run locally from a Chromium
+build. Many of the tests are simple executables:
+
+*   `angle_unittests`
+*   `content_gl_tests`
+*   `gl_tests`
+*   `gl_unittests`
+*   `tab_capture_end2end_tests`
+
+Some run only on the chromium.gpu.fyi waterfall, either because there isn't
+enough machine capacity at the moment, or because they're closed-source tests
+which aren't allowed to run on the regular Chromium waterfalls:
+
+*   `angle_deqp_gles2_tests`
+*   `angle_deqp_gles3_tests`
+*   `angle_end2end_tests`
+*   `audio_unittests`
+
+The remaining GPU tests are run via Telemetry.  In order to run them, just
+build the `chrome` target and then
+invoke `src/content/test/gpu/run_gpu_integration_test.py` with the appropriate
+argument. The tests this script can invoke are
+in `src/content/test/gpu/gpu_tests/`. For example:
+
+*   `run_gpu_integration_test.py context_lost --browser=release`
+*   `run_gpu_integration_test.py pixel --browser=release`
+*   `run_gpu_integration_test.py webgl_conformance --browser=release --webgl-conformance-version=1.0.2`
+*   `run_gpu_integration_test.py maps --browser=release`
+*   `run_gpu_integration_test.py screenshot_sync --browser=release`
+*   `run_gpu_integration_test.py trace_test --browser=release`
+
+**Note:** If you are on Linux and see this test harness exit immediately with
+`**Non zero exit code**`, it's probably because of some incompatible Python
+packages being installed. Please uninstall the `python-egenix-mxdatetime` and
+`python-logilab-common` packages in this case; see
+[Issue 716241](http://crbug.com/716241).
+
+You can also run a subset of tests with this harness:
+
+*   `run_gpu_integration_test.py webgl_conformance --browser=release
+    --test-filter=conformance_attribs`
+
+Figuring out the exact command line that was used to invoke the test on the
+bots can be a little tricky. The bots all\* run their tests via Swarming and
+isolates, meaning that the invocation of a step like `[trigger]
+webgl_conformance_tests on NVIDIA GPU...` will look like:
+
+*   `python -u
+    'E:\b\build\slave\Win7_Release__NVIDIA_\build\src\tools\swarming_client\swarming.py'
+    trigger --swarming https://chromium-swarm.appspot.com
+    --isolate-server https://isolateserver.appspot.com
+    --priority 25 --shards 1 --task-name 'webgl_conformance_tests on NVIDIA GPU...'`
+
+You can figure out the additional command line arguments that were passed to
+each test on the bots by examining the trigger step and searching for the
+argument separator (<code> -- </code>). For a recent invocation of
+`webgl_conformance_tests`, this looked like:
+
+*   `webgl_conformance --show-stdout '--browser=release' -v
+    '--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc'
+    '--isolated-script-test-output=${ISOLATED_OUTDIR}/output.json'`
+
+You can leave off the --isolated-script-test-output argument, so this would
+leave a full command line of:
+
+*   `run_gpu_integration_test.py
+    webgl_conformance --show-stdout '--browser=release' -v
+    '--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc'`
+
+The Maps test requires you to authenticate to cloud storage in order to access
+the Web Page Reply archive containing the test. See [Cloud Storage Credentials]
+for documentation on setting this up.
+
+[Cloud Storage Credentials]: gpu_testing_bot_details.md#Cloud-storage-credentials
+
+Pixel tests use reference images from cloud storage, bots pass
+`--upload-refimg-to-cloud-storage` argument, but to run locally you need to pass
+`--download-refimg-from-cloud-storage` argument, as well as other arguments bot
+uses, like `--refimg-cloud-storage-bucket` and `--os-type`.
+
+Sample command line for Android:
+
+*   `run_gpu_integration_test.py pixel --show-stdout --browser=android-chromium
+    -v --passthrough --extra-browser-args='--enable-logging=stderr
+    --js-flags=--expose-gc' --refimg-cloud-storage-bucket
+    chromium-gpu-archive/reference-images --os-type android
+    --download-refimg-from-cloud-storage`
+
+<!-- XXX: update this section; these isolates don't exist anymore -->
+You can find the isolates for the various tests in
+[src/chrome/](http://src.chromium.org/viewvc/chrome/trunk/src/chrome/):
+
+*   [angle_unittests.isolate](https://chromium.googlesource.com/chromium/src/+/master/chrome/angle_unittests.isolate)
+*   [content_gl_tests.isolate](https://chromium.googlesource.com/chromium/src/+/master/content/content_gl_tests.isolate)
+*   [gl_tests.isolate](https://chromium.googlesource.com/chromium/src/+/master/chrome/gl_tests.isolate)
+*   [gles2_conform_test.isolate](https://chromium.googlesource.com/chromium/src/+/master/chrome/gles2_conform_test.isolate)
+*   [tab_capture_end2end_tests.isolate](https://chromium.googlesource.com/chromium/src/+/master/chrome/tab_capture_end2end_tests.isolate)
+*   [telemetry_gpu_test.isolate](https://chromium.googlesource.com/chromium/src/+/master/chrome/telemetry_gpu_test.isolate)
+
+The isolates contain the full or partial command line for invoking the target.
+The complete command line for any test can be deduced from the contents of the
+isolate plus the stdio output from the test's run on the bot.
+
+Note that for the GN build, the isolates are simply described by build targets,
+and [gn_isolate_map.pyl] describes the mapping between isolate name and build
+target, as well as the command line used to invoke the isolate. Once all
+platforms have switched to GN, the .isolate files will be obsolete and be
+removed.
+
+(\* A few of the one-off GPU configurations on the chromium.gpu.fyi waterfall
+run their tests locally rather than via swarming, in order to decrease the
+number of physical machines needed.)
+
+[gn_isolate_map.pyl]: https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/gn_isolate_map.pyl
+
+## Running Binaries from the Bots Locally
+
+Any binary run remotely on a bot can also be run locally, assuming the local
+machine loosely matches the architecture and OS of the bot.
+
+The easiest way to do this is to find the ID of the swarming task and use
+"swarming.py reproduce" to re-run it:
+
+*   `./src/tools/swarming_client/swarming.py reproduce -S https://chromium-swarm.appspot.com [task ID]`
+
+The task ID can be found in the stdio for the "trigger" step for the test. For
+example, look at a recent build from the [Mac Release (Intel)] bot, and
+look at the `gl_unittests` step. You will see something like:
+
+[Mac Release (Intel)]: https://ci.chromium.org/buildbot/chromium.gpu/Mac%20Release%20%28Intel%29/
+
+```
+Triggered task: gl_unittests on Intel GPU on Mac/Mac-10.12.6/[TRUNCATED_ISOLATE_HASH]/Mac Release (Intel)/83664
+To collect results, use:
+  swarming.py collect -S https://chromium-swarm.appspot.com --json /var/folders/[PATH_TO_TEMP_FILE].json
+Or visit:
+  https://chromium-swarm.appspot.com/user/task/[TASK_ID]
+```
+
+There is a difference between the isolate's hash and Swarming's task ID. Make
+sure you use the task ID and not the isolate's hash.
+
+As of this writing, there seems to be a
+[bug](https://github.com/luci/luci-py/issues/250)
+when attempting to re-run the Telemetry based GPU tests in this way. For the
+time being, this can be worked around by instead downloading the contents of
+the isolate. To do so, look more deeply into the trigger step's log:
+
+*   <code>python -u
+    /b/build/slave/Mac_10_10_Release__Intel_/build/src/tools/swarming_client/swarming.py
+    trigger [...more args...] --tag data:[ISOLATE_HASH] [...more args...]
+    [ISOLATE_HASH] -- **[...TEST_ARGS...]**</code>
+
+As of this writing, the isolate hash appears twice in the command line. To
+download the isolate's contents into directory `foo` (note, this is in the
+"Help" section associated with the page for the isolate's task, but I'm not
+sure whether that's accessible only to Google employees or all members of the
+chromium.org organization):
+
+*   `python isolateserver.py download -I https://isolateserver.appspot.com
+    --namespace default-gzip -s [ISOLATE_HASH] --target foo`
+
+`isolateserver.py` will tell you the approximate command line to use. You
+should concatenate the `TEST_ARGS` highlighted in red above with
+`isolateserver.py`'s recommendation. The `ISOLATED_OUTDIR` variable can be
+safely replaced with `/tmp`.
+
+Note that `isolateserver.py` downloads a large number of files (everything
+needed to run the test) and may take a while. There is a way to use
+`run_isolated.py` to achieve the same result, but as of this writing, there
+were problems doing so, so this procedure is not documented at this time.
+
+Before attempting to download an isolate, you must ensure you have permission
+to access the isolate server. Full instructions can be [found
+here][isolate-server-credentials]. For most cases, you can simply run:
+
+*   `./src/tools/swarming_client/auth.py login
+    --service=https://isolateserver.appspot.com`
+
+The above link requires that you log in with your @google.com credentials. It's
+not known at the present time whether this works with @chromium.org accounts.
+Email kbr@ if you try this and find it doesn't work.
+
+[isolate-server-credentials]: gpu_testing_bot_details.md#Isolate-server-credentials
+
+## Running Locally Built Binaries on the GPU Bots
+
+See the [Swarming documentation] for instructions on how to upload your binaries to the isolate server and trigger execution on Swarming.
+
+[Swarming documentation]: https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Run-a-test-built-locally-on-Swarming
+
+## Adding New Tests to the GPU Bots
+
+The goal of the GPU bots is to avoid regressions in Chrome's rendering stack.
+To that end, let's add as many tests as possible that will help catch
+regressions in the product. If you see a crazy bug in Chrome's rendering which
+would be easy to catch with a pixel test running in Chrome and hard to catch in
+any of the other test harnesses, please, invest the time to add a test!
+
+There are a couple of different ways to add new tests to the bots:
+
+1.  Adding a new test to one of the existing harnesses.
+2.  Adding an entire new test step to the bots.
+
+### Adding a new test to one of the existing test harnesses
+
+Adding new tests to the GTest-based harnesses is straightforward and
+essentially requires no explanation.
+
+As of this writing it isn't as easy as desired to add a new test to one of the
+Telemetry based harnesses. See [Issue 352807](http://crbug.com/352807). Let's
+collectively work to address that issue. It would be great to reduce the number
+of steps on the GPU bots, or at least to avoid significantly increasing the
+number of steps on the bots. The WebGL conformance tests should probably remain
+a separate step, but some of the smaller Telemetry based tests
+(`context_lost_tests`, `memory_test`, etc.) should probably be combined into a
+single step.
+
+If you are adding a new test to one of the existing tests (e.g., `pixel_test`),
+all you need to do is make sure that your new test runs correctly via isolates.
+See the documentation from the GPU bot details on [adding new isolated
+tests][new-isolates] for the `GYP_DEFINES` and authentication needed to upload
+isolates to the isolate server. Most likely the new test will be Telemetry
+based, and included in the `telemetry_gpu_test_run` isolate. You can then
+invoke it via:
+
+*   `./src/tools/swarming_client/run_isolated.py -s [HASH]
+    -I https://isolateserver.appspot.com -- [TEST_NAME] [TEST_ARGUMENTS]`
+
+[new-isolates]: gpu_testing_bot_details.md#Adding-a-new-isolated-test-to-the-bots
+
+o## Adding new steps to the GPU Bots
+
+The tests that are run by the GPU bots are described by a couple of JSON files
+in the Chromium workspace:
+
+*   [`chromium.gpu.json`](https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/chromium.gpu.json)
+*   [`chromium.gpu.fyi.json`](https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/chromium.gpu.fyi.json)
+
+These files are autogenerated by the following script:
+
+*   [`generate_buildbot_json.py`](https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/generate_buildbot_json.py)
+
+This script is completely self-contained and should hopefully be
+self-explanatory. The JSON files are parsed by the chromium and chromium_trybot
+recipes, and describe two types of tests:
+
+*   GTests: those which use the Googletest and Chromium's `base/test/launcher/`
+    frameworks.
+*   Telemetry based tests: those which are built on the Telemetry framework and
+    launch the entire browser.
+
+A prerequisite of adding a new test to the bots is that that test [run via
+isolates][new-isolates]. Once that is done, modify `generate_buildbot_json.py` to add the
+test to the appropriate set of bots. Be careful when adding large new test
+steps to all of the bots, because the GPU bots are a limited resource and do
+not currently have the capacity to absorb large new test suites. It is safer to
+get new tests running on the chromium.gpu.fyi waterfall first, and expand from
+there to the chromium.gpu waterfall (which will also make them run against
+every Chromium CL by virtue of the `linux_chromium_rel_ng`,
+`mac_chromium_rel_ng` and `win_chromium_rel_ng` tryservers' mirroring of the
+bots on this waterfall – so be careful!).
+
+Tryjobs which add new test steps to the chromium.gpu.json file will run those
+new steps during the tryjob, which helps ensure that the new test won't break
+once it starts running on the waterfall.
+
+Tryjobs which modify chromium.gpu.fyi.json can be sent to the
+`win_optional_gpu_tests_rel`, `mac_optional_gpu_tests_rel` and
+`linux_optional_gpu_tests_rel` tryservers to help ensure that they won't
+break the FYI bots.
+
+## Updating and Adding New Pixel Tests to the GPU Bots
+
+Adding new pixel tests which require reference images is a slightly more
+complex process than adding other kinds of tests which can validate their own
+correctness. There are a few reasons for this.
+
+*   Reference image based pixel tests require different golden images for
+    different combinations of operating system, GPU, driver version, OS
+    version, and occasionally other variables.
+*   The reference images must be generated by the main waterfall. The try
+    servers are not allowed to produce new reference images, only consume them.
+    The reason for this is that a patch sent to the try servers might cause an
+    incorrect reference image to be generated. For this reason, the main
+    waterfall bots upload reference images to cloud storage, and the try
+    servers download them and verify their results against them.
+*   The try servers will fail if they run a pixel test requiring a reference
+    image that doesn't exist in cloud storage. This is deliberate, but needs
+    more thought; see [Issue 349262](http://crbug.com/349262).
+
+If a reference image based pixel test's result is going to change because of a
+change in ANGLE or Blink (for example), updating the reference images is a
+slightly tricky process. Here's how to do it:
+
+*   Mark the pixel test as failing in the [pixel tests]' [test expectations]
+*   Commit the change to ANGLE, Blink, etc. which will change the test's
+    results
+*   Note that without the failure expectation, this commit would turn some bots
+    red; a Blink change will turn the GPU bots on the chromium.webkit waterfall
+    red, and an ANGLE change will turn the chromium.gpu.fyi bots red
+*   Wait for Blink/ANGLE/etc. to roll
+*   Commit a change incrementing the revision number associated with the test
+    in the [test pages]
+*   Commit a second change removing the failure expectation, once all of the
+    bots on the main waterfall have generated new reference images. This change
+    should go through the commit queue cleanly.
+
+[pixel tests]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/pixel_test_pages.py
+[test expectations]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/pixel_expectations.py
+[test pages]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/pixel_test_pages.py
+
+When adding a brand new pixel test that uses a reference image, the steps are
+similar, but simpler:
+
+*   Mark the test as failing in the same commit which introduces the new test
+*   Wait for the reference images to be produced by all of the GPU bots on the
+    waterfalls (see [chromium-gpu-archive/reference-images])
+*   Commit a change un-marking the test as failing
+
+When making a Chromium-side change which changes the pixel tests' results:
+
+*   In your CL, both mark the pixel test as failing in the pixel test's test
+    expectations and increment the test's version number in the page set (see
+    above)
+*   After your CL lands, land another CL removing the failure expectations. If
+    this second CL goes through the commit queue cleanly, you know reference
+    images were generated properly.
+
+In general, when adding a new pixel test, it's better to spot check a few
+pixels in the rendered image rather than using a reference image per platform.
+The [GPU rasterization test] is a good example of a recently added test which
+performs such spot checks.
+
+[cloud storage bucket]: https://console.developers.google.com/storage/chromium-gpu-archive/reference-images
+<!-- XXX: old link -->
+[GPU rasterization test]: http://src.chromium.org/viewvc/chrome/trunk/src/content/test/gpu/gpu_tests/gpu_rasterization.py
+
+## Stamping out Flakiness
+
+It's critically important to aggressively investigate and eliminate the root
+cause of any flakiness seen on the GPU bots. The bots have been known to run
+reliably for days at a time, and any flaky failures that are tolerated on the
+bots translate directly into instability of the browser experienced by
+customers. Critical bugs in subsystems like WebGL, affecting high-profile
+products like Google Maps, have escaped notice in the past because the bots
+were unreliable. After much re-work, the GPU bots are now among the most
+reliable automated test machines in the Chromium project. Let's keep them that
+way.
+
+Flakiness affecting the GPU tests can come in from highly unexpected sources.
+Here are some examples:
+
+*   Intermittent pixel_test failures on Linux where the captured pixels were
+    black, caused by the Display Power Management System (DPMS) kicking in.
+    Disabled the X server's built-in screen saver on the GPU bots in response.
+*   GNOME dbus-related deadlocks causing intermittent timeouts ([Issue
+    309093](http://crbug.com/309093) and related bugs).
+*   Windows Audio system changes causing intermittent assertion failures in the
+    browser ([Issue 310838](http://crbug.com/310838)).
+*   Enabling assertion failures in the C++ standard library on Linux causing
+    random assertion failures ([Issue 328249](http://crbug.com/328249)).
+*   V8 bugs causing random crashes of the Maps pixel test (V8 issues
+    [3022](https://code.google.com/p/v8/issues/detail?id=3022),
+    [3174](https://code.google.com/p/v8/issues/detail?id=3174)).
+*   TLS changes causing random browser process crashes ([Issue
+    264406](http://crbug.com/264406)).
+*   Isolated test execution flakiness caused by failures to reliably clean up
+    temporary directories ([Issue 340415](http://crbug.com/340415)).
+*   The Telemetry-based WebGL conformance suite caught a bug in the memory
+    allocator on Android not caught by any other bot ([Issue
+    347919](http://crbug.com/347919)).
+*   context_lost test failures caused by the compositor's retry logic ([Issue
+    356453](http://crbug.com/356453)).
+*   Multiple bugs in Chromium's support for lost contexts causing flakiness of
+    the context_lost tests ([Issue 365904](http://crbug.com/365904)).
+*   Maps test timeouts caused by Content Security Policy changes in Blink
+    ([Issue 395914](http://crbug.com/395914)).
+*   Weak pointer assertion failures in various webgl\_conformance\_tests caused
+    by changes to the media pipeline ([Issue 399417](http://crbug.com/399417)).
+*   A change to a default WebSocket timeout in Telemetry causing intermittent
+    failures to run all WebGL conformance tests on the Mac bots ([Issue
+    403981](http://crbug.com/403981)).
+*   Chrome leaking suspended sub-processes on Windows, apparently a preexisting
+    race condition that suddenly showed up ([Issue
+    424024](http://crbug.com/424024)).
+*   Changes to Chrome's cross-context synchronization primitives causing the
+    wrong tiles to be rendered ([Issue 584381](http://crbug.com/584381)).
+*   A bug in V8's handling of array literals causing flaky failures of
+    texture-related WebGL 2.0 tests ([Issue 606021](http://crbug.com/606021)).
+*   Assertion failures in sync point management related to lost contexts that
+    exposed a real correctness bug ([Issue 606112](http://crbug.com/606112)).
+*   A bug in glibc's `sem_post`/`sem_wait` primitives breaking V8's parallel
+    garbage collection ([Issue 609249](http://crbug.com/609249)).
+
+If you notice flaky test failures either on the GPU waterfalls or try servers,
+please file bugs right away with the component Internals>GPU>Testing and
+include links to the failing builds and copies of the logs, since the logs
+expire after a few days. [GPU pixel wranglers] should give the highest priority
+to eliminating flakiness on the tree.
+
+[GPU pixel wranglers]: pixel_wrangling.md
diff --git a/docs/gpu/gpu_testing_bot_details.md b/docs/gpu/gpu_testing_bot_details.md
new file mode 100644
index 0000000..a0638514
--- /dev/null
+++ b/docs/gpu/gpu_testing_bot_details.md
@@ -0,0 +1,539 @@
+# GPU Bot Details
+
+This PAGE describes in detail how the GPU bots are set up, which files affect
+their configuration, and how to both modify their behavior and add new bots.
+
+[TOC]
+
+## Overview of the GPU bots' setup
+
+Chromium's GPU bots, compared to the majority of the project's test machines,
+are physical pieces of hardware. When end users run the Chrome browser, they
+are almost surely running it on a physical piece of hardware with a real
+graphics processor. There are some portions of the code base which simply can
+not be exercised by running the browser in a virtual machine, or on a software
+implementation of the underlying graphics libraries. The GPU bots were
+developed and deployed in order to cover these code paths, and avoid
+regressions that are otherwise inevitable in a project the size of the Chromium
+browser.
+
+The GPU bots are utilized on the [chromium.gpu] and [chromium.gpu.fyi]
+waterfalls, and various tryservers, as described in [Using the GPU Bots].
+
+[chromium.gpu]: https://build.chromium.org/p/chromium.gpu/console
+[chromium.gpu.fyi]: https://build.chromium.org/p/chromium.gpu.fyi/console
+[Using the GPU Bots]: gpu_testing.md#Using-the-GPU-Bots
+
+The vast majority of the hardware for the bots lives in the Chrome-GPU Swarming
+pool. The waterfall bots are simply virtual machines which spawn Swarming tasks
+with the appropriate tags to get them to run on the desired GPU and operating
+system type. So, for example, the [Win10 Release (NVIDIA)] bot is actually a
+virtual machine which spawns all of its jobs with the Swarming parameters:
+
+[Win10 Release (NVIDIA)]: https://ci.chromium.org/buildbot/chromium.gpu/Win10%20Release%20%28NVIDIA%29/?limit=200
+
+```json
+{
+    "gpu": "10de:1cb3-23.21.13.8816",
+    "os": "Windows-10",
+    "pool": "Chrome-GPU"
+}
+```
+
+Since the GPUs in the Swarming pool are mostly homogeneous, this is sufficient
+to target the pool of Windows 10-like NVIDIA machines. (There are a few Windows
+7-like NVIDIA bots in the pool, which necessitates the OS specifier.)
+
+Details about the bots can be found on [chromium-swarm.appspot.com] and by
+using `src/tools/swarming_client/swarming.py`, for example `swarming.py bots`.
+If you are authenticated with @google.com credentials you will be able to make
+queries of the bots and see, for example, which GPUs are available.
+
+[chromium-swarm.appspot.com]: https://chromium-swarm.appspot.com/
+
+The waterfall bots run tests on a single GPU type in order to make it easier to
+see regressions or flakiness that affect only a certain type of GPU.
+
+The tryservers like `win_chromium_rel_ng` which include GPU tests, on the other
+hand, run tests on more than one GPU type. As of this writing, the Windows
+tryservers ran tests on NVIDIA and AMD GPUs; the Mac tryservers ran tests on
+Intel and NVIDIA GPUs. The way these tryservers' tests are specified is simply
+by *mirroring* how one or more waterfall bots work. This is an inherent
+property of the [`chromium_trybot` recipe][chromium_trybot.py], which was designed to eliminate
+differences in behavior between the tryservers and waterfall bots. Since the
+tryservers mirror waterfall bots, if the waterfall bot is working, the
+tryserver must almost inherently be working as well.
+
+[chromium_trybot.py]: https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipes/chromium_trybot.py
+
+There are a few one-off GPU configurations on the waterfall where the tests are
+run locally on physical hardware, rather than via Swarming. A few examples are:
+
+<!-- XXX: update this list -->
+*   [Mac Pro Release (AMD)](https://luci-milo.appspot.com/buildbot/chromium.gpu.fyi/Mac%20Pro%20Release%20%28AMD%29/)
+*   [Mac Pro Debug (AMD)](https://luci-milo.appspot.com/buildbot/chromium.gpu.fyi/Mac%20Pro%20Debug%20%28AMD%29/)
+*   [Linux Release (Intel HD 630)](https://luci-milo.appspot.com/buildbot/chromium.gpu.fyi/Linux%20Release%20%28Intel%20HD%20630%29/)
+*   [Linux Release (AMD R7 240)](https://luci-milo.appspot.com/buildbot/chromium.gpu.fyi/Linux%20Release%20%28AMD%20R7%20240%29/)
+
+There are a couple of reasons to continue to support running tests on a
+specific machine: it might be too expensive to deploy the required multiple
+copies of said hardware, or the configuration might not be reliable enough to
+begin scaling it up.
+
+## Adding a new isolated test to the bots
+
+Adding a new test step to the bots requires that the test run via an isolate.
+Isolates describe both the binary and data dependencies of an executable, and
+are the underpinning of how the Swarming system works. See the [LUCI wiki] for
+background on Isolates and Swarming.
+
+<!-- XXX: broken link -->
+[LUCI wiki]: https://github.com/luci/luci-py/wiki
+
+### Adding a new isolate
+
+1.  Define your target using the `template("test")` template in
+    [`src/testing/test.gni`][testing/test.gni]. See `test("gl_tests")` in
+    [`src/gpu/BUILD.gn`][gpu/BUILD.gn] for an example. For a more complex
+    example which invokes a series of scripts which finally launches the
+    browser, see [`src/chrome/telemetry_gpu_test.isolate`][telemetry_gpu_test.isolate].
+2.  Add an entry to [`src/testing/buildbot/gn_isolate_map.pyl`][gn_isolate_map.pyl] that refers to
+    your target. Find a similar target to yours in order to determine the
+    `type`. The type is referenced in [`src/tools/mb/mb_config.pyl`][mb_config.pyl].
+
+[testing/test.gni]:           https://chromium.googlesource.com/chromium/src/+/master/testing/test.gni
+[gpu/BUILD.gn]:               https://chromium.googlesource.com/chromium/src/+/master/gpu/BUILD.gn
+<!-- XXX: broken link -->
+[telemetry_gpu_test.isolate]: https://chromium.googlesource.com/chromium/src/+/master/chrome/telemetry_gpu_test.isolate
+[gn_isolate_map.pyl]:         https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/gn_isolate_map.pyl
+[mb_config.pyl]:              https://chromium.googlesource.com/chromium/src/+/master/tools/mb/mb_config.pyl
+
+At this point you can build and upload your isolate to the isolate server.
+
+See [Isolated Testing for SWEs] for the most up-to-date instructions. These
+instructions are a copy which show how to run an isolate that's been uploaded
+to the isolate server on your local machine rather than on Swarming.
+
+[Isolated Testing for SWEs]: https://www.chromium.org/developers/testing/isolated-testing/for-swes
+
+If `cd`'d into `src/`:
+
+1.  `./tools/mb/mb.py isolate //out/Release [target name]`
+    *   For example: `./tools/mb/mb.py isolate //out/Release angle_end2end_tests`
+1.  `python tools/swarming_client/isolate.py batcharchive -I https://isolateserver.appspot.com out/Release/[target name].isolated.gen.json`
+    *   For example: `python tools/swarming_client/isolate.py batcharchive -I https://isolateserver.appspot.com out/Release/angle_end2end_tests.isolated.gen.json`
+1.  This will write a hash to stdout. You can run it via:
+    `python tools/swarming_client/run_isolated.py -I https://isolateserver.appspot.com -s [HASH] -- [any additional args for the isolate]`
+
+See the section below on [isolate server credentials](#Isolate-server-credentials).
+
+### Adding your new isolate to the tests that are run on the bots
+
+See [Adding new steps to the GPU bots] for details on this process.
+
+[Adding new steps to the GPU bots]: gpu_testing.md#Adding-new-steps-to-the-GPU-Bots
+
+## Relevant files that control the operation of the GPU bots
+
+In the [tools/build] workspace:
+
+*   [masters/master.chromium.gpu] and [masters/master.chromium.gpu.fyi]:
+    *   builders.pyl in these two directories defines the bots that show up on
+        the waterfall. If you are adding a new bot, you need to add it to
+        builders.pyl and use go/bug-a-trooper to request a restart of either
+        master.chromium.gpu or master.chromium.gpu.fyi.
+    *   Only changes under masters/ require a waterfall restart. All other
+        changes – for example, to scripts/slave/ in this workspace, or the
+        Chromium workspace – do not require a master restart (and go live the
+        minute they are committed).
+*   `scripts/slave/recipe_modules/chromium_tests/`:
+    *   <code>[chromium_gpu.py]</code> and
+        <code>[chromium_gpu_fyi.py]</code> define the following for
+        each builder and tester:
+        *   How the workspace is checked out (e.g., this is where top-of-tree
+            ANGLE is specified)
+        *   The build configuration (e.g., this is where 32-bit vs. 64-bit is
+            specified)
+        *   Various gclient defines (like compiling in the hardware-accelerated
+            video codecs, and enabling compilation of certain tests, like the
+            dEQP tests, that can't be built on all of the Chromium builders)
+        *   Note that the GN configuration of the bots is also controlled by
+            <code>[mb_config.pyl]</code> in the Chromium workspace; see below.
+    *   <code>[trybots.py]</code> defines how try bots *mirror* one or more
+        waterfall bots.
+        *   The concept of try bots mirroring waterfall bots ensures there are
+            no differences in behavior between the waterfall bots and the try
+            bots. This helps ensure that a CL will not pass the commit queue
+            and then break on the waterfall.
+        *   This file defines the behavior of the following GPU-related try
+            bots:
+            *   `linux_chromium_rel_ng`, `mac_chromium_rel_ng`, and
+                `win_chromium_rel_ng`, which run against every Chromium CL, and
+                which mirror the behavior of bots on the chromium.gpu
+                waterfall.
+            *   The ANGLE try bots, which run against ANGLE CLs, and mirror the
+                behavior of the chromium.gpu.fyi waterfall (including using
+                top-of-tree ANGLE, and running additional tests not run by the
+                regular Chromium try bots)
+           *   The optional GPU try servers `linux_optional_gpu_tests_rel`,
+               `mac_optional_gpu_tests_rel` and
+               `win_optional_gpu_tests_rel`, which are triggered manually and
+               run some tests which can't be run on the regular Chromium try
+               servers mainly due to lack of hardware capacity.
+
+[tools/build]:                     https://chromium.googlesource.com/chromium/tools/build/
+[masters/master.chromium.gpu]:     https://chromium.googlesource.com/chromium/tools/build/+/master/masters/master.chromium.gpu/
+[masters/master.chromium.gpu.fyi]: https://chromium.googlesource.com/chromium/tools/build/+/master/masters/master.chromium.gpu.fyi/
+[chromium_gpu.py]:                 https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipe_modules/chromium_tests/chromium_gpu.py
+[chromium_gpu_fyi.py]:             https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipe_modules/chromium_tests/chromium_gpu_fyi.py
+[trybots.py]:                      https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/slave/recipe_modules/chromium_tests/trybots.py
+
+In the [chromium/src] workspace:
+
+*   [src/testing/buildbot]:
+    *   <code>[chromium.gpu.json]</code> and
+        <code>[chromium.gpu.fyi.json]</code> define which steps are run on
+        which bots. These files are autogenerated. Don't modify them directly!
+    *   <code>[gn_isolate_map.pyl]</code> defines all of the isolates' behavior in the GN
+        build.
+*   [`src/tools/mb/mb_config.pyl`][mb_config.pyl]
+    *   Defines the GN arguments for all of the bots.
+*   [`src/content/test/gpu/generate_buildbot_json.py`][generate_buildbot_json.py]
+    *   The generator script for `chromium.gpu.json` and
+        `chromium.gpu.fyi.json`. It defines on which GPUs various tests run.
+    *   It's completely self-contained and should hopefully be fairly
+        comprehensible.
+    *   When modifying this script, don't forget to also run it, to regenerate
+        the JSON files.
+    *   See [Adding new steps to the GPU bots] for more details.
+
+[chromium/src]:              https://chromium.googlesource.com/chromium/src/
+[src/testing/buildbot]:      https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot
+[chromium.gpu.json]:         https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/chromium.gpu.json
+[chromium.gpu.fyi.json]:     https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/chromium.gpu.fyi.json
+[gn_isolate_map.pyl]:        https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/gn_isolate_map.pyl
+[mb_config.pyl]:             https://chromium.googlesource.com/chromium/src/+/master/tools/mb/mb_config.pyl
+[generate_buildbot_json.py]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/generate_buildbot_json.py
+
+In the [infradata/config] workspace (Google internal only, sorry):
+
+*   [configs/chromium-swarm/bots.cfg]
+    *   Defines a `Chrome-GPU` Swarming pool which contains most of the
+        specialized hardware: as of this writing, the Windows and Linux NVIDIA
+        bots, the Windows AMD bots, and the MacBook Pros with NVIDIA and AMD
+        GPUs. New GPU hardware should be added to this pool.
+
+[infradata/config]:                https://chrome-internal.googlesource.com/infradata/config
+[configs/chromium-swarm/bots.cfg]: https://chrome-internal.googlesource.com/infradata/config/+/master/configs/chromium-swarm/bots.cfg
+
+## Walkthroughs of various maintenance scenarios
+
+This section describes various common scenarios that might arise when
+maintaining the GPU bots, and how they'd be addressed.
+
+### How to add a new test or an entire new step to the bots
+
+This is described in [Adding new tests to the GPU bots].
+
+[Adding new tests to the GPU bots]: https://www.chromium.org/developers/testing/gpu-testing/#TOC-Adding-New-Tests-to-the-GPU-Bots
+
+### How to add a new bot
+
+The first decision point when adding a new GPU bot is whether it is a one-off
+piece of hardware, or one which is expected to be scaled up at some point. If
+it's a one-off piece of hardware, it can be added to the chromium.gpu.fyi
+waterfall as a non-swarmed test machine. If it's expected to be scaled up at
+some point, the hardware should be added to the swarming pool. These two
+scenarios are described in more detail below.
+
+#### How to add a new, non-swarmed, physical bot to the chromium.gpu.fyi waterfall
+
+1.  Work with the Chrome Infrastructure Labs team to get the hardware deployed
+    so it can talk to the chromium.gpu.fyi master.
+1.  Create a CL in the build workspace which:
+    1.  Add the new machine to
+        [`masters/master.chromium.gpu.fyi/builders.pyl`][master.chromium.gpu.fyi/builders.pyl].
+    1.  Add the new machine to
+        [`scripts/slave/recipe_modules/chromium_tests/chromium_gpu_fyi.py`][chromium_gpu_fyi.py].
+        Set the `enable_swarming` property to `False`.
+    1.  Retrain recipe expectations
+        (`scripts/slave/recipes.py --use-bootstrap test train`) and add the
+        newly created JSON file(s) corresponding to the new machines to your CL.
+1.  Create a CL in the Chromium workspace to:
+    1.  Add the new machine to
+        [`src/content/test/gpu/generate_buildbot_json.py`][generate_buildbot_json.py].
+        Make sure to set the `swarming` property to `False`.
+    1.  If the machine runs GN, add a description to
+        [`src/tools/mb/mb_config.pyl`][mb_config.pyl].
+1.  Once the build workspace CL lands, use go/bug-a-trooper (or contact kbr@)
+    to schedule a restart of the chromium.gpu.fyi waterfall. This is only
+    necessary when modifying files under the masters/ directory. A reboot of
+    the machine may be needed once the waterfall has been restarted in order to
+    make it connect properly.
+1.  The CLs from (2) and (3) can land in either order, though it is preferable
+    to land the Chromium-side CL first so that the machine knows what tests to
+    run the first time it boots up.
+
+[master.chromium.gpu.fyi/builders.pyl]: https://chromium.googlesource.com/chromium/tools/build/+/master/masters/master.chromium.gpu.fyi/builders.pyl
+
+#### How to add a new swarmed bot to the chromium.gpu.fyi waterfall
+
+When deploying a new GPU configuration, it should be added to the
+chromium.gpu.fyi waterfall first. The chromium.gpu waterfall should be reserved
+for those GPUs which are tested on the commit queue. (Some of the bots violate
+this rule – namely, the Debug bots – though we should strive to eliminate these
+differences.) Once the new configuration is ready to be fully deployed on
+tryservers, bots can be added to the chromium.gpu waterfall, and the tryservers
+changed to mirror them.
+
+In order to add Release and Debug waterfall bots for a new configuration,
+experience has shown that at least 4 physical machines are needed in the
+swarming pool. The reason is that the tests all run in parallel on the Swarming
+cluster, so the load induced on the swarming bots is higher than it would be
+for a non-swarmed bot that executes its tests serially.
+
+With these prerequisites, these are the steps to add a new swarmed bot.
+(Actually, pair of bots -- Release and Debug.)
+
+1.  Work with the Chrome Infrastructure Labs team to get the (minimum 4)
+    physical machines added to the Swarming pool. Use
+    [chromium-swarm.appspot.com] or `src/tools/swarming_client/swarming.py bots`
+    to determine the PCI IDs of the GPUs in the bots. (These instructions will
+    need to be updated for Android bots which don't have PCI buses.)
+    1.  Make sure to add these new machines to the Chrome-GPU Swarming pool by
+        creating a CL against [`configs/chromium-swarm/bots.cfg`][bots.cfg] in
+        the [infradata/config] workspace.
+1.  File a Chrome Infrastructure Labs ticket requesting 2 virtual machines for
+    the testers. These need to match the OS of the physical machines and
+    builders because of limitations in the scripts which transfer builds from
+    the builder to the tester; see [this feature
+    request](http://crbug.com/581953). For example, if you're adding a "Windows
+    7 CoolNewGPUType" tester, you'll need 2 Windows VMs.
+1.  Once the VMs are ready, create a CL in the build workspace which:
+    1.  Adds the new VMs as the Release and Debug bots in
+        [`master.chromium.gpu.fyi/builders.pyl`][master.chromium.gpu.fyi/builders.pyl].
+    1.  Adds the new VMs to [`chromium_gpu_fyi.py`][chromium_gpu_fyi.py]. Make
+        sure to set the `enable_swarming` and `serialize_tests` properties to
+        `True`. Double-check the `parent_buildername` property for each. It
+        must match the Release/Debug flavor of the builder.
+    1.  Retrain recipe expectations
+        (`scripts/slave/recipes.py --use-bootstrap test train`) and add the
+        newly created JSON file(s) corresponding to the new machines to your CL.
+1.  Create a CL in the Chromium workspace which:
+    1.  Adds the new machine to
+        `src/content/test/gpu/generate_buildbot_json.py`.
+        1.  The swarming dimensions are crucial. These must match the GPU and
+            OS type of the physical hardware in the Swarming pool. This is what
+            causes the VMs to spawn their tests on the correct hardware. Make
+            sure to use the Chrome-GPU pool, and that the new machines were
+            specifically added to that pool.
+        1.  Make sure to set the `swarming` property to `True` for both the
+            Release and Debug bots.
+        1.  Make triply sure that there are no collisions between the new
+            hardware you're adding and hardware already in the Swarming pool.
+            For example, it used to be the case that all of the Windows NVIDIA
+            bots ran the same OS version. Later, the Windows 8 flavor bots were
+            added. In order to avoid accidentally running tests on Windows 8
+            when Windows 7 was intended, the OS in the swarming dimensions of
+            the Win7 bots had to be changed from `win` to
+            `Windows-2008ServerR2-SP1` (the Win7-like flavor running in our
+            data center). Similarly, the Win8 bots had to have a very precise
+            OS description (`Windows-2012ServerR2-SP0`).
+    1.  If the machine runs GN, adds a description to
+        [`src/tools/mb/mb_config.pyl`][mb_config.pyl].
+1.  Once the tools/build CL lands, use go/bug-a-trooper (or contact kbr@) to
+    schedule a restart of the chromium.gpu.fyi waterfall. This is only
+    necessary when modifying files under the masters/ directory. A reboot of
+    the VMs may be needed once the waterfall has been restarted in order to
+    make them connect properly.
+1.  The CLs from (3) and (4) can land in either order, though it is preferable
+    to land the Chromium-side CL first so that the machine knows what tests to
+    run the first time it boots up.
+
+[bots.cfg]: https://chrome-internal.googlesource.com/infradata/config/+/master/configs/chromium-swarm/bots.cfg
+[infradata/config]: https://chrome-internal.googlesource.com/infradata/config/
+
+#### How to start running tests on a new GPU type on an existing try bot
+
+Let's say that you want to cause the `win_chromium_rel_ng` try bot to run tests
+on CoolNewGPUType in addition to the types it currently runs (as of this
+writing, NVIDIA and AMD). To do this:
+
+1.  Make sure there is enough hardware capacity. Unfortunately, tools to report
+    utilization of the Swarming pool are still being developed, but a
+    back-of-the-envelope estimate is that you will need a minimum of 30
+    machines in the Swarming pool to run the current set of GPU tests on the
+    tryservers. We estimate that 90 machines will be needed in order to
+    additionally run the WebGL 2.0 conformance tests. Plan for the larger
+    capacity, as it's desired to run the larger test suite on as many
+    configurations as possible.
+2.  Deploy Release and Debug testers on the chromium.gpu waterfall, following
+    the instructions for the chromium.gpu.fyi waterfall above. You will also
+    need to temporarily add suppressions to
+    [`tests/masters_recipes_test.py`][tests/masters_recipes_test.py] for these
+    new testers since they aren't yet covered by try bots and are going on a
+    non-FYI waterfall. Make sure these run green for a day or two before
+    proceeding.
+3.  Create a CL in the tools/build workspace, adding the new Release tester
+    to `win_chromium_rel_ng`'s `bot_ids` list
+    in `scripts/slave/recipe_modules/chromium_tests/trybots.py`. Rerun
+    `scripts/slave/recipes.py --use-bootstrap test train`.
+4.  Once the CL in (3) lands, the commit queue will **immediately** start
+    running tests on the CoolNewGPUType configuration. Be vigilant and make
+    sure that tryjobs are green. If they are red for any reason, revert the CL
+    and figure out offline what went wrong.
+
+[tests/masters_recipes_test.py]: https://chromium.googlesource.com/chromium/tools/build/+/master/tests/masters_recipes_test.py
+
+#### How to add a new optional try bot
+
+The "optional" GPU try bots are a concession to the reality that there are some
+long-running GPU test suites that simply can not run against every Chromium CL.
+They run some additional tests that are usually run only on the
+chromium.gpu.fyi waterfall. Some of these tests, like the WebGL 2.0 conformance
+suite, are intended to be run on the normal try bots once hardware capacity is
+available. Some are not intended to ever run on the normal try bots.
+
+The optional try bots are a little different because they mirror waterfall bots
+that don't actually exist. The waterfall bots' specifications exist only to
+tell the optional try bots which tests to run.
+
+Let's say that you intended to add a new such optional try bot on Windows. Call
+it `win_new_optional_tests_rel` for example. Now, if you wanted to just add
+this GPU type to the existing `win_optional_gpu_tests_rel` try bot, you'd
+just follow the instructions above
+([How to start running tests on a new GPU type on an existing try bot](#How-to-start-running-tests-on-a-new-GPU-type-on-an-existing-try-bot)). The steps below describe how to spin up
+an entire new optional try bot.
+
+1.  Make sure that you have some swarming capacity for the new GPU type. Since
+    it's not running against all Chromium CLs you don't need the recommended 30
+    minimum bots, though ~10 would be good.
+1.  Create a CL in the Chromium workspace:
+    1.  Add your new bot (for example, "Optional Win7 Release
+        (CoolNewGPUType)") to the chromium.gpu.fyi waterfall in
+        [generate_buildbot_json.py]. (Note, this is a bad example: the
+        "optional" bots have special semantics in this script. You'd probably
+        want to define some new category of bot if you didn't intend to add
+        this to `win_optional_gpu_tests_rel`.) 
+    1.  Re-run the script to regenerate the JSON files.
+1.  Land the above CL.
+1.  Create a CL in the tools/build workspace:
+    1.  Modify `masters/master.tryserver.chromium.win`'s [master.cfg] and
+        [slaves.cfg] to add the new tryserver. Follow the pattern for the
+        existing `win_optional_gpu_tests_rel` tryserver. Namely, add the new
+        entry to master.cfg, and add the new tryserver to the
+        `optional_builders` list in `slaves.cfg`.
+    1.  Modify [`chromium_gpu_fyi.py`][chromium_gpu_fyi.py] to add the new
+        "Optional Win7 Release (CoolNewGPUType)" entry.
+    1.  Modify [`trybots.py`][trybots.py] to add
+        the new `win_new_optional_tests_rel` try bot, mirroring "Optional
+        Win7 Release (CoolNewGPUType)".
+1.  Land the above CL and request an off-hours restart of the
+    tryserver.chromium.win waterfall.
+1.  Now you can send CLs to the new bot with:
+    `git cl try -m tryserver.chromium.win -b win_new_optional_tests_rel`
+
+[master.cfg]: https://chromium.googlesource.com/chromium/tools/build/+/master/masters/master.tryserver.chromium.win/master.cfg
+[slaves.cfg]: https://chromium.googlesource.com/chromium/tools/build/+/master/masters/master.tryserver.chromium.win/slaves.cfg
+
+#### How to test and deploy a driver update
+
+Let's say that you want to roll out an update to the graphics drivers on one of
+the configurations like the Win7 NVIDIA bots. The responsible way to do this is
+to run the new driver on one of the waterfalls for a day or two to make sure
+the tests are running reliably green before rolling out the driver update
+everywhere. To do this:
+
+1.  Work with the Chrome Infrastructure Labs team to deploy a single,
+    non-swarmed, physical machine on the chromium.gpu.fyi waterfall running the
+    new driver. The OS and GPU should exactly match the configuration you
+    intend to upgrade. See
+    [How to add a new, non-swarmed, physical bot to the chromium.gpu.fyi waterfall](#How-to-add-a-new_non-swarmed_physical-bot-to-the-chromium_gpu_fyi-waterfall).
+2.  Hopefully, the new machine will pass the pixel tests. If it doesn't, then
+    unfortunately, it'll be necessary to follow the instructions on
+    [updating the pixel tests] to temporarily suppress the failures on this
+    particular configuration. Keep the time window for these test suppressions
+    as narrow as possible.
+3.  Watch the new machine for a day or two to make sure it's stable.
+4.  When it is, ask the Chrome Infrastructure Labs team to roll out the driver
+    update across all of the similarly configured bots in the swarming pool.
+5.  If necessary, update pixel test expectations and remove the suppressions
+    added above.
+6.  Prepare and land a CL removing the temporary machine from the
+    chromium.gpu.fyi waterfall. Request a waterfall restart.
+7.  File a ticket with the Chrome Infrastructure Labs team to reclaim the
+    temporary machine.
+
+Note that with recent improvements to Swarming, in particular [this
+RFE](https://github.com/luci/luci-py/issues/253) and others, these steps are no
+longer strictly necessary – it's possible to target Swarming jobs at a
+particular driver version. If
+[`generate_buildbot_json.py`][generate_buildbot_json.py] were improved to be
+more specific about the driver version on the various bots, then the machines
+with the new drivers could simply be added to the Swarming pool, and this
+process could be a lot simpler. Patches welcome. :)
+
+[updating the pixel tests]: https://www.chromium.org/developers/testing/gpu-testing/#TOC-Updating-and-Adding-New-Pixel-Tests-to-the-GPU-Bots
+
+## Credentials for various servers
+
+Working with the GPU bots requires credentials to various services: the isolate
+server, the swarming server, and cloud storage.
+
+### Isolate server credentials
+
+To upload and download isolates you must first authenticate to the isolate
+server. From a Chromium checkout, run:
+
+*   `./src/tools/swarming_client/auth.py login
+    --service=https://isolateserver.appspot.com`
+
+This will open a web browser to complete the authentication flow. A @google.com
+email address is required in order to properly authenticate.
+
+To test your authentication, find a hash for a recent isolate. Consult the
+instructions on [Running Binaries from the Bots Locally] to find a random hash
+from a target like `gl_tests`. Then run the following:
+
+[Running Binaries from the Bots Locally]: https://www.chromium.org/developers/testing/gpu-testing#TOC-Running-Binaries-from-the-Bots-Locally
+
+If authentication succeeded, this will silently download a file called
+`delete_me` into the current working directory. If it failed, the script will
+report multiple authentication errors. In this case, use the following command
+to log out and then try again:
+
+*   `./src/tools/swarming_client/auth.py logout
+    --service=https://isolateserver.appspot.com`
+
+### Swarming server credentials
+
+The swarming server uses the same `auth.py` script as the isolate server. You
+will need to authenticate if you want to manually download the results of
+previous swarming jobs, trigger your own jobs, or run `swarming.py reproduce`
+to re-run a remote job on your local workstation. Follow the instructions
+above, replacing the service with `https://chromium-swarm.appspot.com`.
+
+### Cloud storage credentials
+
+Authentication to Google Cloud Storage is needed for a couple of reasons:
+uploading pixel test results to the cloud, and potentially uploading and
+downloading builds as well, at least in Debug mode. Use the copy of gsutil in
+`depot_tools/third_party/gsutil/gsutil`, and follow the [Google Cloud Storage
+instructions] to authenticate. You must use your @google.com email address and
+be a member of the Chrome GPU team in order to receive read-write access to the
+appropriate cloud storage buckets. Roughly:
+
+1.  Run `gsutil config`
+2.  Copy/paste the URL into your browser
+3.  Log in with your @google.com account
+4.  Allow the app to access the information it requests
+5.  Copy-paste the resulting key back into your Terminal
+6.  Press "enter" when prompted for a project-id (i.e., leave it empty)
+
+At this point you should be able to write to the cloud storage bucket.
+
+Navigate to
+<https://console.developers.google.com/storage/chromium-gpu-archive> to view
+the contents of the cloud storage bucket.
+
+[Google Cloud Storage instructions]: https://developers.google.com/storage/docs/gsutil
diff --git a/docs/gpu/images/wrangler.png b/docs/gpu/images/wrangler.png
new file mode 100644
index 0000000..ff269644
--- /dev/null
+++ b/docs/gpu/images/wrangler.png
Binary files differ
diff --git a/docs/gpu/pixel_wrangling.md b/docs/gpu/pixel_wrangling.md
new file mode 100644
index 0000000..7d1fcda
--- /dev/null
+++ b/docs/gpu/pixel_wrangling.md
@@ -0,0 +1,298 @@
+# GPU Bots & Pixel Wrangling
+
+![](images/wrangler.png)
+
+(December 2017: presentation on GPU bots and pixel wrangling: see [slides].)
+
+GPU Pixel Wrangling is the process of keeping various GPU bots green. On the
+GPU bots, tests run on physical hardware with real GPUs, not in VMs like the
+majority of the bots on the Chromium waterfall.
+
+[slides]: https://docs.google.com/presentation/d/1sZjyNe2apUhwr5sinRfPs7eTzH-3zO0VQ-Cj-8DlEDQ/edit?usp=sharing
+
+[TOC]
+
+## Fleet Status
+
+The following links (sorry, Google employees only) show the status of various
+GPU bots in the fleet.
+
+Primary configurations:
+
+*   [Windows 10 Quadro P400 Pool](http://shortn/_dmtaFfY2Jq)
+*   [Windows 10 Intel HD 630 Pool](http://shortn/_QsoGIGIFYd)
+*   [Linux Quadro P400 Pool](http://shortn/_fNgNs1uROQ)
+*   [Linux Intel HD 630 Pool](http://shortn/_dqEGjCGMHT)
+*   [Mac AMD Retina 10.12.6 GPU Pool](http://shortn/_BcrVmfRoSo)
+*   [Mac Mini Chrome Pool](http://shortn/_Ru8NESapPM)
+*   [Android Nexus 5X Chrome Pool](http://shortn/_G3j7AVmuNR)
+
+Secondary configurations:
+
+*   [Windows 7 Quadro P400 Pool](http://shortn/_cuxSKC15UX)
+*   [Windows AMD R7 240 GPU Pool](http://shortn/_XET7RTMHQm)
+*   [Mac NVIDIA Retina 10.12.6 GPU Pool](http://shortn/_jQWG7W71Ek)
+
+## GPU Bots' Waterfalls
+
+The waterfalls work much like any other; see the [Tour of the Chromium Buildbot
+Waterfall] for a more detailed explanation of how this is laid out. We have
+more subtle configurations because the GPU matters, not just the OS and release
+v. debug. Hence we have Windows Nvidia Release bots, Mac Intel Debug bots, and
+so on. The waterfalls we’re interested in are:
+
+*   [Chromium GPU]
+    *   Various operating systems, configurations, GPUs, etc.
+*   [Chromium GPU FYI]
+    *   These bots run less-standard configurations like Windows with AMD GPUs,
+        Linux with Intel GPUs, etc.
+    *   These bots build with top of tree ANGLE rather than the `DEPS` version.
+    *   The [ANGLE tryservers] help ensure that these bots stay green. However,
+        it is possible that due to ANGLE changes these bots may be red while
+        the chromium.gpu bots are green.
+    *   The [ANGLE Wrangler] is on-call to help resolve ANGLE-related breakage
+        on this watefall.
+    *   To determine if a different ANGLE revision was used between two builds,
+        compare the `got_angle_revision` buildbot property on the GPU builders
+        or `parent_got_angle_revision` on the testers. This revision can be
+        used to do a `git log` in the `third_party/angle` repository.
+
+<!-- TODO(kainino): update link when the page is migrated -->
+[Tour of the Chromium Buildbot Waterfall]: http://www.chromium.org/developers/testing/chromium-build-infrastructure/tour-of-the-chromium-buildbot
+[Chromium GPU]: https://ci.chromium.org/p/chromium/g/chromium.gpu/console?reload=120
+[Chromium GPU FYI]: https://ci.chromium.org/p/chromium/g/chromium.gpu.fyi/console?reload=120
+[ANGLE tryservers]: https://build.chromium.org/p/tryserver.chromium.angle/waterfall
+<!-- TODO(kainino): update link when the page is migrated -->
+[ANGLE Wrangler]: https://sites.google.com/a/chromium.org/dev/developers/how-tos/angle-wrangling
+
+## Test Suites
+
+The bots run several test suites. The majority of them have been migrated to
+the Telemetry harness, and are run within the full browser, in order to better
+test the code that is actually shipped. As of this writing, the tests included:
+
+*   Tests using the Telemetry harness:
+    *   The WebGL conformance tests: `webgl_conformance_integration_test.py`
+    *   A Google Maps test: `maps_integration_test.py`
+    *   Context loss tests: `context_lost_integration_test.py`
+    *   Depth capture tests: `depth_capture_integration_test.py`
+    *   GPU process launch tests: `gpu_process_integration_test.py`
+    *   Hardware acceleration validation tests:
+        `hardware_accelerated_feature_integration_test.py`
+    *   Pixel tests validating the end-to-end rendering pipeline:
+        `pixel_integration_test.py`
+    *   Stress tests of the screenshot functionality other tests use:
+        `screenshot_sync_integration_test.py`
+*   `angle_unittests`: see `src/gpu/gpu.gyp`
+*   drawElements tests (on the chromium.gpu.fyi waterfall): see
+    `src/third_party/angle/src/tests/BUILD.gn`
+*   `gles2_conform_test` (requires internal sources): see
+    `src/gpu/gles2_conform_support/gles2_conform_test.gyp`
+*   `gl_tests`: see `src/gpu/BUILD.gn`
+*   `gl_unittests`: see `src/ui/gl/BUILD.gn`
+
+And more. See `src/content/test/gpu/generate_buildbot_json.py` for the
+complete description of bots and tests.
+
+Additionally, the Release bots run:
+
+*   `tab_capture_end2end_tests:` see
+    `src/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc` and
+    `src/chrome/browser/extensions/api/cast_streaming/cast_streaming_apitest.cc`
+
+### More Details
+
+More details about the bots' setup can be found on the [GPU Testing] page.
+
+[GPU Testing]: https://sites.google.com/a/chromium.org/dev/developers/testing/gpu-testing
+
+## Wrangling
+
+### Prerequisites
+
+1.  Ideally a wrangler should be a Chromium committer. If you're on the GPU
+pixel wrangling rotation, there will be an email notifying you of the upcoming
+shift, and a calendar appointment.
+    *   If you aren't a committer, don't panic. It's still best for everyone on
+        the team to become acquainted with the procedures of maintaining the
+        GPU bots.
+    *   In this case you'll upload CLs to Gerrit to perform reverts (optionally
+        using the new "Revert" button in the UI), and might consider using
+        `TBR=` to speed through trivial and urgent CLs. In general, try to send
+        all CLs through the commit queue.
+    *   Contact bajones, kainino, kbr, vmiura, zmo, or another member of the
+        Chrome GPU team who's already a committer for help landing patches or
+        reverts during your shift.
+2.  Apply for [access to the bots].
+
+[access to the bots]: https://sites.google.com/a/google.com/chrome-infrastructure/golo/remote-access?pli=1
+
+### How to Keep the Bots Green
+
+1.  Watch for redness on the tree.
+    1.  [Sheriff-O-Matic now has support for the chromium.gpu.fyi waterfall]!
+    1.  The chromium.gpu bots are covered under Sheriff-O-Matic's [Chromium
+        tab]. As pixel wrangler, ignore any non-GPU test failures in this tab.
+    1.  The bots are expected to be green all the time. Flakiness on these bots
+        is neither expected nor acceptable.
+    1.  If a bot goes consistently red, it's necessary to figure out whether a
+        recent CL caused it, or whether it's a problem with the bot or
+        infrastructure.
+    1.  If it looks like a problem with the bot (deep problems like failing to
+        check out the sources, the isolate server failing, etc.) notify the
+        Chromium troopers and file a P1 bug with labels: Infra\>Labs,
+        Infra\>Troopers and Internals\>GPU\>Testing. See the general [tree
+        sheriffing page] for more details.
+    1.  Otherwise, examine the builds just before and after the redness was
+        introduced. Look at the revisions in the builds before and after the
+        failure was introduced.
+    1.  **File a bug** capturing the regression range and excerpts of any
+        associated logs. Regressions should be marked P1. CC engineers who you
+        think may be able to help triage the issue. Keep in mind that the logs
+        on the bots expire after a few days, so make sure to add copies of
+        relevant logs to the bug report.
+    1.  Use the `Hotlist=PixelWrangler` label to mark bugs that require the
+        pixel wrangler's attention, so it's easy to find relevant bugs when
+        handing off shifts.
+    1.  Study the regression range carefully. Use drover to revert any CLs
+        which break the chromium.gpu bots. Use your judgment about
+        chromium.gpu.fyi, since not all bots are covered by trybots. In the
+        revert message, provide a clear description of what broke, links to
+        failing builds, and excerpts of the failure logs, because the build
+        logs expire after a few days.
+1.  Make sure the bots are running jobs.
+    1.  Keep an eye on the console views of the various bots.
+    1.  Make sure the bots are all actively processing jobs. If they go offline
+        for a long period of time, the "summary bubble" at the top may still be
+        green, but the column in the console view will be gray.
+    1.  Email the Chromium troopers if you find a bot that's not processing
+        jobs.
+1.  Make sure the GPU try servers are in good health.
+    1.  The GPU try servers are no longer distinct bots on a separate
+        waterfall, but instead run as part of the regular tryjobs on the
+        Chromium waterfalls. The GPU tests run as part of the following
+        tryservers' jobs:
+        1.  <code>[linux_chromium_rel_ng]</code> on the [luci.chromium.try]
+            waterfall
+<!-- TODO(kainino): update link to luci.chromium.try -->
+        1.  <code>[mac_chromium_rel_ng]</code> on the [tryserver.chromium.mac]
+            waterfall
+<!-- TODO(kainino): update link to luci.chromium.try -->
+        1.  <code>[win7_chromium_rel_ng]</code> on the [tryserver.chromium.win]
+            waterfall
+    1.  The best tool to use to quickly find flakiness on the tryservers is the
+        new [Chromium Try Flakes] tool. Look for the names of GPU tests (like
+        maps_pixel_test) as well as the test machines (e.g.
+        mac_chromium_rel_ng). If you see a flaky test, file a bug like [this
+        one](http://crbug.com/444430). Also look for compile flakes that may
+        indicate that a bot needs to be clobbered. Contact the Chromium
+        sheriffs or troopers if so.
+    1.  Glance at these trybots from time to time and see if any GPU tests are
+        failing frequently. **Note** that test failures are **expected** on
+        these bots: individuals' patches may fail to apply, fail to compile, or
+        break various tests. Look specifically for patterns in the failures. It
+        isn't necessary to spend a lot of time investigating each individual
+        failure. (Use the "Show: 200" link at the bottom of the page to see
+        more history.)
+    1.  If the same set of tests are failing repeatedly, look at the individual
+        runs. Examine the swarming results and see whether they're all running
+        on the same machine. (This is the "Bot assigned to task" when clicking
+        any of the test's shards in the build logs.) If they are, something
+        might be wrong with the hardware. Use the [Swarming Server Stats] tool
+        to drill down into the specific builder.
+    1.  If you see the same test failing in a flaky manner across multiple
+        machines and multiple CLs, it's crucial to investigate why it's
+        happening. [crbug.com/395914](http://crbug.com/395914) was one example
+        of an innocent-looking Blink change which made it through the commit
+        queue and introduced widespread flakiness in a range of GPU tests. The
+        failures were also most visible on the try servers as opposed to the
+        main waterfalls.
+1.  Check if any pixel test failures are actual failures or need to be
+    rebaselined.
+    1.  For a given build failing the pixel tests, click the "stdio" link of
+        the "pixel" step.
+    1.  The output will contain a link of the form
+        <http://chromium-browser-gpu-tests.commondatastorage.googleapis.com/view_test_results.html?242523_Linux_Release_Intel__telemetry>
+    1.  Visit the link to see whether the generated or reference images look
+        incorrect.
+    1.  All of the reference images for all of the bots are stored in cloud
+        storage under [chromium-gpu-archive/reference-images]. They are indexed
+        by version number, OS, GPU vendor, GPU device, and whether or not
+        antialiasing is enabled in that configuration. You can download the
+        reference images individually to examine them in detail.
+1.  Rebaseline pixel test reference images if necessary.
+    1.  Follow the [instructions on the GPU testing page].
+    1.  Alternatively, if absolutely necessary, you can use the [Chrome
+        Internal GPU Pixel Wrangling Instructions] to delete just the broken
+        reference images for a particular configuration.
+1.  Update Telemetry-based test expectations if necessary.
+    1.  Most of the GPU tests are run inside a full Chromium browser, launched
+        by Telemetry, rather than a Gtest harness. The tests and their
+        expectations are contained in [src/content/test/gpu/gpu_tests/] . See
+        for example <code>[webgl_conformance_expectations.py]</code>,
+        <code>[gpu_process_expectations.py]</code> and
+        <code>[pixel_expectations.py]</code>.
+    1.  See the header of the file a list of modifiers to specify a bot
+        configuration. It is possible to specify OS (down to a specific
+        version, say, Windows 7 or Mountain Lion), GPU vendor
+        (NVIDIA/AMD/Intel), and a specific GPU device.
+    1.  The key is to maintain the highest coverage: if you have to disable a
+        test, disable it only on the specific configurations it's failing. Note
+        that it is not possible to discern between Debug and Release
+        configurations.
+    1.  Mark tests failing or skipped, which will suppress flaky failures, only
+        as a last resort. It is only really necessary to suppress failures that
+        are showing up on the GPU tryservers, since failing tests no longer
+        close the Chromium tree.
+    1.  Please read the section on [stamping out flakiness] for motivation on
+        how important it is to eliminate flakiness rather than hiding it.
+1.  For the remaining Gtest-style tests, use the [`DISABLED_`
+    modifier][gtest-DISABLED] to suppress any failures if necessary.
+
+[Sheriff-O-Matic now has support for the chromium.gpu.fyi waterfall]: https://sheriff-o-matic.appspot.com/chromium.gpu.fyi
+[Chromium tab]: https://sheriff-o-matic.appspot.com/chromium
+[tree sheriffing page]: https://sites.google.com/a/chromium.org/dev/developers/tree-sheriffs
+[linux_chromium_rel_ng]: https://ci.chromium.org/p/chromium/builders/luci.chromium.try/linux_chromium_rel_ng
+[luci.chromium.try]: https://ci.chromium.org/p/chromium/g/luci.chromium.try/builders
+[mac_chromium_rel_ng]: https://ci.chromium.org/buildbot/tryserver.chromium.mac/mac_chromium_rel_ng/
+[tryserver.chromium.mac]: https://ci.chromium.org/p/chromium/g/tryserver.chromium.mac/builders
+[win7_chromium_rel_ng]: https://ci.chromium.org/buildbot/tryserver.chromium.win/win7_chromium_rel_ng/
+[tryserver.chromium.win]: https://ci.chromium.org/p/chromium/g/tryserver.chromium.win/builders
+[Chromium Try Flakes]: http://chromium-try-flakes.appspot.com/
+<!-- TODO(kainino): link doesn't work, but is still included from chromium-swarm homepage so not removing it now -->
+[Swarming Server Stats]: https://chromium-swarm.appspot.com/stats
+[chromium-gpu-archive/reference-images]: https://console.developers.google.com/storage/chromium-gpu-archive/reference-images
+[instructions on the GPU testing page]: https://sites.google.com/a/chromium.org/dev/developers/testing/gpu-testing#TOC-Updating-and-Adding-New-Pixel-Tests-to-the-GPU-Bots
+[Chrome Internal GPU Pixel Wrangling Instructions]: https://sites.google.com/a/google.com/client3d/documents/chrome-internal-gpu-pixel-wrangling-instructions
+[src/content/test/gpu/gpu_tests/]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/
+[webgl_conformance_expectations.py]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+[gpu_process_expectations.py]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/gpu_process_expectations.py
+[pixel_expectations.py]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/gpu_tests/pixel_expectations.py
+[stamping out flakiness]: gpu_testing.md#Stamping-out-Flakiness
+[gtest-DISABLED]: https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#temporarily-disabling-tests
+
+### When Bots Misbehave (SSHing into a bot)
+
+1.  See the [Chrome Internal GPU Pixel Wrangling Instructions] for information
+    on ssh'ing in to the GPU bots.
+
+[Chrome Internal GPU Pixel Wrangling Instructions]: https://sites.google.com/a/google.com/client3d/documents/chrome-internal-gpu-pixel-wrangling-instructions
+
+### Reproducing WebGL conformance test failures locally
+
+1.  From the buildbot build output page, click on the failed shard to get to
+    the swarming task page. Scroll to the bottom of the left panel for a
+    command to run the task locally. This will automatically download the build
+    and any other inputs needed.
+2.  Alternatively, to run the test on a local build, pass the arguments
+    `--browser=exact --browser-executable=/path/to/binary` to
+    `content/test/gpu/run_gpu_integration_test.py`.
+    Also see the [telemetry documentation].
+
+[telemetry documentation]: https://cs.chromium.org/chromium/src/third_party/catapult/telemetry/docs/run_benchmarks_locally.md
+
+## Extending the GPU Pixel Wrangling Rotation
+
+See the [Chrome Internal GPU Pixel Wrangling Instructions] for information on extending the rotation.
+
+[Chrome Internal GPU Pixel Wrangling Instructions]: https://sites.google.com/a/google.com/client3d/documents/chrome-internal-gpu-pixel-wrangling-instructions
diff --git a/gpu/command_buffer/common/mailbox.cc b/gpu/command_buffer/common/mailbox.cc
index 8ca3d53..b495351 100644
--- a/gpu/command_buffer/common/mailbox.cc
+++ b/gpu/command_buffer/common/mailbox.cc
@@ -10,6 +10,7 @@
 
 #include "base/logging.h"
 #include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
 
 namespace gpu {
 
@@ -58,4 +59,14 @@
 #endif
 }
 
+std::string Mailbox::ToDebugString() const {
+  std::string s;
+  for (int i = 0; i < GL_MAILBOX_SIZE_CHROMIUM; ++i) {
+    if (i > 0)
+      s += ':';
+    s += base::StringPrintf("%u", name[i]);
+  }
+  return s;
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/common/mailbox.h b/gpu/command_buffer/common/mailbox.h
index f6b334a8..6db8cb2 100644
--- a/gpu/command_buffer/common/mailbox.h
+++ b/gpu/command_buffer/common/mailbox.h
@@ -8,6 +8,8 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <string>
+
 #include "gpu/gpu_export.h"
 
 // From gl2/gl2ext.h.
@@ -46,6 +48,8 @@
   // check, only to catch bugs where clients forgot to call Mailbox::Generate.
   bool Verify() const;
 
+  std::string ToDebugString() const;
+
   Name name;
 
   bool operator<(const Mailbox& other) const {
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index 6804424..6fd4452 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -257,6 +257,15 @@
   disallowed_features_ = disallowed_features;
   context_type_ = context_type;
   is_passthrough_cmd_decoder_ = is_passthrough_cmd_decoder;
+  switch (context_type) {
+    case CONTEXT_TYPE_WEBGL1:
+    case CONTEXT_TYPE_OPENGLES2:
+      break;
+    default:
+      // https://crbug.com/826509
+      workarounds_.use_client_side_arrays_for_stream_buffers = false;
+      break;
+  }
   InitializeFeatures();
 }
 
diff --git a/gpu/command_buffer/service/feature_info.h b/gpu/command_buffer/service/feature_info.h
index d11afc0..ac75135 100644
--- a/gpu/command_buffer/service/feature_info.h
+++ b/gpu/command_buffer/service/feature_info.h
@@ -227,7 +227,7 @@
   FeatureFlags feature_flags_;
 
   // Flags for Workarounds.
-  const GpuDriverBugWorkarounds workarounds_;
+  GpuDriverBugWorkarounds workarounds_;
 
   bool ext_color_buffer_float_available_ = false;
   bool ext_color_buffer_half_float_available_ = false;
diff --git a/gpu/command_buffer/service/feature_info_unittest.cc b/gpu/command_buffer/service/feature_info_unittest.cc
index f7c020b..6f6035a 100644
--- a/gpu/command_buffer/service/feature_info_unittest.cc
+++ b/gpu/command_buffer/service/feature_info_unittest.cc
@@ -1427,8 +1427,16 @@
   workarounds.use_client_side_arrays_for_stream_buffers = true;
   SetupInitExpectationsWithWorkarounds("GL_OES_vertex_array_object",
                                        workarounds);
-  EXPECT_TRUE(info_->workarounds().use_client_side_arrays_for_stream_buffers);
-  EXPECT_FALSE(info_->feature_flags().native_vertex_array_object);
+  if (GetContextType() == CONTEXT_TYPE_OPENGLES2) {
+    EXPECT_TRUE(info_->workarounds().use_client_side_arrays_for_stream_buffers);
+    EXPECT_FALSE(info_->feature_flags().native_vertex_array_object);
+  } else {  // CONTEXT_TYPE_OPENGLES3
+    // We only turn on use_client_side_arrays_for_stream_buffers on ES2
+    // contexts. See https://crbug.com/826509.
+    EXPECT_FALSE(
+        info_->workarounds().use_client_side_arrays_for_stream_buffers);
+    EXPECT_TRUE(info_->feature_flags().native_vertex_array_object);
+  }
 }
 
 TEST_P(FeatureInfoTest, InitializeEXT_blend_minmax) {
diff --git a/infra/config/branch/cq.cfg b/infra/config/branch/cq.cfg
index cd50f2565..b073aacb 100644
--- a/infra/config/branch/cq.cfg
+++ b/infra/config/branch/cq.cfg
@@ -30,6 +30,13 @@
 # https://sites.google.com/a/chromium.org/dev/developers/testing/chromium-build-infrastructure/tour-of-the-chromium-buildbot?pli=1#TOC-Adding-new-build-configurations-and-tests-to-the-main-Chromium-waterfall-Commit-Queue
   try_job {
     buckets {
+      name: "luci.chromium.try"
+      builders {
+        name: "linux_chromium_rel_ng_patch_on_gclient"
+        experiment_percentage: 5
+      }
+    }
+    buckets {
       name: "master.tryserver.chromium.android"
       builders {
         name: "android_arm64_dbg_recipe"
@@ -117,10 +124,6 @@
         name: "linux_chromium_rel_ng"
         equivalent_to { bucket: "luci.chromium.try" percentage: 100 }
       }
-      builders {
-        name: "linux_chromium_rel_ng_patch_on_gclient"
-        experiment_percentage: 5
-      }
     }
     buckets {
       name: "master.tryserver.chromium.mac"
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index d521a3a3..baa4905e 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -120,6 +120,7 @@
 #import "ios/chrome/browser/ui/external_file_remover_impl.h"
 #import "ios/chrome/browser/ui/first_run/first_run_util.h"
 #import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
+#include "ios/chrome/browser/ui/history/history_coordinator.h"
 #import "ios/chrome/browser/ui/history/history_panel_view_controller.h"
 #import "ios/chrome/browser/ui/main/browser_view_wrangler.h"
 #import "ios/chrome/browser/ui/main/main_coordinator.h"
@@ -388,9 +389,12 @@
   // Cached launchOptions from -didFinishLaunchingWithOptions.
   NSDictionary* _launchOptions;
 
-  // View controller for displaying the history panel.
+  // View controller for displaying the legacy history panel.
   UIViewController* _historyPanelViewController;
 
+  // Coordinator for displaying history.
+  HistoryCoordinator* _historyCoordinator;
+
   // Variable backing metricsMediator property.
   __weak MetricsMediator* _metricsMediator;
 
@@ -1435,13 +1439,23 @@
 }
 
 - (void)showHistory {
-  _historyPanelViewController = [[HistoryPanelViewController alloc]
-      initWithLoader:self.currentBVC
-        browserState:_mainBrowserState
-          dispatcher:self.mainBVC.dispatcher];
-  [self.currentBVC presentViewController:_historyPanelViewController
-                                animated:YES
-                              completion:nil];
+  if (experimental_flags::IsCollectionsUIRebootEnabled()) {
+    // New History UIReboot coordinator.
+    _historyCoordinator = [[HistoryCoordinator alloc]
+        initWithBaseViewController:self.currentBVC
+                      browserState:_mainBrowserState];
+    _historyCoordinator.loader = self.currentBVC;
+    _historyCoordinator.dispatcher = self.mainBVC.dispatcher;
+    [_historyCoordinator start];
+  } else {
+    _historyPanelViewController = [[HistoryPanelViewController alloc]
+        initWithLoader:self.currentBVC
+          browserState:_mainBrowserState
+            dispatcher:self.mainBVC.dispatcher];
+    [self.currentBVC presentViewController:_historyPanelViewController
+                                  animated:YES
+                                completion:nil];
+  }
 }
 
 - (void)closeSettingsUIAndOpenURL:(OpenUrlCommand*)command {
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index d6e66b5..5308d2f 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -257,6 +257,10 @@
     {"feedback-kit-v2", flag_descriptions::kFeedbackKitV2Name,
      flag_descriptions::kFeedbackKitV2Description, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kFeedbackKitV2)},
+    {"feedback-kit-v2-sso-service",
+     flag_descriptions::kFeedbackKitV2WithSSOServiceName,
+     flag_descriptions::kFeedbackKitV2WithSSOServiceDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kFeedbackKitV2WithSSOService)},
     {"new-clear-browsing-data-ui",
      flag_descriptions::kNewClearBrowsingDataUIName,
      flag_descriptions::kNewClearBrowsingDataUIDescription, flags_ui::kOsIos,
diff --git a/ios/chrome/browser/experimental_flags.h b/ios/chrome/browser/experimental_flags.h
index 11de883..18ceb526 100644
--- a/ios/chrome/browser/experimental_flags.h
+++ b/ios/chrome/browser/experimental_flags.h
@@ -54,6 +54,9 @@
 // Whether a new version of FeedbackKit is the preferred feedback UI provider.
 bool IsNewFeedbackKitEnabled();
 
+// Whether SSOService is configured for new version of FeedbackKit.
+bool IsNewFeedbackKitEnabledWithSSOService();
+
 // Whether the 3rd party keyboard omnibox workaround is enabled.
 bool IsThirdPartyKeyboardWorkaroundEnabled();
 
diff --git a/ios/chrome/browser/experimental_flags.mm b/ios/chrome/browser/experimental_flags.mm
index 8cf3922..877cd97 100644
--- a/ios/chrome/browser/experimental_flags.mm
+++ b/ios/chrome/browser/experimental_flags.mm
@@ -117,6 +117,10 @@
   return base::FeatureList::IsEnabled(kFeedbackKitV2);
 }
 
+bool IsNewFeedbackKitEnabledWithSSOService() {
+  return base::FeatureList::IsEnabled(kFeedbackKitV2WithSSOService);
+}
+
 bool IsThirdPartyKeyboardWorkaroundEnabled() {
   // Check if the experimental flag is forced on or off.
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
diff --git a/ios/chrome/browser/history/history_tab_helper.mm b/ios/chrome/browser/history/history_tab_helper.mm
index 34ff02e..b8a2e42 100644
--- a/ios/chrome/browser/history/history_tab_helper.mm
+++ b/ios/chrome/browser/history/history_tab_helper.mm
@@ -97,6 +97,10 @@
     return;
   }
 
+  if (!navigation_context->HasCommitted()) {
+    return;
+  }
+
   DCHECK(web_state->GetNavigationManager()->GetVisibleItem());
   web::NavigationItem* visible_item =
       web_state_->GetNavigationManager()->GetVisibleItem();
diff --git a/ios/chrome/browser/infobars/infobar_container_delegate_ios.h b/ios/chrome/browser/infobars/infobar_container_delegate_ios.h
index 298d706..def95f45 100644
--- a/ios/chrome/browser/infobars/infobar_container_delegate_ios.h
+++ b/ios/chrome/browser/infobars/infobar_container_delegate_ios.h
@@ -28,9 +28,6 @@
 
   ~InfoBarContainerDelegateIOS() override {}
 
-  // Calling this method on iOS is a programming error.
-  SkColor GetInfoBarSeparatorColor() const override;
-
   // This method always returns 0 on iOS.
   int ArrowTargetHeightForInfoBar(
       size_t index,
diff --git a/ios/chrome/browser/infobars/infobar_container_delegate_ios.mm b/ios/chrome/browser/infobars/infobar_container_delegate_ios.mm
index 13df1c4d..d3d6a7ea 100644
--- a/ios/chrome/browser/infobars/infobar_container_delegate_ios.mm
+++ b/ios/chrome/browser/infobars/infobar_container_delegate_ios.mm
@@ -12,11 +12,6 @@
 #error "This file requires ARC support."
 #endif
 
-SkColor InfoBarContainerDelegateIOS::GetInfoBarSeparatorColor() const {
-  NOTIMPLEMENTED();
-  return SK_ColorBLACK;
-}
-
 int InfoBarContainerDelegateIOS::ArrowTargetHeightForInfoBar(
     size_t index,
     const gfx::SlideAnimation& animation) const {
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index 41f2935..51737b4 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -53,6 +53,10 @@
 
 const char kFeedbackKitV2Name[] = "FeedbackKit V2";
 const char kFeedbackKitV2Description[] = "Enable use of FeedbackKit V2.";
+const char kFeedbackKitV2WithSSOServiceName[] =
+    "FeedbackKit V2 with SSOService configured for FeedbackKit";
+const char kFeedbackKitV2WithSSOServiceDescription[] =
+    "Send SSOService with configuration for FeedbackKit V2.";
 
 const char kHistoryBatchUpdatesFilterName[] = "History Single Batch Filtering";
 const char kHistoryBatchUpdatesFilterDescription[] =
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index e269ac00..22ae52d 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -45,9 +45,11 @@
 extern const char kExternalSearchName[];
 extern const char kExternalSearchDescription[];
 
-// Title and description for the flag to enable use of FeedbackKit V2.
+// Title and description for the flags to enable use of FeedbackKit V2.
 extern const char kFeedbackKitV2Name[];
 extern const char kFeedbackKitV2Description[];
+extern const char kFeedbackKitV2WithSSOServiceName[];
+extern const char kFeedbackKitV2WithSSOServiceDescription[];
 
 // Title and description for the flag to enable History batch filtering.
 extern const char kHistoryBatchUpdatesFilterName[];
diff --git a/ios/chrome/browser/itunes_links/itunes_links_flag.mm b/ios/chrome/browser/itunes_links/itunes_links_flag.mm
index a886670..a2f6f7a 100644
--- a/ios/chrome/browser/itunes_links/itunes_links_flag.mm
+++ b/ios/chrome/browser/itunes_links/itunes_links_flag.mm
@@ -9,4 +9,4 @@
 #endif
 
 const base::Feature kITunesLinksStoreKitHandling{
-    "ITunesLinksStoreKitHandling", base::FEATURE_DISABLED_BY_DEFAULT};
+    "ITunesLinksStoreKitHandling", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/tabs/tab_unittest.mm b/ios/chrome/browser/tabs/tab_unittest.mm
index d5cd5f6e..5cb9f1f 100644
--- a/ios/chrome/browser/tabs/tab_unittest.mm
+++ b/ios/chrome/browser/tabs/tab_unittest.mm
@@ -223,6 +223,7 @@
     context2.SetUrl(redirect_url);
     web_state_impl_->OnNavigationStarted(&context2);
     [tab_ navigationManagerImpl]->CommitPendingItem();
+    context2.SetHasCommitted(true);
     web_state_impl_->UpdateHttpResponseHeaders(redirect_url);
     web_state_impl_->OnNavigationFinished(&context2);
     web_state_impl_->SetIsLoading(true);
diff --git a/ios/chrome/browser/ui/download/BUILD.gn b/ios/chrome/browser/ui/download/BUILD.gn
index 18af241..e118268 100644
--- a/ios/chrome/browser/ui/download/BUILD.gn
+++ b/ios/chrome/browser/ui/download/BUILD.gn
@@ -25,6 +25,8 @@
     "radial_progress_view.mm",
   ]
   deps = [
+    "resources:background_compact",
+    "resources:background_regular",
     "resources:done_badge",
     "resources:download_manager_controller_xib",
     "resources:error_badge",
diff --git a/ios/chrome/browser/ui/download/download_manager_view_controller.mm b/ios/chrome/browser/ui/download/download_manager_view_controller.mm
index 962d8d6..2053a74 100644
--- a/ios/chrome/browser/ui/download/download_manager_view_controller.mm
+++ b/ios/chrome/browser/ui/download/download_manager_view_controller.mm
@@ -30,6 +30,10 @@
 // button). Defines the minimal distance between elements.
 const CGFloat kElementMargin = 16;
 
+// The size of the shadow used for background resizable image.
+const CGFloat kTopShadowHeight = 8;
+const CGFloat kLeftRightShadowHeight = 16;
+
 // Returns formatted size string.
 NSString* GetSizeString(long long size_in_bytes) {
   return [NSByteCountFormatter
@@ -56,11 +60,8 @@
   BOOL _installDriveButtonVisible;
   BOOL _addedConstraints;  // YES if NSLayoutConstraits were added.
 }
-// Shadow view sits on top of background view. Union of shadow and background
-// views fills self.view area. The shadow is dropped to web page, not to white
-// Download Manager background.
-@property(nonatomic, readonly) UIImageView* shadow;
-@property(nonatomic, readonly) UIView* background;
+// Background is a resizable image with edge shadows.
+@property(nonatomic, readonly) UIImageView* background;
 
 // Download Manager UI has 2 rows. First row is always visible and contains
 // essential download controls: close button, action button and status label.
@@ -92,7 +93,6 @@
 @implementation DownloadManagerViewController
 
 @synthesize delegate = _delegate;
-@synthesize shadow = _shadow;
 @synthesize background = _background;
 @synthesize downloadControlsRow = _downloadControlsRow;
 @synthesize installDriveControlsRow = _installDriveControlsRow;
@@ -105,7 +105,6 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  [self.view addSubview:self.shadow];
   [self.view addSubview:self.background];
   [self.view addSubview:self.downloadControlsRow];
   [self.view addSubview:self.installDriveControlsRow];
@@ -136,33 +135,23 @@
   [self updateBottomConstraints];
   [self updateWidthConstraintsForTraitCollection:self.traitCollection];
 
-  // shadow constraints.
-  UIImageView* shadow = self.shadow;
-  [NSLayoutConstraint activateConstraints:@[
-    [shadow.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
-    [shadow.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
-    [shadow.topAnchor constraintEqualToAnchor:view.topAnchor],
-  ]];
-
   // background constraints.
   UIView* background = self.background;
   [NSLayoutConstraint activateConstraints:@[
     [background.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
     [background.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
     [background.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
-    [background.topAnchor constraintEqualToAnchor:shadow.bottomAnchor],
+    [background.topAnchor constraintEqualToAnchor:view.topAnchor],
   ]];
 
   // download controls row constraints.
   UIView* downloadRow = self.downloadControlsRow;
   UIButton* closeButton = self.closeButton;
-  // Account for bottom white pixel on shadow image.
-  CGFloat shadowHeight = CGRectGetHeight(shadow.frame) - 1;
   [NSLayoutConstraint activateConstraints:@[
     [downloadRow.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
     [downloadRow.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
     [downloadRow.topAnchor constraintEqualToAnchor:view.topAnchor
-                                          constant:shadowHeight],
+                                          constant:kTopShadowHeight],
     [downloadRow.layoutMarginsGuide.heightAnchor
         constraintEqualToAnchor:closeButton.heightAnchor],
   ]];
@@ -281,6 +270,7 @@
   void (^alongsideBlock)(id<UIViewControllerTransitionCoordinatorContext>) =
       ^(id<UIViewControllerTransitionCoordinatorContext> context) {
         [self updateWidthConstraintsForTraitCollection:newCollection];
+        [self updateBackgroundForTraitCollection:newCollection];
       };
   [coordinator animateAlongsideTransition:alongsideBlock completion:nil];
 }
@@ -342,20 +332,11 @@
 
 #pragma mark - UI elements
 
-- (UIImageView*)shadow {
-  if (!_shadow) {
-    UIImage* shadowImage = [UIImage imageNamed:@"infobar_shadow"];
-    _shadow = [[UIImageView alloc] initWithImage:shadowImage];
-    _shadow.translatesAutoresizingMaskIntoConstraints = NO;
-  }
-  return _shadow;
-}
-
-- (UIView*)background {
+- (UIImageView*)background {
   if (!_background) {
-    _background = [[UIView alloc] initWithFrame:CGRectZero];
+    _background = [[UIImageView alloc] initWithImage:nil];
     _background.translatesAutoresizingMaskIntoConstraints = NO;
-    _background.backgroundColor = [UIColor whiteColor];
+    [self updateBackgroundForTraitCollection:self.traitCollection];
   }
   return _background;
 }
@@ -572,6 +553,20 @@
   self.bottomConstraint.active = YES;
 }
 
+// Updates background image for the given UITraitCollection.
+- (void)updateBackgroundForTraitCollection:(UITraitCollection*)traitCollection {
+  NSString* imageName =
+      traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular
+          ? @"background_regular"
+          : @"background_compact";
+
+  UIImage* image = [UIImage imageNamed:imageName];
+  UIEdgeInsets insets = UIEdgeInsetsMake(
+      kTopShadowHeight, kLeftRightShadowHeight, 0, kLeftRightShadowHeight);
+
+  self.background.image = [image resizableImageWithCapInsets:insets];
+}
+
 // Updates and activates self.widthConstraint anchored to superview width. Uses
 // smaller multiplier for regular ui size class, because otherwise the bar will
 // be too wide.
diff --git a/ios/chrome/browser/ui/download/resources/BUILD.gn b/ios/chrome/browser/ui/download/resources/BUILD.gn
index f51b30ba..0a97f58 100644
--- a/ios/chrome/browser/ui/download/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/download/resources/BUILD.gn
@@ -9,6 +9,23 @@
   source = "DownloadManagerController.xib"
 }
 
+imageset("background_compact") {
+  sources = [
+    "background_compact.imageset/Contents.json",
+    "background_compact.imageset/background_compact.png",
+    "background_compact.imageset/background_compact@2x.png",
+    "background_compact.imageset/background_compact@3x.png",
+  ]
+}
+
+imageset("background_regular") {
+  sources = [
+    "background_regular.imageset/Contents.json",
+    "background_regular.imageset/background_regular.png",
+    "background_regular.imageset/background_regular@2x.png",
+    "background_regular.imageset/background_regular@3x.png",
+  ]
+}
 imageset("done_badge") {
   sources = [
     "done_badge.imageset/Contents.json",
diff --git a/ios/chrome/browser/ui/download/resources/background_compact.imageset/Contents.json b/ios/chrome/browser/ui/download/resources/background_compact.imageset/Contents.json
new file mode 100644
index 0000000..1f595cd
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_compact.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "background_compact.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "background_compact@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "background_compact@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact.png b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact.png
new file mode 100644
index 0000000..434d59d1
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact.png
Binary files differ
diff --git a/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@2x.png b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@2x.png
new file mode 100644
index 0000000..2260abb
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@3x.png b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@3x.png
new file mode 100644
index 0000000..657c28e
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_compact.imageset/background_compact@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/download/resources/background_regular.imageset/Contents.json b/ios/chrome/browser/ui/download/resources/background_regular.imageset/Contents.json
new file mode 100644
index 0000000..f53b95e
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_regular.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "background_regular.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "background_regular@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "background_regular@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular.png b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular.png
new file mode 100644
index 0000000..8589b70
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular.png
Binary files differ
diff --git a/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@2x.png b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@2x.png
new file mode 100644
index 0000000..01f6195
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@3x.png b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@3x.png
new file mode 100644
index 0000000..835db86c
--- /dev/null
+++ b/ios/chrome/browser/ui/download/resources/background_regular.imageset/background_regular@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/history/BUILD.gn b/ios/chrome/browser/ui/history/BUILD.gn
index 23ececd..493241a 100644
--- a/ios/chrome/browser/ui/history/BUILD.gn
+++ b/ios/chrome/browser/ui/history/BUILD.gn
@@ -11,6 +11,8 @@
     "favicon_view.mm",
     "favicon_view_provider.h",
     "favicon_view_provider.mm",
+    "history_coordinator.h",
+    "history_coordinator.mm",
     "history_entries_status_item_delegate.h",
     "history_entry_inserter.h",
     "history_entry_inserter.mm",
@@ -35,6 +37,7 @@
   ]
   deps = [
     ":history_base_feature",
+    ":history_ui",
     "//base",
     "//base:i18n",
     "//components/browser_sync",
@@ -61,6 +64,7 @@
     "//ios/chrome/browser/ui/colors",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/context_menu",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/icons",
     "//ios/chrome/browser/ui/keyboard",
     "//ios/chrome/browser/ui/list_model",
@@ -68,6 +72,7 @@
     "//ios/chrome/browser/ui/ntp/recent_tabs/views",
     "//ios/chrome/browser/ui/popup_menu",
     "//ios/chrome/browser/ui/settings",
+    "//ios/chrome/browser/ui/table_view",
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/common",
@@ -86,6 +91,17 @@
   ]
 }
 
+source_set("history_ui") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "history_table_view_controller.h",
+    "history_table_view_controller.mm",
+  ]
+  deps = [
+    "//ios/chrome/browser/ui/table_view",
+  ]
+}
+
 source_set("history_base_feature") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
diff --git a/ios/chrome/browser/ui/history/history_coordinator.h b/ios/chrome/browser/ui/history/history_coordinator.h
new file mode 100644
index 0000000..1ebeb3b
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_coordinator.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
+
+@protocol ApplicationCommands;
+@protocol BrowserCommands;
+@protocol UrlLoader;
+
+// Coordinator that presents History.
+@interface HistoryCoordinator : ChromeCoordinator
+// The dispatcher for this Coordinator.
+@property(nonatomic, weak) id<ApplicationCommands, BrowserCommands> dispatcher;
+// URL loader being managed by this Coordinator.
+@property(nonatomic, weak) id<UrlLoader> loader;
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/history/history_coordinator.mm b/ios/chrome/browser/ui/history/history_coordinator.mm
new file mode 100644
index 0000000..95a7147
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_coordinator.mm
@@ -0,0 +1,63 @@
+// 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 "ios/chrome/browser/ui/history/history_coordinator.h"
+
+#include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/ui/history/history_table_view_controller.h"
+#import "ios/chrome/browser/ui/table_view/table_container_view_controller.h"
+#import "ios/chrome/browser/ui/util/form_sheet_navigation_controller.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface HistoryCoordinator ()
+// ViewController being managed by this Coordinator.
+@property(nonatomic, strong)
+    TableContainerViewController* historyContainerViewController;
+@end
+
+@implementation HistoryCoordinator
+@synthesize dispatcher = _dispatcher;
+@synthesize historyContainerViewController = _historyContainerViewController;
+@synthesize loader = _loader;
+
+- (void)start {
+  // Initialize and configure HistoryTableViewController.
+  HistoryTableViewController* historyTableViewController =
+      [[HistoryTableViewController alloc] init];
+  historyTableViewController.browserState = self.browserState;
+  historyTableViewController.loader = self.loader;
+
+  // Initialize and configure HistoryContainerViewController.
+  self.historyContainerViewController = [[TableContainerViewController alloc]
+      initWithTable:historyTableViewController];
+  self.historyContainerViewController.title =
+      l10n_util::GetNSString(IDS_HISTORY_TITLE);
+  // TODO(crbug.com/805192): Move this configuration code to
+  // HistoryContainerVC once its created, we will use a dispatcher then.
+  [self.historyContainerViewController.dismissButton setTarget:self];
+  [self.historyContainerViewController.dismissButton setAction:@selector(stop)];
+  self.historyContainerViewController.navigationItem.rightBarButtonItem =
+      self.historyContainerViewController.dismissButton;
+
+  // Present HistoryContainerViewController.
+  FormSheetNavigationController* navController =
+      [[FormSheetNavigationController alloc]
+          initWithRootViewController:self.historyContainerViewController];
+  [navController setModalPresentationStyle:UIModalPresentationFormSheet];
+  [self.baseViewController presentViewController:navController
+                                        animated:YES
+                                      completion:nil];
+}
+
+- (void)stop {
+  [self.historyContainerViewController dismissViewControllerAnimated:YES
+                                                          completion:nil];
+  self.historyContainerViewController = nil;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/history/history_table_view_controller.h b/ios/chrome/browser/ui/history/history_table_view_controller.h
new file mode 100644
index 0000000..85ad50b
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_table_view_controller.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_TABLE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_TABLE_VIEW_CONTROLLER_H_
+
+#import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h"
+
+namespace ios {
+class ChromeBrowserState;
+}
+
+@protocol UrlLoader;
+
+// ChromeTableViewController for displaying history items.
+@interface HistoryTableViewController : ChromeTableViewController
+// The ViewController's BrowserState.
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
+// The UrlLoader used by this ViewController.
+@property(nonatomic, weak) id<UrlLoader> loader;
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/history/history_table_view_controller.mm b/ios/chrome/browser/ui/history/history_table_view_controller.mm
new file mode 100644
index 0000000..f5521f67
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_table_view_controller.mm
@@ -0,0 +1,14 @@
+// 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 "ios/chrome/browser/ui/history/history_table_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation HistoryTableViewController
+@synthesize browserState = _browserState;
+@synthesize loader = _loader;
+@end
diff --git a/ios/chrome/browser/ui/user_feedback_features.cc b/ios/chrome/browser/ui/user_feedback_features.cc
index a7af363..673aabb 100644
--- a/ios/chrome/browser/ui/user_feedback_features.cc
+++ b/ios/chrome/browser/ui/user_feedback_features.cc
@@ -6,3 +6,6 @@
 
 const base::Feature kFeedbackKitV2{"FeedbackKitV2",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kFeedbackKitV2WithSSOService{
+    "FeedbackKitV2WithSSOService", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/ui/user_feedback_features.h b/ios/chrome/browser/ui/user_feedback_features.h
index a590c51..9491a81 100644
--- a/ios/chrome/browser/ui/user_feedback_features.h
+++ b/ios/chrome/browser/ui/user_feedback_features.h
@@ -10,4 +10,8 @@
 // Feature flag to enable FeedbackKit V2
 extern const base::Feature kFeedbackKitV2;
 
+// Feature flag to send SSOService to FeedbackKit V2. This feature flag is
+// used only if kFeedbackKitV2 is enabled.
+extern const base::Feature kFeedbackKitV2WithSSOService;
+
 #endif  // IOS_CHROME_BROWSER_UI_USER_FEEDBACK_FEATURES_H_
diff --git a/ios/web/features.mm b/ios/web/features.mm
index 4cc5466..d76ecb6 100644
--- a/ios/web/features.mm
+++ b/ios/web/features.mm
@@ -17,7 +17,7 @@
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kWKHTTPSystemCookieStore{"WKHTTPSystemCookieStore",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
+                                             base::FEATURE_ENABLED_BY_DEFAULT};
 
 }  // namespace features
 }  // namespace web
diff --git a/media/audio/fuchsia/audio_output_stream_fuchsia.cc b/media/audio/fuchsia/audio_output_stream_fuchsia.cc
index f748492..05b4c3c6 100644
--- a/media/audio/fuchsia/audio_output_stream_fuchsia.cc
+++ b/media/audio/fuchsia/audio_output_stream_fuchsia.cc
@@ -5,6 +5,7 @@
 #include "media/audio/fuchsia/audio_output_stream_fuchsia.h"
 
 #include <media/audio.h>
+#include <zircon/syscalls.h>
 
 #include "media/audio/fuchsia/audio_manager_fuchsia.h"
 #include "media/base/audio_sample_types.h"
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index e9e4517..8cb80aa 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1386,7 +1386,20 @@
   // has been told about the ReadyState change.
   if (attempting_suspended_start_ &&
       pipeline_controller_.IsPipelineSuspended()) {
+    skip_metrics_due_to_startup_suspend_ = true;
     OnBufferingStateChangeInternal(BUFFERING_HAVE_ENOUGH, true);
+
+    // If |skip_metrics_due_to_startup_suspend_| is unset by a resume started by
+    // the OnBufferingStateChangeInternal() call, record a histogram of it here.
+    //
+    // If the value is unset, that means we should not have suspended and we've
+    // likely incurred some cost to TimeToFirstFrame and TimeToPlayReady which
+    // will be reflected in those statistics.
+    base::UmaHistogramBoolean(
+        std::string("Media.PreloadMetadataSuspendWasIdeal.") +
+            ((HasVideo() && HasAudio()) ? "AudioVideo"
+                                        : (HasVideo() ? "Video" : "Audio")),
+        skip_metrics_due_to_startup_suspend_);
   }
 
   attempting_suspended_start_ = false;
@@ -1415,6 +1428,20 @@
 }
 
 void WebMediaPlayerImpl::OnBeforePipelineResume() {
+  // We went through suspended startup, so the player is only just now spooling
+  // up for playback. As such adjust |load_start_time_| so it reports the same
+  // metric as what would be reported if we had not suspended at startup.
+  if (skip_metrics_due_to_startup_suspend_) {
+    // In the event that the call to SetReadyState() initiated after pipeline
+    // startup immediately tries to start playback, we should not update
+    // |load_start_time_| to avoid losing visibility into the impact of a
+    // suspended startup on the time until first frame / play ready for cases
+    // where suspended startup was applied incorrectly.
+    if (!attempting_suspended_start_)
+      load_start_time_ = base::TimeTicks::Now() - time_to_metadata_;
+    skip_metrics_due_to_startup_suspend_ = false;
+  }
+
   // Enable video track if we disabled it in the background - this way the new
   // renderer will attach its callbacks to the video stream properly.
   // TODO(avayvod): Remove this when disabling and enabling video tracks in
@@ -1533,9 +1560,12 @@
 void WebMediaPlayerImpl::OnMetadata(PipelineMetadata metadata) {
   DVLOG(1) << __func__;
   DCHECK(main_task_runner_->BelongsToCurrentThread());
-  const base::TimeDelta elapsed = base::TimeTicks::Now() - load_start_time_;
-  media_metrics_provider_->SetTimeToMetadata(elapsed);
-  RecordTimingUMA("Media.TimeToMetadata", elapsed);
+
+  // Cache the |time_to_metadata_| to use for adjusting the TimeToFirstFrame and
+  // TimeToPlayReady metrics later if we end up doing a suspended startup.
+  time_to_metadata_ = base::TimeTicks::Now() - load_start_time_;
+  media_metrics_provider_->SetTimeToMetadata(time_to_metadata_);
+  RecordTimingUMA("Media.TimeToMetadata", time_to_metadata_);
 
   pipeline_metadata_ = metadata;
 
@@ -1665,31 +1695,40 @@
       playback_rate_ == 0.0 ? 1.0 : playback_rate_);
 }
 
-void WebMediaPlayerImpl::OnBufferingStateChangeInternal(BufferingState state,
-                                                        bool force_update) {
+void WebMediaPlayerImpl::OnBufferingStateChangeInternal(
+    BufferingState state,
+    bool for_suspended_start) {
   DVLOG(1) << __func__ << "(" << state << ")";
   DCHECK(main_task_runner_->BelongsToCurrentThread());
 
   // Ignore buffering state changes until we've completed all outstanding
-  // operations unless we've been asked to force the update.
-  if (!pipeline_controller_.IsStable() && !force_update)
+  // operations unless this is a buffering update for a suspended startup.
+  if (!pipeline_controller_.IsStable() && !for_suspended_start)
     return;
 
-  media_log_->AddEvent(media_log_->CreateBufferingStateChangedEvent(
-      "pipeline_buffering_state", state));
+  auto log_event = media_log_->CreateBufferingStateChangedEvent(
+      "pipeline_buffering_state", state);
+  log_event->params.SetBoolean("for_suspended_start", for_suspended_start);
+  media_log_->AddEvent(std::move(log_event));
 
   if (state == BUFFERING_HAVE_ENOUGH) {
     TRACE_EVENT1("media", "WebMediaPlayerImpl::BufferingHaveEnough", "id",
                  media_log_->id());
-    SetReadyState(CanPlayThrough() ? WebMediaPlayer::kReadyStateHaveEnoughData
-                                   : WebMediaPlayer::kReadyStateHaveFutureData);
-    if (!have_reported_time_to_play_ready_) {
+    // The SetReadyState() call below may clear
+    // |skip_metrics_due_to_startup_suspend_| so report this first.
+    if (!have_reported_time_to_play_ready_ &&
+        !skip_metrics_due_to_startup_suspend_) {
+      DCHECK(!for_suspended_start);
       have_reported_time_to_play_ready_ = true;
       const base::TimeDelta elapsed = base::TimeTicks::Now() - load_start_time_;
       media_metrics_provider_->SetTimeToPlayReady(elapsed);
       RecordTimingUMA("Media.TimeToPlayReady", elapsed);
     }
 
+    // Warning: This call may be re-entrant.
+    SetReadyState(CanPlayThrough() ? WebMediaPlayer::kReadyStateHaveEnoughData
+                                   : WebMediaPlayer::kReadyStateHaveFutureData);
+
     // Let the DataSource know we have enough data. It may use this information
     // to release unused network connections.
     if (data_source_)
@@ -3058,6 +3097,7 @@
 
 void WebMediaPlayerImpl::OnFirstFrame(base::TimeTicks frame_time) {
   DCHECK(!load_start_time_.is_null());
+  DCHECK(!skip_metrics_due_to_startup_suspend_);
   const base::TimeDelta elapsed = frame_time - load_start_time_;
   media_metrics_provider_->SetTimeToFirstFrame(elapsed);
   RecordTimingUMA("Media.TimeToFirstFrame", elapsed);
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 14ddfd9..aded24e 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -533,10 +533,10 @@
   bool CanPlayThrough();
 
   // Internal implementation of Pipeline::Client::OnBufferingStateChange(). When
-  // |force_update| is true, the given state will be set even if the pipeline is
-  // not currently stable.
+  // |for_suspended_start| is true, the given state will be set even if the
+  // pipeline is not currently stable.
   void OnBufferingStateChangeInternal(BufferingState state,
-                                      bool force_update = false);
+                                      bool for_suspended_start = false);
 
   // Records |natural_size| to MediaLog and video height to UMA.
   void RecordVideoNaturalSize(const gfx::Size& natural_size);
@@ -785,6 +785,12 @@
 
   // The time at which DoLoad() is executed.
   base::TimeTicks load_start_time_;
+
+  // Time elapsed time from |load_start_time_| to OnMetadata(). Used to later
+  // adjust |load_start_time_| if a suspended startup occurred.
+  base::TimeDelta time_to_metadata_;
+  bool skip_metrics_due_to_startup_suspend_ = false;
+
   bool have_reported_time_to_play_ready_ = false;
 
   // Records pipeline statistics for describing media capabilities.
diff --git a/mojo/edk/system/channel_fuchsia.cc b/mojo/edk/system/channel_fuchsia.cc
index e25cc551..6a87fc7 100644
--- a/mojo/edk/system/channel_fuchsia.cc
+++ b/mojo/edk/system/channel_fuchsia.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/containers/circular_deque.h"
 #include "base/files/scoped_file.h"
+#include "base/fuchsia/scoped_zx_handle.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
diff --git a/net/base/ip_address.cc b/net/base/ip_address.cc
index 5443206..9fb2568 100644
--- a/net/base/ip_address.cc
+++ b/net/base/ip_address.cc
@@ -250,15 +250,10 @@
 }
 
 bool IPAddress::AssignFromIPLiteral(const base::StringPiece& ip_literal) {
-  IPAddressBytes number;
-
-  // TODO(rch): change the contract so ip_address_ is cleared on failure,
-  // to avoid needing this temporary at all.
-  if (!ParseIPLiteralToBytes(ip_literal, &number))
-    return false;
-
-  ip_address_ = number;
-  return true;
+  bool success = ParseIPLiteralToBytes(ip_literal, &ip_address_);
+  if (!success)
+    ip_address_.Resize(0);
+  return success;
 }
 
 std::vector<uint8_t> IPAddress::CopyBytesToVector() const {
diff --git a/net/base/ip_address.h b/net/base/ip_address.h
index 72f9d75..83d184a 100644
--- a/net/base/ip_address.h
+++ b/net/base/ip_address.h
@@ -176,6 +176,9 @@
 
   // Parses an IP address literal (either IPv4 or IPv6) to its numeric value.
   // Returns true on success and fills |ip_address_| with the numeric value.
+  //
+  // When parsing fails, the original value of |this| will be overwritten such
+  // that |this->empty()| and |!this->IsValid()|.
   bool AssignFromIPLiteral(const base::StringPiece& ip_literal)
       WARN_UNUSED_RESULT;
 
@@ -248,7 +251,8 @@
 // Parses an IP block specifier from CIDR notation to an
 // (IP address, prefix length) pair. Returns true on success and fills
 // |*ip_address| with the numeric value of the IP address and sets
-// |*prefix_length_in_bits| with the length of the prefix.
+// |*prefix_length_in_bits| with the length of the prefix. On failure,
+// |ip_address| will be cleared to an empty value.
 //
 // CIDR notation literals can use either IPv4 or IPv6 literals. Some examples:
 //
@@ -262,7 +266,8 @@
 // Parses a URL-safe IP literal (see RFC 3986, Sec 3.2.2) to its numeric value.
 // Returns true on success, and fills |ip_address| with the numeric value.
 // In other words, |hostname| must be an IPv4 literal, or an IPv6 literal
-// surrounded by brackets as in [::1].
+// surrounded by brackets as in [::1]. On failure |ip_address| may have been
+// overwritten and could contain an invalid IPAddress.
 NET_EXPORT bool ParseURLHostnameToAddress(const base::StringPiece& hostname,
                                           IPAddress* ip_address)
     WARN_UNUSED_RESULT;
diff --git a/net/base/ip_address_unittest.cc b/net/base/ip_address_unittest.cc
index d8acf86b..7170f1ad 100644
--- a/net/base/ip_address_unittest.cc
+++ b/net/base/ip_address_unittest.cc
@@ -395,6 +395,20 @@
   EXPECT_FALSE(address.AssignFromIPLiteral("[::1]"));
 }
 
+// Test that a failure calling AssignFromIPLiteral() has the sideffect of
+// clearing the current value.
+TEST(IPAddressTest, AssignFromIPLiteral_ResetOnFailure) {
+  IPAddress address = IPAddress::IPv6Localhost();
+
+  EXPECT_TRUE(address.IsValid());
+  EXPECT_FALSE(address.empty());
+
+  EXPECT_FALSE(address.AssignFromIPLiteral("bad value"));
+
+  EXPECT_FALSE(address.IsValid());
+  EXPECT_TRUE(address.empty());
+}
+
 // Test parsing an IPv4 literal.
 TEST(IPAddressTest, AssignFromIPLiteral_IPv4) {
   IPAddress address;
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 3b4d6ea..db1b149f 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -742,6 +742,12 @@
 // the request should be retried.
 NET_ERROR(SPDY_CLAIMED_PUSHED_STREAM_RESET_BY_SERVER, -374)
 
+// An HTTP transaction was retried too many times due for authentication or
+// invalid certificates. This may be due to a bug in the net stack that would
+// otherwise infinite loop, or if the server or proxy continually requests fresh
+// credentials or presents a fresh invalid certificate.
+NET_ERROR(TOO_MANY_RETRIES, -375)
+
 // The cache does not have the requested entry.
 NET_ERROR(CACHE_MISS, -400)
 
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index 4ad2427..3d99995 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -57,9 +57,6 @@
 // latter case, the cookie callback will be queued in tasks_pending_for_key_
 // while PermanentCookieStore loads cookies for the specified domain key on DB
 // thread.
-//
-// TODO(deanm) Implement CookieMonster, the cookie database.
-//  - Verify that our domain enforcement and non-dotted handling is correct
 class NET_EXPORT CookieMonster : public CookieStore {
  public:
   class PersistentCookieStore;
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 7c069f0..dd3dba8 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -71,9 +71,21 @@
 #include "url/url_canon.h"
 
 namespace {
+
 // Max number of |retry_attempts| (excluding the initial request) after which
 // we give up and show an error page.
 const size_t kMaxRetryAttempts = 2;
+
+// Max number of calls to RestartWith* allowed for a single connection. A single
+// HttpNetworkTransaction should not signal very many restartable errors, but it
+// may occur due to a bug (e.g. https://crbug.com/823387 or
+// https://crbug.com/488043) or simply if the server or proxy requests
+// authentication repeatedly. Although these calls are often associated with a
+// user prompt, in other scenarios (remembered preferences, extensions,
+// multi-leg authentication), they may be triggered automatically. To avoid
+// looping forever, bound the number of restarts.
+const size_t kMaxRestarts = 32;
+
 }  // namespace
 
 namespace net {
@@ -99,7 +111,8 @@
       enable_alternative_services_(true),
       websocket_handshake_stream_base_create_helper_(NULL),
       net_error_details_(),
-      retry_attempts_(0) {}
+      retry_attempts_(0),
+      num_restarts_(0) {}
 
 HttpNetworkTransaction::~HttpNetworkTransaction() {
   if (stream_.get()) {
@@ -157,6 +170,9 @@
   DCHECK(!stream_request_.get());
   DCHECK_EQ(STATE_NONE, next_state_);
 
+  if (!CheckMaxRestarts())
+    return ERR_TOO_MANY_RETRIES;
+
   next_state_ = STATE_CREATE_STREAM;
 
   int rv = DoLoop(OK);
@@ -175,6 +191,9 @@
   DCHECK(!stream_.get());
   DCHECK_EQ(STATE_NONE, next_state_);
 
+  if (!CheckMaxRestarts())
+    return ERR_TOO_MANY_RETRIES;
+
   SSLConfig* ssl_config = response_.cert_request_info->is_proxy ?
       &proxy_ssl_config_ : &server_ssl_config_;
   ssl_config->send_client_cert = true;
@@ -195,6 +214,9 @@
 
 int HttpNetworkTransaction::RestartWithAuth(
     const AuthCredentials& credentials, const CompletionCallback& callback) {
+  if (!CheckMaxRestarts())
+    return ERR_TOO_MANY_RETRIES;
+
   HttpAuth::Target target = pending_auth_target_;
   if (target == HttpAuth::AUTH_NONE) {
     NOTREACHED();
@@ -1686,6 +1708,11 @@
   return (retry_attempts_ >= kMaxRetryAttempts);
 }
 
+bool HttpNetworkTransaction::CheckMaxRestarts() {
+  num_restarts_++;
+  return num_restarts_ < kMaxRestarts;
+}
+
 void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
   if (stream_.get()) {
     stream_->Close(true);
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 74a20838..b6021b71 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -253,6 +253,10 @@
   // HTTP2 or QUIC network errors, and no further retries should be attempted.
   bool HasExceededMaxRetries() const;
 
+  // Increments the number of restarts and returns true if the restart may
+  // proceed.
+  bool CheckMaxRestarts();
+
   // Resets the connection and the request headers for resend.  Called when
   // ShouldResendRequest() is true.
   void ResetConnectionAndRequestForResend();
@@ -421,6 +425,9 @@
   // of times we can retry a request on reused sockets is limited.
   size_t retry_attempts_;
 
+  // Number of times the transaction was restarted via a RestartWith* call.
+  size_t num_restarts_;
+
   DISALLOW_COPY_AND_ASSIGN(HttpNetworkTransaction);
 };
 
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index a4b3c98..e7e1cd3 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -2690,6 +2690,77 @@
   EXPECT_EQ("127.0.0.2:80", endpoint.ToString());
 }
 
+// Test that, if the server requests auth indefinitely, HttpNetworkTransaction
+// will eventually give up.
+TEST_F(HttpNetworkTransactionTest, BasicAuthForever) {
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL("http://www.example.org/");
+  request.traffic_annotation =
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+
+  TestNetLog log;
+  session_deps_.net_log = &log;
+  std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+  HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
+
+  MockWrite data_writes[] = {
+      MockWrite("GET / HTTP/1.1\r\n"
+                "Host: www.example.org\r\n"
+                "Connection: keep-alive\r\n\r\n"),
+  };
+
+  MockRead data_reads[] = {
+      MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+      // Give a couple authenticate options (only the middle one is actually
+      // supported).
+      MockRead("WWW-Authenticate: Basic invalid\r\n"),  // Malformed.
+      MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+      MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
+      MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+      // Large content-length -- won't matter, as connection will be reset.
+      MockRead("Content-Length: 10000\r\n\r\n"),
+      MockRead(SYNCHRONOUS, ERR_FAILED),
+  };
+
+  // After calling trans->RestartWithAuth(), this is the request we should
+  // be issuing -- the final header line contains the credentials.
+  MockWrite data_writes_restart[] = {
+      MockWrite("GET / HTTP/1.1\r\n"
+                "Host: www.example.org\r\n"
+                "Connection: keep-alive\r\n"
+                "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+  };
+
+  StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+                                arraysize(data_writes));
+  session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+  TestCompletionCallback callback;
+  int rv = callback.GetResult(
+      trans.Start(&request, callback.callback(), NetLogWithSource()));
+
+  std::vector<std::unique_ptr<StaticSocketDataProvider>> data_restarts;
+  for (int i = 0; i < 32; i++) {
+    // Check the previous response was a 401.
+    EXPECT_THAT(rv, IsOk());
+    const HttpResponseInfo* response = trans.GetResponseInfo();
+    ASSERT_TRUE(response);
+    EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+    data_restarts.push_back(std::make_unique<StaticSocketDataProvider>(
+        data_reads, arraysize(data_reads), data_writes_restart,
+        arraysize(data_writes_restart)));
+    session_deps_.socket_factory->AddSocketDataProvider(
+        data_restarts.back().get());
+    rv = callback.GetResult(trans.RestartWithAuth(AuthCredentials(kFoo, kBar),
+                                                  callback.callback()));
+  }
+
+  // After too many tries, the transaction should have given up.
+  EXPECT_THAT(rv, IsError(ERR_TOO_MANY_RETRIES));
+}
+
 TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) {
   HttpRequestInfo request;
   request.method = "GET";
diff --git a/notification_helper/notification_helper.cc b/notification_helper/notification_helper.cc
index 2c001bf..9e4119b 100644
--- a/notification_helper/notification_helper.cc
+++ b/notification_helper/notification_helper.cc
@@ -7,10 +7,14 @@
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/persistent_histogram_storage.h"
 #include "base/process/memory.h"
+#include "base/timer/elapsed_timer.h"
 #include "base/win/process_startup_helper.h"
 #include "base/win/scoped_winrt_initializer.h"
 #include "chrome/install_static/product_install_details.h"
+#include "chrome/install_static/user_data_dir.h"
 #include "notification_helper/com_server_module.h"
 #include "notification_helper/notification_helper_crash_reporter_client.h"
 #include "notification_helper/notification_helper_util.h"
@@ -20,6 +24,13 @@
                                HINSTANCE prev_instance,
                                wchar_t* command_line,
                                int show_command) {
+  // Persist histograms so they can be uploaded later.
+  // The allocator name must be kept in sync with the writer in
+  // chrome/browser/metrics/chrome_metrics_service_client.cc.
+  base::PersistentHistogramStorage persistent_histogram_storage(
+      "NotificationHelperMetrics",
+      base::PersistentHistogramStorage::StorageDirCreation::kEnable);
+
   // Initialize the CommandLine singleton from the environment.
   base::CommandLine::Init(0, nullptr);
 
@@ -32,6 +43,13 @@
 
   install_static::InitializeProductDetailsForPrimaryModule();
 
+  // The histogram storage folder should be under folder "User Data".
+  base::string16 user_data_dir;
+  install_static::GetUserDataDirectory(&user_data_dir, nullptr);
+
+  persistent_histogram_storage.set_storage_base_dir(
+      base::FilePath(std::move(user_data_dir)));
+
   // The exit manager is in charge of calling the dtors of singletons.
   base::AtExitManager exit_manager;
 
@@ -54,8 +72,13 @@
     return -1;
   }
 
+  base::ElapsedTimer run_timer;
+
   notification_helper::ComServerModule com_server_module;
   com_server_module.Run();
 
+  base::UmaHistogramMediumTimes(
+      "Notifications.NotificationHelper.ServerRuntime", run_timer.Elapsed());
+
   return 0;
 }
diff --git a/remoting/client/chromoting_client_runtime.cc b/remoting/client/chromoting_client_runtime.cc
index 1e2c7b5b..d478eea 100644
--- a/remoting/client/chromoting_client_runtime.cc
+++ b/remoting/client/chromoting_client_runtime.cc
@@ -31,19 +31,17 @@
 ChromotingClientRuntime::ChromotingClientRuntime() {
   base::TaskScheduler::CreateAndStartWithDefaultParams("Remoting");
 
-  if (!base::MessageLoop::current()) {
-    VLOG(1) << "Starting main message loop";
-    ui_loop_.reset(new base::MessageLoopForUI());
+  DCHECK(!base::MessageLoop::current());
+
+  VLOG(1) << "Starting main message loop";
+  ui_loop_.reset(new base::MessageLoopForUI());
 #if defined(OS_ANDROID)
-    // On Android, the UI thread is managed by Java, so we need to attach and
-    // start a special type of message loop to allow Chromium code to run tasks.
-    ui_loop_->Start();
+  // On Android, the UI thread is managed by Java, so we need to attach and
+  // start a special type of message loop to allow Chromium code to run tasks.
+  ui_loop_->Start();
 #elif defined(OS_IOS)
-    base::MessageLoopForUI::current()->Attach();
+  ui_loop_->Attach();
 #endif  // OS_ANDROID, OS_IOS
-  } else {
-    ui_loop_.reset(base::MessageLoopForUI::current());
-  }
 
 #if defined(DEBUG)
   net::URLFetcher::SetIgnoreCertificateRequests(true);
diff --git a/services/service_manager/sandbox/linux/bpf_gpu_policy_linux.cc b/services/service_manager/sandbox/linux/bpf_gpu_policy_linux.cc
index bc16952..d683aac 100644
--- a/services/service_manager/sandbox/linux/bpf_gpu_policy_linux.cc
+++ b/services/service_manager/sandbox/linux/bpf_gpu_policy_linux.cc
@@ -61,7 +61,20 @@
     case __NR_open:
 #endif  // !defined(__aarch64__)
     case __NR_faccessat:
-    case __NR_openat: {
+    case __NR_openat:
+#if defined(__NR_stat)
+    case __NR_stat:
+#endif
+#if defined(__NR_stat64)
+    case __NR_stat64:
+#endif
+#if defined(__NR_fstatat)
+    case __NR_fstatat:
+#endif
+#if defined(__NR_newfstatat)
+    case __NR_newfstatat:
+#endif
+    {
       auto* broker_process = SandboxLinux::GetInstance()->broker_process();
       DCHECK(broker_process);
       return Trap(BrokerProcess::SIGSYS_Handler, broker_process);
diff --git a/services/shape_detection/detection_utils_win.cc b/services/shape_detection/detection_utils_win.cc
index 458d384..7dff7840 100644
--- a/services/shape_detection/detection_utils_win.cc
+++ b/services/shape_detection/detection_utils_win.cc
@@ -4,12 +4,80 @@
 
 #include "services/shape_detection/detection_utils_win.h"
 
+#include <windows.foundation.collections.h>
+#include <windows.media.faceanalysis.h>
+#include <windows.media.ocr.h>
+#include <wrl/implements.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
 #include "base/numerics/checked_math.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/win/winrt_storage_util.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace shape_detection {
 
+using ABI::Windows::Foundation::Collections::IVector;
+using ABI::Windows::Foundation::IAsyncOperationCompletedHandler;
+using ABI::Windows::Media::FaceAnalysis::DetectedFace;
+using ABI::Windows::Media::Ocr::OcrResult;
+using ABI::Windows::Media::FaceAnalysis::FaceDetector;
+
+template <typename RuntimeType>
+HRESULT AsyncOperation<RuntimeType>::BeginAsyncOperation(
+    typename AsyncOperation<RuntimeType>::Callback callback,
+    typename AsyncOperation<RuntimeType>::IAsyncOperationPtr async_op_ptr) {
+  auto instance =
+      new AsyncOperation<RuntimeType>(std::move(callback), async_op_ptr);
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::SequencedTaskRunnerHandle::Get();
+
+  typedef WRL::Implements<WRL::RuntimeClassFlags<WRL::ClassicCom>,
+                          IAsyncOperationCompletedHandler<RuntimeType*>,
+                          WRL::FtmBase>
+      AsyncCallback;
+  auto async_callback = WRL::Callback<AsyncCallback>(
+      [instance, task_runner](IAsyncOperation<RuntimeType*>* async_op,
+                              AsyncStatus status) {
+        // A reference to |async_op| is kept in |async_op_ptr_|, safe to pass
+        // outside.  This is happening on an OS thread.
+        task_runner->PostTask(
+            FROM_HERE, base::BindOnce(&AsyncOperation::AsyncCallbackInternal,
+                                      base::Owned(instance),
+                                      base::Unretained(async_op), status));
+
+        return S_OK;
+      });
+
+  return async_op_ptr->put_Completed(async_callback.Get());
+}
+
+template HRESULT AsyncOperation<IVector<DetectedFace*>>::BeginAsyncOperation(
+    AsyncOperation<IVector<DetectedFace*>>::Callback callback,
+    AsyncOperation<IVector<DetectedFace*>>::IAsyncOperationPtr async_op_ptr);
+
+template HRESULT AsyncOperation<FaceDetector>::BeginAsyncOperation(
+    AsyncOperation<FaceDetector>::Callback callback,
+    AsyncOperation<FaceDetector>::IAsyncOperationPtr async_op_ptr);
+
+template HRESULT AsyncOperation<OcrResult>::BeginAsyncOperation(
+    AsyncOperation<OcrResult>::Callback callback,
+    AsyncOperation<OcrResult>::IAsyncOperationPtr async_op_ptr);
+
+template <typename RuntimeType>
+void AsyncOperation<RuntimeType>::AsyncCallbackInternal(
+    IAsyncOperation<RuntimeType*>* async_op,
+    AsyncStatus status) {
+  DCHECK_EQ(async_op, async_op_ptr_.Get());
+
+  std::move(callback_).Run((async_op && status == AsyncStatus::Completed)
+                               ? std::move(async_op_ptr_)
+                               : nullptr);
+}
+
 WRL::ComPtr<ISoftwareBitmap> CreateWinBitmapFromSkBitmap(
     const SkBitmap& bitmap,
     ISoftwareBitmapStatics* bitmap_factory) {
diff --git a/services/shape_detection/detection_utils_win.h b/services/shape_detection/detection_utils_win.h
index 3e0f4fc..5f52210 100644
--- a/services/shape_detection/detection_utils_win.h
+++ b/services/shape_detection/detection_utils_win.h
@@ -8,14 +8,9 @@
 #include <windows.storage.streams.h>
 #include <wrl/client.h>
 #include <wrl/event.h>
-#include <wrl/implements.h>
-#include <utility>
 
-#include "base/bind.h"
 #include "base/callback.h"
-#include "base/logging.h"
 #include "base/macros.h"
-#include "base/sequenced_task_runner.h"
 
 class SkBitmap;
 
@@ -24,7 +19,6 @@
 namespace WRL = Microsoft::WRL;
 
 using ABI::Windows::Foundation::IAsyncOperation;
-using ABI::Windows::Foundation::IAsyncOperationCompletedHandler;
 using ABI::Windows::Graphics::Imaging::ISoftwareBitmapStatics;
 using ABI::Windows::Graphics::Imaging::ISoftwareBitmap;
 using ABI::Windows::Graphics::Imaging::BitmapPixelFormat;
@@ -46,32 +40,7 @@
   // Creates an AsyncOperation instance which sets |callback| to be called when
   // the asynchronous action completes.
   static HRESULT BeginAsyncOperation(Callback callback,
-                                     IAsyncOperationPtr async_op_ptr) {
-    auto instance =
-        new AsyncOperation<RuntimeType>(std::move(callback), async_op_ptr);
-
-    scoped_refptr<base::SequencedTaskRunner> task_runner =
-        base::SequencedTaskRunnerHandle::Get();
-
-    typedef WRL::Implements<WRL::RuntimeClassFlags<WRL::ClassicCom>,
-                            IAsyncOperationCompletedHandler<RuntimeType*>,
-                            WRL::FtmBase>
-        AsyncCallback;
-    auto async_callback = WRL::Callback<AsyncCallback>(
-        [instance, task_runner](IAsyncOperation<RuntimeType*>* async_op,
-                                AsyncStatus status) {
-          // A reference to |async_op| is kept in |async_op_ptr_|, safe to pass
-          // outside.  This is happening on an OS thread.
-          task_runner->PostTask(
-              FROM_HERE, base::BindOnce(&AsyncOperation::AsyncCallbackInternal,
-                                        base::Owned(instance),
-                                        base::Unretained(async_op), status));
-
-          return S_OK;
-        });
-
-    return async_op_ptr->put_Completed(async_callback.Get());
-  }
+                                     IAsyncOperationPtr async_op_ptr);
 
  private:
   AsyncOperation(Callback callback, IAsyncOperationPtr async_op_ptr)
@@ -79,13 +48,7 @@
         callback_(std::move(callback)) {}
 
   void AsyncCallbackInternal(IAsyncOperation<RuntimeType*>* async_op,
-                             AsyncStatus status) {
-    DCHECK_EQ(async_op, async_op_ptr_.Get());
-
-    std::move(callback_).Run((async_op && status == AsyncStatus::Completed)
-                                 ? std::move(async_op_ptr_)
-                                 : nullptr);
-  }
+                             AsyncStatus status);
 
   IAsyncOperationPtr async_op_ptr_;
   Callback callback_;
diff --git a/services/shape_detection/face_detection_impl_win.cc b/services/shape_detection/face_detection_impl_win.cc
index 3bd14b6..3a4c6b9 100644
--- a/services/shape_detection/face_detection_impl_win.cc
+++ b/services/shape_detection/face_detection_impl_win.cc
@@ -4,8 +4,6 @@
 
 #include "services/shape_detection/face_detection_impl_win.h"
 
-#include <windows.media.faceanalysis.h>
-
 #include "base/bind.h"
 #include "base/logging.h"
 
diff --git a/styleguide/c++/c++11.html b/styleguide/c++/c++11.html
index 16923163..ed6cbb1 100644
--- a/styleguide/c++/c++11.html
+++ b/styleguide/c++/c++11.html
@@ -401,6 +401,14 @@
 </tr>
 
 <tr>
+<td>Generic lambdas</td>
+<td><code>[](const auto&amp; x) { <i>...</i> }</code></td>
+<td>Allows lambda argument types to be deduced using <code>auto</code> (according to the rules that apply to templates).</td>
+<td><a href="http://en.cppreference.com/w/cpp/language/lambda">lambda expressions</a></td>
+<td><a href="https://groups.google.com/a/chromium.org/d/topic/cxx/LasGKwE3SFM/discussion">Discussion thread</a></td>
+</tr>
+
+<tr>
 <td>Relaxed constant expressions</td>
 <td><code>constexpr int Factorial(int n) {<br>&nbsp;&nbsp;int result = 1;<br>&nbsp;&nbsp;while (n > 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;result *= n--;<br>&nbsp;&nbsp;return result;<br>}</code></td>
 <td>Allows use of more declarations, conditional statements and loops inside <code>constexpr</code> functions.</td>
@@ -782,14 +790,6 @@
 <td>Temporarily banned since it <a href="https://bugs.llvm.org/show_bug.cgi?id=34185">can cause infinite loops in clang</a>. We expect to allow this once that bug is fixed. Usage should be rare, primarily for abstract template code. <a href="https://groups.google.com/a/chromium.org/d/topic/cxx/-Ox7YgRS_no/discussion">Discussion thread</a></td>
 </tr>
 
-<tr>
-<td>Generic lambdas</td>
-<td><code>[](const auto&amp; x) { <i>...</i> }</code></td>
-<td>Allows lambda argument types to be deduced using <code>auto</code> (according to the rules that apply to templates).</td>
-<td><a href="http://en.cppreference.com/w/cpp/language/lambda">lambda expressions</a></td>
-<td>Temporarily banned since it <a href="https://bugs.llvm.org/show_bug.cgi?id=33561">can cause infinite loops in clang</a>. We expect to allow this once that bug is fixed. <a href="https://chromium-review.googlesource.com/#/c/chromium/src/+/855776">Discussion thread</a></td>
-</tr>
-
 </tbody>
 </table>
 
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 1e84dc8..74d0f4f8 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -767,6 +767,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -798,6 +810,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -810,6 +828,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -862,6 +886,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -905,6 +935,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -1011,6 +1047,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -1027,6 +1069,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -1058,6 +1112,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -1070,6 +1130,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -1122,6 +1188,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -1165,6 +1237,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -1271,6 +1349,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -1287,6 +1371,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -1311,6 +1407,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -1323,6 +1425,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -1375,6 +1483,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -1418,6 +1532,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -1512,6 +1632,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -1528,6 +1654,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -1571,6 +1709,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -1583,6 +1727,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -1641,6 +1791,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -1684,6 +1840,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -1802,6 +1964,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -1824,6 +1992,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -1867,6 +2047,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -1879,6 +2065,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -1937,6 +2129,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -1980,6 +2178,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -2098,6 +2302,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -2120,6 +2330,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -2163,6 +2385,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -2175,6 +2403,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -2233,6 +2467,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -2276,6 +2516,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -2394,6 +2640,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -2416,6 +2668,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -2459,6 +2723,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -2471,6 +2741,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -2529,6 +2805,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -2572,6 +2854,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -2690,6 +2978,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -2712,6 +3006,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -2755,6 +3061,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -2767,6 +3079,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -2825,6 +3143,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -2868,6 +3192,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -2986,6 +3316,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -3008,6 +3344,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -3051,6 +3399,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3063,6 +3417,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -3121,6 +3481,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -3164,6 +3530,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -3282,6 +3654,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -3304,6 +3682,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -3347,6 +3737,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3359,6 +3755,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -3417,6 +3819,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -3460,6 +3868,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -3578,6 +3992,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -3600,6 +4020,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -3643,6 +4075,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3655,6 +4093,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -3713,6 +4157,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -3756,6 +4206,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -3874,6 +4330,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -3896,6 +4358,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -3939,6 +4413,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3951,6 +4431,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -4009,6 +4495,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -4052,6 +4544,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -4170,6 +4668,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -4192,6 +4696,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -4235,6 +4751,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -4247,6 +4769,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -4305,6 +4833,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -4348,6 +4882,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -4466,6 +5006,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -7931,6 +8477,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -7986,6 +8544,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -8174,6 +8738,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       }
     ]
@@ -8190,6 +8760,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "angle_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "aura_unittests"
       },
       {
@@ -8853,6 +9435,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "aura_unittests"
       },
       {
@@ -9799,6 +10387,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -9860,6 +10460,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -9967,6 +10573,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -10085,6 +10697,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -10107,6 +10725,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -10168,6 +10798,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -10226,6 +10862,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -10269,6 +10911,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -10387,6 +11035,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -10409,6 +11063,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -10452,6 +11118,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -10464,6 +11136,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -10522,6 +11200,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -10565,6 +11249,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -10683,6 +11373,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -10705,6 +11401,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -10748,6 +11456,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -10760,6 +11474,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -10818,6 +11538,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -10861,6 +11587,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -10979,6 +11711,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -11001,6 +11739,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -11044,6 +11794,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -11056,6 +11812,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -11114,6 +11876,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -11157,6 +11925,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -11275,6 +12049,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -11297,6 +12077,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -11340,6 +12132,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -11352,6 +12150,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -11410,6 +12214,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -11453,6 +12263,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -11571,6 +12387,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -11593,6 +12415,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -11636,6 +12470,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -11648,6 +12488,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -11706,6 +12552,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -11749,6 +12601,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -11867,6 +12725,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -11889,6 +12753,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -11932,6 +12808,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -11944,6 +12826,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -12002,6 +12890,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -12045,6 +12939,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -12163,6 +13063,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -12371,6 +13277,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -12517,6 +13429,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -12560,6 +13484,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -12572,6 +13502,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -12630,6 +13566,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -12673,6 +13615,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -12791,6 +13739,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wtf_unittests"
       },
       {
@@ -12819,6 +13773,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "aura_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 79b672a..331933f 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -303,6 +303,15 @@
       },
       {
         "args": [
+          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete_o2.json",
+          "--additional-apk=src/third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
+          "--additional-apk=src/third_party/gvr-android-sdk/test-apks/daydream_home/daydream_home_current.apk"
+        ],
+        "name": "chrome_public_test_vr_apk (daydream, O2)",
+        "test": "chrome_public_test_vr_apk"
+      },
+      {
+        "args": [
           "--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json",
           "--additional-apk=src/third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
           "--additional-apk=src/third_party/gvr-android-sdk/test-apks/daydream_home/daydream_home_current.apk",
@@ -2699,6 +2708,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -2730,6 +2751,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -2742,6 +2769,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -2794,6 +2827,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -2837,6 +2876,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -2943,6 +2988,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -2959,6 +3010,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -2990,6 +3053,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3002,6 +3071,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -3054,6 +3129,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -3097,6 +3178,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -3203,6 +3290,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -3219,6 +3312,18 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "app_shell_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "aura_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "base_unittests"
       },
       {
@@ -3243,6 +3348,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "chrome_elf_import_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "chrome_elf_unittests"
       },
       {
@@ -3255,6 +3366,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "compositor_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "content_browsertests"
       },
       {
@@ -3307,6 +3424,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "install_static_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
@@ -3350,6 +3473,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
@@ -3444,6 +3573,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "wm_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "zucchini_unittests"
       }
     ]
@@ -7454,6 +7589,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "mini_installer_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "native_theme_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index 7699c8f..5903956e 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -893,6 +893,14 @@
         "test": "installer_util_unittests"
       },
       {
+        "experiment_percentage": 100,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "shards": 2
+        },
+        "test": "interactive_ui_tests"
+      },
+      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index b1be0c2..d160e9b2 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -51,8 +51,6 @@
   },
   'angle_unittests': {
     'remove_from': [
-      # chromium.clang
-      'ToTLinuxMSan',
       # chromium.fyi
       'mac-views-rel',
       'Chromium Mac 10.11',
@@ -69,37 +67,7 @@
   },
   'app_shell_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTLinuxASan',
-      'ToTLinuxMSan',
-      'ToTLinuxUBSanVptr',
-      'ToTWin',
-      'ToTWin(dbg)',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
-      'UBSanVptr Linux',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
       # chromium.memory
@@ -119,34 +87,7 @@
   },
   'aura_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTLinuxASan',
-      'ToTWin',
-      'ToTWin(dbg)',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
       # chromium.memory
@@ -557,31 +498,7 @@
   },
   'chrome_elf_import_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
     ],
@@ -1007,34 +924,7 @@
   },
   'compositor_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTLinuxASan',
-      'ToTWin',
-      'ToTWin(dbg)',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
       # chromium.memory
@@ -1873,32 +1763,7 @@
   },
   'install_static_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTWin(dbg)',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
     ],
@@ -1912,8 +1777,6 @@
       'Chromium Win 10 GCE Tests',
       # chromium.memory
       'Linux TSan Tests',
-      # chromium.win; temporary, https://crbug.com/826735
-      'Win10 Tests x64',
     ],
     'modifications': {
       # chromium.clang
@@ -2005,6 +1868,10 @@
           'shards': 3,
         },
       },
+      # chromium.win; temporary, https://crbug.com/826735
+      'Win10 Tests x64': {
+        'experiment_percentage': 100,
+      },
     },
   },
   'ipc_tests': {
@@ -2306,6 +2173,12 @@
       'Android CFI',
     ],
   },
+  'mini_installer_tests': {
+    'remove_from': [
+      # chromium.clang
+      'linux-win_cross-rel',  # https://crbug.com/799827
+    ],
+  },
   'mojo_common_unittests': {
     'remove_from': [
       # On chromium.android, unclear why these aren't run on all bots.
@@ -4002,34 +3875,7 @@
   },
   'wm_unittests': {
     'remove_from': [
-      # chromium.clang
-      'CrWinAsan',
-      'CrWinAsan(dll)',
-      'CrWinAsanCov',
-      'CrWinClang',
-      'CrWinClang(dbg)',
-      'CrWinClang(shared)',
-      'CrWinClang64',
-      'CrWinClang64(dbg)',
-      'CrWinClang64(dll)',
-      'CrWinClangLLD',
-      'CrWinClangLLD64',
-      'CrWinClngLLD64dbg',
-      'CrWinClngLLDdbg',
-      'ToTLinuxASan',
-      'ToTWin',
-      'ToTWin(dbg)',
-      'ToTWin(dll)',
-      'ToTWin64',
-      'ToTWin64(dbg)',
-      'ToTWin64(dll)',
-      'ToTWinCFI',
-      'ToTWinCFI64',
-      'ToTWinThinLTO64',
       # chromium.fyi
-      'CrWinAsan tester',
-      'CrWinAsan(dll) tester',
-      'CrWinAsanCov tester',
       'Out of Process Profiling Windows',
       'Win 10 Fast Ring',
       # chromium.memory
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index cbaa8301..02f7c0c 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -189,6 +189,14 @@
       ],
       'test': 'chrome_public_test_vr_apk',
     },
+    'chrome_public_test_vr_apk (daydream, O2)': {
+      'args': [
+        '--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete_o2.json',
+        '--additional-apk=src/third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk',
+        '--additional-apk=src/third_party/gvr-android-sdk/test-apks/daydream_home/daydream_home_current.apk',
+      ],
+      'test': 'chrome_public_test_vr_apk',
+    },
     'chrome_public_test_vr_apk (don enabled)': {
       'args': [
         '--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json',
@@ -2241,6 +2249,7 @@
     'aura_gtests',
     'chromium_gtests',
     'chromium_gtests_for_devices_with_graphical_output',
+    'mini_installer_tests_isolated_tests',
     'non_android_chromium_gtests',
     'non_android_and_cast_chromium_gtests',
     'non_android_and_cast_and_chromeos_chromium_gtests',
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index e49ea559..1149935 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -98,6 +98,7 @@
 crbug.com/591099 animations/interpolation/webkit-clip-path-interpolation.html [ Pass Timeout ]
 crbug.com/591099 animations/rotate-transform-equivalent.html [ Failure ]
 crbug.com/591099 animations/timing/timing-model.html [ Timeout ]
+crbug.com/591099 bluetooth/requestDevice/chooser/new-scan-device-added.html [ Crash ]
 crbug.com/714962 compositing/background-color/view-blending-base-background.html [ Failure ]
 crbug.com/591099 compositing/draws-content/canvas-background-layer.html [ Failure ]
 crbug.com/591099 compositing/draws-content/webgl-background-layer.html [ Failure ]
@@ -288,6 +289,7 @@
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-percents-001.xht [ Failure ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-remove-006.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/root-box-001.xht [ Failure ]
+crbug.com/591099 external/wpt/css/CSS2/text/white-space-mixed-003.xht [ Pass ]
 crbug.com/714962 external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-5.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-image-5.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-backgrounds/box-shadow-syntax-001.xht [ Failure ]
@@ -736,7 +738,7 @@
 crbug.com/591099 external/wpt/editing/run/outdent.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/removeformat.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/strikethrough.html [ Timeout ]
-crbug.com/591099 external/wpt/editing/run/subscript.html [ Timeout ]
+crbug.com/591099 external/wpt/editing/run/subscript.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/editing/run/superscript.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/underline.html [ Timeout ]
 crbug.com/591099 external/wpt/encoding/api-invalid-label.html [ Timeout ]
@@ -1239,7 +1241,7 @@
 crbug.com/714962 fast/events/drag-in-frames.html [ Failure ]
 crbug.com/714962 fast/events/event-on-culled_inline.html [ Failure ]
 crbug.com/591099 fast/events/keyboardevent-getModifierState.html [ Timeout ]
-crbug.com/714962 fast/events/middleClickAutoscroll-latching.html [ Timeout ]
+crbug.com/714962 fast/events/middleClickAutoscroll-latching.html [ Pass Timeout ]
 crbug.com/714962 fast/events/mouse-down-on-pseudo-element-remove-crash.html [ Failure ]
 crbug.com/591099 fast/events/mouse-event-buttons-attribute.html [ Timeout ]
 crbug.com/591099 fast/events/mouse-relative-position.html [ Failure ]
@@ -1249,7 +1251,7 @@
 crbug.com/591099 fast/events/pointerevents/mouse-pointer-capture.html [ Timeout ]
 crbug.com/591099 fast/events/pointerevents/mouse-pointer-event-properties.html [ Timeout ]
 crbug.com/591099 fast/events/pointerevents/mouse-pointer-preventdefault.html [ Timeout ]
-crbug.com/591099 fast/events/pointerevents/multi-pointer-preventdefault.html [ Timeout ]
+crbug.com/591099 fast/events/pointerevents/multi-pointer-preventdefault.html [ Pass Timeout ]
 crbug.com/591099 fast/events/pointerevents/touch-capture-in-iframe.html [ Timeout ]
 crbug.com/591099 fast/events/pointerevents/touch-capture.html [ Timeout ]
 crbug.com/591099 fast/events/select-element.html [ Timeout ]
@@ -1309,7 +1311,7 @@
 crbug.com/591099 fast/js/dfg-arguments-alias-activation.html [ Timeout ]
 crbug.com/591099 fast/js/dfg-byte-array-put.html [ Timeout ]
 crbug.com/591099 fast/js/document-all-triggers-masquerades-watchpoint.html [ Timeout ]
-crbug.com/591099 fast/layout/scroll-anchoring/fullscreen-crash.html [ Crash ]
+crbug.com/591099 fast/layout/scroll-anchoring/fullscreen-crash.html [ Crash Pass ]
 crbug.com/591099 fast/lists/001-vertical.html [ Failure ]
 crbug.com/591099 fast/lists/001.html [ Failure ]
 crbug.com/591099 fast/lists/003-vertical.html [ Failure ]
@@ -1519,7 +1521,8 @@
 crbug.com/591099 fast/table/percent-height-content-in-fixed-height-border-box-sized-cell-with-collapsed-border.html [ Failure ]
 crbug.com/591099 fast/table/percent-height-content-in-fixed-height-content-box-sized-cell.html [ Failure ]
 crbug.com/591099 fast/table/percent-height-overflow-auto-content-in-cell.html [ Failure Pass ]
-crbug.com/591099 fast/table/percent-height-overflow-scroll-content-in-cell.html [ Failure ]
+crbug.com/591099 fast/table/percent-height-overflow-scroll-content-in-cell.html [ Failure Pass ]
+crbug.com/591099 fast/table/percent-height-replaced-content-in-cell.html [ Failure ]
 crbug.com/591099 fast/table/percent-widths-stretch-vertical.html [ Failure ]
 crbug.com/591099 fast/table/table-display-types-vertical.html [ Failure ]
 crbug.com/591099 fast/table/unbreakable-images-quirk.html [ Failure ]
@@ -1729,7 +1732,7 @@
 crbug.com/591099 http/tests/devtools/editor/text-editor-block-indent.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/editor/text-editor-ctrl-d-1.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/editor/text-editor-ctrl-d-2.js [ Timeout ]
-crbug.com/714962 http/tests/devtools/editor/text-editor-formatter.js [ Timeout ]
+crbug.com/714962 http/tests/devtools/editor/text-editor-formatter.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/editor/text-editor-word-jumps.js [ Pass Timeout ]
 crbug.com/714962 http/tests/devtools/elements/edit/edit-dom-actions-4.js [ Crash ]
 crbug.com/591099 http/tests/devtools/elements/elements-panel-rewrite-href.js [ Failure Pass ]
@@ -1765,7 +1768,7 @@
 crbug.com/591099 http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Failure ]
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-appcache.html [ Crash Failure ]
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.html [ Crash Failure Pass ]
-crbug.com/591099 http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash ]
+crbug.com/591099 http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash Pass ]
 crbug.com/591099 http/tests/security/shape-image-cors-allow-origin.html [ Failure ]
 crbug.com/591099 http/tests/security/shape-image-cors-data-url.html [ Failure ]
 crbug.com/591099 http/tests/security/shape-image-cors-same-origin.html [ Failure ]
@@ -2284,7 +2287,7 @@
 crbug.com/714962 virtual/mouseevent_fractional/fast/events/event-on-culled_inline.html [ Failure ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/keyboardevent-getModifierState.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/menu-key-context-menu-document-pinch-zoom.html [ Failure Pass ]
-crbug.com/714962 virtual/mouseevent_fractional/fast/events/middleClickAutoscroll-latching.html [ Timeout ]
+crbug.com/714962 virtual/mouseevent_fractional/fast/events/middleClickAutoscroll-latching.html [ Pass Timeout ]
 crbug.com/714962 virtual/mouseevent_fractional/fast/events/mouse-down-on-pseudo-element-remove-crash.html [ Failure ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/mouse-event-buttons-attribute.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/mouse-relative-position.html [ Failure ]
@@ -2293,7 +2296,7 @@
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/mouse-pointer-capture-transition-events.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/mouse-pointer-capture.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/mouse-pointer-preventdefault.html [ Timeout ]
-crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/multi-pointer-preventdefault.html [ Timeout ]
+crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/multi-pointer-preventdefault.html [ Pass Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/touch-capture-in-iframe.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/pointerevents/touch-capture.html [ Timeout ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/select-element.html [ Timeout ]
@@ -2323,3 +2326,4 @@
 crbug.com/591099 webexposed/global-interface-listing-dedicated-worker.html [ Timeout ]
 crbug.com/591099 webexposed/global-interface-listing-shared-worker.html [ Timeout ]
 crbug.com/591099 webexposed/global-interface-listing.html [ Timeout ]
+crbug.com/591099 xr/webGLCanvasContext_set_device_lost_context.html [ Crash ]
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
index c3eefef..80b8db0 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
@@ -68,6 +68,7 @@
 
 Bug(none) virtual/android/ [ Skip ]
 Bug(none) virtual/color_space/ [ Skip ]
+Bug(none) virtual/disable-rls/ [ Skip ]
 Bug(none) virtual/enable_asmjs/ [ Skip ]
 Bug(none) virtual/enable_wasm/ [ Skip ]
 Bug(none) virtual/exotic-color-space/ [ Skip ]
@@ -1454,3 +1455,5 @@
 Bug(none) paint/invalidation/reflection/scroll-fixed-layer-with-reflection.html [ Failure ]
 Bug(none) paint/invalidation/reflection/scroll-fixed-reflected-layer.html [ Failure ]
 Bug(none) svg/transforms/text-with-mask-with-svg-transform.svg [ Failure ]
+
+Bug(none) compositing/squashing/frame-clip-squashed-scrolled.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 09cbda7..37cd02a 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1300,8 +1300,6 @@
 crbug.com/736319 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-006.html [ Failure ]
 crbug.com/736319 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-006a.html [ Failure ]
 
-crbug.com/825349 http/tests/devtools/console/command-line-api.js [ Skip ]
-
 crbug.com/752449 [ Mac10.12 Mac10.13 Retina ] external/wpt/css/css-fonts/matching/fixed-stretch-style-over-weight.html [ Failure ]
 crbug.com/752449 [ Mac10.12 Retina ] external/wpt/css/css-fonts/matching/stretch-distance-over-weight-distance.html [ Failure ]
 crbug.com/752449 [ Mac10.12 Retina ] external/wpt/css/css-fonts/matching/style-ranges-over-weight-direction.html [ Failure ]
@@ -3290,7 +3288,7 @@
 crbug.com/802835 external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html [ Failure ]
 crbug.com/802835 virtual/outofblink-cors/external/wpt/fetch/corb/script-html-correctly-labeled.tentative.sub.html [ Failure ]
 
-crbug.com/813704 [ Win7 Mac ] http/tests/images/png-partial-load-as-document.html [ Failure Pass ]
+crbug.com/813704 http/tests/images/png-partial-load-as-document.html [ Failure Pass ]
 
 crbug.com/806645 [ Win7 ] http/tests/devtools/elements/elements-panel-rewrite-href.js [ Failure Pass ]
 
@@ -3383,5 +3381,5 @@
 
 # Sheriff 2018-03-29
 crbug.com/827088 bluetooth/requestDevice/chooser/new-scan-device-added.html [ Pass Crash ]
-crbug.com/827209 [ Win7 ] fast/events/middleClickAutoscroll-latching.html [ Pass Failure ]
-crbug.com/827209 [ Win7 ] virtual/mouseevent_fractional/fast/events/middleClickAutoscroll-latching.html [ Pass Failure ]
+crbug.com/827209 fast/events/middleClickAutoscroll-latching.html [ Pass Failure ]
+crbug.com/827209 virtual/mouseevent_fractional/fast/events/middleClickAutoscroll-latching.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index 790a03f..5f3352b 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -293,6 +293,12 @@
     "args": ["--disable-blink-features=SlimmingPaintV175"]
   },
   {
+    "prefix": "disable-rls",
+    "base": "compositing/squashing",
+    "args": ["--disable-blink-features=RootLayerScrolling"]
+  },
+
+  {
     "prefix": "scalefactor200",
     "base": "fast/hidpi/static",
     "args": ["--force-device-scale-factor=2"]
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.png b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.png
new file mode 100644
index 0000000..a9eac14
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.txt b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
new file mode 100644
index 0000000..c3e99843
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
@@ -0,0 +1,63 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 2038],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV)",
+      "position": [8, 8],
+      "bounds": [102, 2002],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -300, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled.html b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled.html
new file mode 100644
index 0000000..b731a711
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/squashing/frame-clip-squashed-scrolled.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="../../paint/invalidation/resources/text-based-repaint.js"></script>
+<div style="transform: translateZ(0px)"></div>
+<div style="position: relative; width: 100px; border: 1px solid black">
+  <div style="width: 50px; height: 2000px; background: green"></div>
+</div>
+There should be a green rectangle across the entire height of the screen
+<script>
+  function repaintTest() {
+    window.scrollBy(0, 300);
+  }
+  onload = runRepaintAndPixelTest;
+</script>
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/block/margin-collapse/103-expected.png b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/block/margin-collapse/103-expected.png
index 7736f52..fdc1256 100644
--- a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/block/margin-collapse/103-expected.png
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/block/margin-collapse/103-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/console/command-line-api-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/console/command-line-api-expected.txt
index e377a3a..680f042 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/console/command-line-api-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/console/command-line-api-expected.txt
@@ -38,5 +38,6 @@
 undefined
 
 queryObjects(Foo)
-[Foo]
+undefined
+Array(1)
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box-expected.txt
index a77355d3..d9419c6 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box-expected.txt
@@ -4,6 +4,7 @@
 Intermediate Suggestion Applied: First
 
 Testing that no item is selected.
+No item selected.
 
 Testing that highest priority item is selected.
 Intermediate Suggestion Applied: The best suggestion
@@ -20,5 +21,6 @@
 Suggestion accepted
 
 Testing that highest priority item is selected.
+No item selected.
 Intermediate Suggestion Applied: The best suggestion
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box.js b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box.js
index 43b845a..11dc032 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/suggest-box.js
@@ -3,7 +3,10 @@
 
   var delegate = {
       applySuggestion: function(suggestion, isIntermediateSuggestion) {
-          TestRunner.addResult((isIntermediateSuggestion ? "Intermediate " : "") + "Suggestion Applied: " + suggestion);
+          if (!suggestion)
+              TestRunner.addResult('No item selected.')
+          else
+              TestRunner.addResult((isIntermediateSuggestion ? "Intermediate " : "") + "Suggestion Applied: " + suggestion);
       },
       acceptSuggestion: function() {
           TestRunner.addResult("Suggestion accepted");
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-expected.txt
index eab40d0..8d36b10 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-expected.txt
@@ -1,7 +1,17 @@
 This tests if the TextPrompt autocomplete works properly.
+
 Text:hey
 TextWithCurrentSuggestion:heyoo
+
 Test with inexact match:
 Text:inexactmatch
 TextWithCurrentSuggestion:heyoo
 
+Test with a blank prompt
+Text:
+TextWithCurrentSuggestion:heyoo
+
+Test with disableDefaultSuggestionForEmptyInput
+Text:
+TextWithCurrentSuggestion:
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-hint.js b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-hint.js
index 415c935..ad715484 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-hint.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt-hint.js
@@ -46,17 +46,19 @@
       typeCharacter(null);
       waitForAutocomplete().then(step5);
   }
-  function step5()
+  async function step5()
   {
       dumpTextPrompt();
       prompt.setText("something_before test");
-      prompt.complete();
-      completionsDone().then(()=>{
-          dumpTextPrompt();
-          typeCharacter("T");
-          dumpTextPrompt();
-          TestRunner.completeTest();
-      });
+      await Promise.all([
+          prompt.complete(),
+          completionsDone()
+      ]);
+
+      dumpTextPrompt();
+      typeCharacter("T");
+      dumpTextPrompt();
+      TestRunner.completeTest();
   }
 
   function completions(expression, query)
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt.js b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt.js
index c5399bd..5d91e4c 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/unit/text-prompt.js
@@ -1,5 +1,5 @@
 (async function() {
-  TestRunner.addResult("This tests if the TextPrompt autocomplete works properly.");
+  TestRunner.addResult("This tests if the TextPrompt autocomplete works properly.\n");
 
   var suggestions = ["heyoo", "hey it's a suggestion", "hey another suggestion"].map(s => ({text: s}));
   var prompt = new UI.TextPrompt();
@@ -8,21 +8,29 @@
   UI.inspectorView.element.appendChild(div);
   prompt.attachAndStartEditing(div);
   prompt.setText("hey");
-  TestRunner.addSnifferPromise(prompt, "_completionsReady").then(step2);
-  prompt.complete();
-  function step2() {
-      TestRunner.addResult("Text:" + prompt.text());
-      TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
+  await prompt.complete();
+  TestRunner.addResult("Text:" + prompt.text());
+  TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
 
-      TestRunner.addResult("Test with inexact match:");
-      prompt.clearAutocomplete();
-      prompt.setText("inexactmatch");
-      TestRunner.addSnifferPromise(prompt, "_completionsReady").then(step3);
-      prompt.complete();
-  }
-  function step3() {
-      TestRunner.addResult("Text:" + prompt.text());
-      TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
-      TestRunner.completeTest();
-  }
+  TestRunner.addResult("\nTest with inexact match:");
+  prompt.clearAutocomplete();
+  prompt.setText("inexactmatch");
+  await prompt.complete();
+  TestRunner.addResult("Text:" + prompt.text());
+  TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
+
+  TestRunner.addResult("\nTest with a blank prompt")
+  prompt.setText("");
+  await prompt.complete();
+  TestRunner.addResult("Text:" + prompt.text());
+  TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
+
+  TestRunner.addResult("\nTest with disableDefaultSuggestionForEmptyInput")
+  prompt.disableDefaultSuggestionForEmptyInput();
+  prompt.setText("");
+  await prompt.complete();
+  TestRunner.addResult("Text:" + prompt.text());
+  TestRunner.addResult("TextWithCurrentSuggestion:" + prompt.textWithCurrentSuggestion());
+
+  TestRunner.completeTest();
 })();
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
new file mode 100644
index 0000000..cab8077
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
@@ -0,0 +1,149 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 40, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 40, 39],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 42, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 42, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 160, 40, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 160, 40, 39],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/platform/mac/compositing/squashing/frame-clip-squashed-scrolled-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
new file mode 100644
index 0000000..bab8a50
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
@@ -0,0 +1,63 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 2036],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV)",
+      "position": [8, 8],
+      "bounds": [102, 2002],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -300, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
new file mode 100644
index 0000000..243426c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
@@ -0,0 +1,51 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 2036],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV)",
+      "position": [8, 8],
+      "bounds": [102, 2002],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -300, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
similarity index 96%
rename from third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
rename to third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
index d2f3e93..c130381 100644
--- a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
@@ -3,7 +3,6 @@
     {
       "name": "LayoutView #document",
       "bounds": [800, 600],
-      "drawsContent": false,
       "contentsOpaque": true,
       "backgroundColor": "#FFFFFF"
     },
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
new file mode 100644
index 0000000..461ff5dd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
@@ -0,0 +1,149 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 42, 36],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 42, 36],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 44, 36],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 44, 36],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 160, 42, 36],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 160, 42, 36],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
new file mode 100644
index 0000000..f05b622
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-rls/compositing/squashing/selection-repaint-with-gaps-expected.txt
@@ -0,0 +1,149 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 38, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 38, 39],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='overlap'",
+      "bounds": [300, 500],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='item')",
+      "position": [15, 35],
+      "bounds": [100, 210],
+      "paintInvalidations": [
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 80, 39, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 80, 39, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'ipsum'",
+          "rect": [0, 160, 38, 39],
+          "reason": "geometry"
+        },
+        {
+          "object": "InlineTextBox 'lorem'",
+          "rect": [0, 160, 38, 39],
+          "reason": "geometry"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    },
+    {
+      "object": "LayoutText #text",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'lorem'",
+      "reason": "geometry"
+    },
+    {
+      "object": "InlineTextBox 'ipsum'",
+      "reason": "geometry"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/README.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/README.txt
new file mode 100644
index 0000000..702681b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/README.txt
@@ -0,0 +1 @@
+# This suite runs tests with --disable-blink-features=RootLayerScrolling
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/add-remove-squashed-layers-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/add-remove-squashed-layers-expected.txt
new file mode 100644
index 0000000..b0b7b3c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/add-remove-squashed-layers-expected.txt
@@ -0,0 +1,334 @@
+Test that layers can be nicely added or removed from a squashed layer, without unnecessary repaints on any layer. Click anywhere to test interactively; keep clicking to proceed through the test.
+
+CASE 1, original layer tree with overlap1 and overlap2:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [180, 180]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, overlap3 gets added:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [160, 160, 100, 100],
+          "reason": "appeared"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "appeared"
+    }
+  ]
+}
+CASE 3, overlap2 gets removed. Since this does not resize the layer, there should only be a repaint of overlap2:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [80, 80, 100, 100],
+          "reason": "disappeared"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 4, overlap1 gets removed:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='C' class='overlap3')",
+      "position": [300, 300],
+      "bounds": [100, 100],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [0, 0, 100, 100],
+          "reason": "disappeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "compositing update"
+    }
+  ]
+}
+CASE 5, overlap2 gets added back:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='B' class='overlap2')",
+      "position": [220, 220],
+      "bounds": [180, 180],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [80, 80, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [0, 0, 100, 100],
+          "reason": "appeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "reason": "appeared"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "compositing update"
+    }
+  ]
+}
+CASE 6, overlap1 gets added back, and overlap3 gets removed:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [180, 180],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [80, 80, 100, 100],
+          "reason": "paint property change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [80, 80, 100, 100],
+          "reason": "disappeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [0, 0, 100, 100],
+          "reason": "appeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "reason": "appeared"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "reason": "compositing update"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/clipping-ancestor-expected.txt
similarity index 65%
copy from third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
copy to third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/clipping-ancestor-expected.txt
index d2f3e93..646cdc4 100644
--- a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/clipping-ancestor-expected.txt
@@ -3,26 +3,18 @@
     {
       "name": "LayoutView #document",
       "bounds": [800, 600],
-      "drawsContent": false,
       "contentsOpaque": true,
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "LayoutBlockFlow DIV",
-      "bounds": [784, 10],
-      "contentsOpaque": true,
-      "backgroundColor": "#ADD8E6",
-      "transform": 1
-    },
-    {
-      "name": "Child Containment Layer",
-      "bounds": [784, 10],
-      "drawsContent": false,
-      "transform": 1
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [200, 10],
+      "drawsContent": false
     },
     {
       "name": "LayoutBlockFlow DIV id='inner'",
-      "bounds": [784, 10],
+      "bounds": [200, 10],
       "contentsOpaque": true,
       "backgroundColor": "#F5F5F5",
       "transform": 1
@@ -30,7 +22,8 @@
     {
       "name": "LayoutBlockFlow (positioned) DIV class='hoverable'",
       "position": [8, 0],
-      "bounds": [211, 100],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#90EE90"
     }
   ],
@@ -42,7 +35,8 @@
         [0, 1, 0, 0],
         [0, 0, 1, 0],
         [8, 8, 0, 1]
-      ]
+      ],
+      "flattenInheritedTransform": false
     }
   ]
 }
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/composited-bounds-for-negative-z-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/composited-bounds-for-negative-z-expected.txt
new file mode 100644
index 0000000..851dac0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/composited-bounds-for-negative-z-expected.txt
@@ -0,0 +1,82 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "bounds": [800, 408]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [10, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow HTML (foreground) Layer",
+      "bounds": [800, 408]
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [108, 100],
+      "bounds": [300, 300]
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [108, 100],
+      "bounds": [285, 300],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [108, 100],
+      "bounds": [285, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [108, 100],
+      "bounds": [300, 300],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [393, 100],
+      "bounds": [15, 300],
+      "drawsContent": false
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "position": [108, 100],
+      "bounds": [285, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV)",
+      "position": [108, 100],
+      "bounds": [285, 1000]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 100, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt
new file mode 100644
index 0000000..efbccb730
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt
@@ -0,0 +1,18 @@
+Item 1
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='composited-container'",
+      "position": [8, 8],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-iframes-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-iframes-expected.txt
new file mode 100644
index 0000000..55fc4812
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-iframes-expected.txt
@@ -0,0 +1,24 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [104, 104],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [50, 50],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-videos-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-videos-expected.txt
new file mode 100644
index 0000000..e91ba30
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/dont-squash-into-videos-expected.txt
@@ -0,0 +1,33 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutVideo (positioned) VIDEO",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6"
+    },
+    {
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='phase-pre-ready state-no-source use-default-poster'",
+      "bounds": [100, 100]
+    },
+    {
+      "name": "LayoutFlexibleBox (relative positioned) DIV",
+      "bounds": [100, 58],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [50, 50],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
new file mode 100644
index 0000000..fee20c52
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/frame-clip-squashed-scrolled-expected.txt
@@ -0,0 +1,51 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 2038],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV)",
+      "position": [8, 8],
+      "bounds": [102, 2002],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -300, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/iframes-are-never-squashed-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/iframes-are-never-squashed-expected.txt
new file mode 100644
index 0000000..faa90540
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/iframes-are-never-squashed-expected.txt
@@ -0,0 +1,35 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [200, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [104, 104],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
new file mode 100644
index 0000000..d8e593f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
@@ -0,0 +1,24 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='chip'",
+          "rect": [408, 108, 20, 20],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow DIV id='chip'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invisible-layers-should-not-affect-geometry-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invisible-layers-should-not-affect-geometry-expected.txt
new file mode 100644
index 0000000..10ed367
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/invisible-layers-should-not-affect-geometry-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box')",
+      "position": [10, 10],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [10, 10, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/move-squashing-layer-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/move-squashing-layer-expected.txt
new file mode 100644
index 0000000..b9d18e90
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/move-squashing-layer-expected.txt
@@ -0,0 +1,57 @@
+This tests that squashed layers' offset from renderer is updated properly. If not properly updated, the two divs will appear to be aligned vertically in the pixel results, while really the blue div ('squashed') should be offset 100px left of the green ('host').
+
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='background'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='host'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='squashed')",
+      "position": [8, 8],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 50, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-for-filters-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-for-filters-expected.txt
new file mode 100644
index 0000000..ac0b57a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-for-filters-expected.txt
@@ -0,0 +1,44 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='trysquashed'",
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='trysquashed')",
+      "position": [50, 50],
+      "bounds": [50, 50]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
similarity index 94%
copy from third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
copy to third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
index d2f3e93..90edfaf1 100644
--- a/third_party/WebKit/LayoutTests/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
@@ -3,7 +3,6 @@
     {
       "name": "LayoutView #document",
       "bounds": [800, 600],
-      "drawsContent": false,
       "contentsOpaque": true,
       "backgroundColor": "#FFFFFF"
     },
@@ -30,7 +29,7 @@
     {
       "name": "LayoutBlockFlow (positioned) DIV class='hoverable'",
       "position": [8, 0],
-      "bounds": [211, 100],
+      "bounds": [216, 100],
       "backgroundColor": "#90EE90"
     }
   ],
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt
new file mode 100644
index 0000000..b7f7b6f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt
@@ -0,0 +1,48 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='fixedpos'",
+      "position": [0, 50],
+      "bounds": [800, 550],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [0, 50],
+      "bounds": [800, 550],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='compositedlayer'",
+      "bounds": [24, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='notsquashedelement'",
+      "bounds": [800, 60],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [400, 40, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/opacity-squashed-owner-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/opacity-squashed-owner-expected.txt
new file mode 100644
index 0000000..261643e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/opacity-squashed-owner-expected.txt
@@ -0,0 +1,40 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='target' class='composited box opaque'",
+      "bounds": [100, 100],
+      "opacity": 0.5,
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='squashed')",
+      "position": [16, 4],
+      "bounds": [40, 90]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/repaint-child-of-squashed-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/repaint-child-of-squashed-expected.txt
new file mode 100644
index 0000000..4ad7be1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/repaint-child-of-squashed-expected.txt
@@ -0,0 +1,92 @@
+CASE 1, original layer tree
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box top')",
+      "position": [130, 130],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, change color of "inner" to red
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box top')",
+      "position": [130, 130],
+      "bounds": [100, 100],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='inner'",
+          "rect": [10, 10, 50, 50],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='inner'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-1-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-1-expected.txt
new file mode 100644
index 0000000..fa657ba
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-1-expected.txt
@@ -0,0 +1,309 @@
+The gray div is a composited fixed-position element, and the cyan/lime elements should be squashed together on top. When scrolling, paragraphs may pop in-and-out of the squashing layer when they change overlapping status with respect to the composited layer underneath.
+
+This scenario tests (1) that content repaints correctly as layers pop in and out of squashing, and (2) that the positioning of the squashing layer remains correct (i.e. scrolls properly) when the squashing layer is on top of a fixed-position composited layer.
+
+CASE 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 1400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited background'",
+      "bounds": [300, 300],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='paragraph-b' class='overlapping lime'",
+      "position": [0, 100],
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FF00"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan')",
+      "position": [0, 200],
+      "bounds": [200, 300]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 150, 0, 1]
+      ]
+    }
+  ]
+}
+CASE 2, scrolling y to 80, new layers will be squashed, so things repaint:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 1400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-f' class='overlapping lime'",
+          "rect": [0, 500, 200, 100],
+          "reason": "disappeared"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-b' class='overlapping lime'",
+          "rect": [0, 100, 200, 100],
+          "reason": "appeared"
+        }
+      ],
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited background'",
+      "bounds": [300, 300],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+      "position": [0, 200],
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+          "rect": [0, 0, 200, 100],
+          "reason": "full"
+        }
+      ],
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime')",
+      "position": [0, 300],
+      "bounds": [200, 300],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -80, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 230, 0, 1]
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-b' class='overlapping lime'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-e' class='overlapping cyan'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-f' class='overlapping lime'",
+      "reason": "compositing update"
+    }
+  ]
+}
+CASE 3, scrolling y to 120, no repaints expected:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 1400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited background'",
+      "bounds": [300, 300],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+      "position": [0, 200],
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime')",
+      "position": [0, 300],
+      "bounds": [200, 300],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -120, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 270, 0, 1]
+      ]
+    }
+  ]
+}
+CASE 4, scrolling y to 170 new layers will be squashed, so things repaint:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 1400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-g' class='overlapping cyan'",
+          "rect": [0, 600, 200, 100],
+          "reason": "disappeared"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+          "rect": [0, 200, 200, 100],
+          "reason": "appeared"
+        }
+      ],
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited background'",
+      "bounds": [300, 300],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime'",
+      "position": [0, 300],
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FF00",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime'",
+          "rect": [0, 0, 200, 100],
+          "reason": "full"
+        }
+      ],
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='paragraph-e' class='overlapping cyan')",
+      "position": [0, 400],
+      "bounds": [200, 300],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -170, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 320, 0, 1]
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-c' class='overlapping cyan'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-d' class='overlapping lime'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-e' class='overlapping cyan'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-f' class='overlapping lime'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (relative positioned) DIV id='paragraph-g' class='overlapping cyan'",
+      "reason": "compositing update"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-2-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-2-expected.txt
new file mode 100644
index 0000000..cfa8fda
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-2-expected.txt
@@ -0,0 +1,84 @@
+This scenario verifies that the cyan "container" element scrolls properly with squashing enabled. The "container" element should not squash into a composited layer mapping owned by the fixed position layer or its descendant, since this would make it behave like a fixed position element during composited scrolling.
+
+CASE 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 4050],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='fixed'",
+      "bounds": [400, 200],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#0000FF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='compositedInsideFixed'",
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#FF0000"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [100, 50],
+      "bounds": [200, 4000],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF"
+    }
+  ]
+}
+CASE 2, scrolling y to 80, the "container" element should remain positioned with respect to the scrolled document, the fixed-pos layer compensates for the new scroll position:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 4050],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='fixed'",
+      "position": [0, 80],
+      "bounds": [400, 200],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='compositedInsideFixed'",
+      "position": [0, 80],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#FF0000",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [100, 50],
+      "bounds": [200, 4000],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -80, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-3-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-3-expected.txt
new file mode 100644
index 0000000..8292e9b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-above-fixed-3-expected.txt
@@ -0,0 +1,204 @@
+This scenario verifies that the green "container" element and lime "innerScrolling" element scroll properly even though there is a blue fixed-position element layered in between them.
+
+The catch is that the squashing requirements should be computed in correct paint order, so that the green container does not accidentally position itself with respect to the wrong layer and not scroll.
+
+CASE 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 4100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='backgroundFixed'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [100, 100],
+      "bounds": [100, 4000],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='innerFixed'",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='innerScrolling'",
+      "position": [200, 100],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FF00"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 150, 0, 1]
+      ]
+    }
+  ]
+}
+CASE 2, scrolling y by 10 pixels, both the "container" and "inner" should scroll properly.
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 4100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='backgroundFixed'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [100, 100],
+      "bounds": [100, 4000],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='innerFixed'",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='innerScrolling'",
+      "position": [200, 100],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FF00",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -10, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 10, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 160, 0, 1]
+      ]
+    }
+  ]
+}
+CASE 3, scrolling y further so that "inner" no longer overlaps the fixed-pos layer, then the stacking context of "container" includes the "innerScrolling" layer, and doubles in width:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [785, 4100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='backgroundFixed'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [100, 100],
+      "bounds": [200, 4000],
+      "backgroundColor": "#008000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='innerScrolling'",
+          "rect": [100, 0, 100, 100],
+          "reason": "appeared"
+        }
+      ],
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='innerFixed'",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 3
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -110, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 110, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 260, 0, 1]
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='innerScrolling'",
+      "reason": "compositing update"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-compositing-hover-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-compositing-hover-expected.txt
new file mode 100644
index 0000000..9fd9952
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-compositing-hover-expected.txt
@@ -0,0 +1,331 @@
+Test overlap is rendered correctly when hovering over elements
+
+Case 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [180, 180],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+Case 2: hovering over the "middle" element (causes that div to become its own composited layer)
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='box middle'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 3
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle2')",
+      "position": [260, 260],
+      "bounds": [180, 180]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [180, 180, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+Case 3: hovering over the "middle2" element (causes that div to become its own composited layer)
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [180, 180],
+      "bounds": [100, 100]
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='box middle2'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 3
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box top')",
+      "position": [340, 340],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [260, 260, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+Case 4: hovering over the "top" element (causes that div to become its own composited layer)
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [180, 180],
+      "bounds": [180, 180]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='box top'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 3
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [340, 340, 0, 1]
+      ]
+    }
+  ]
+}
+Case 5: back to situation in case 1
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [180, 180],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-distant-relative-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-distant-relative-expected.txt
new file mode 100644
index 0000000..968a5fc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-distant-relative-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='squashing'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='squashed')",
+      "position": [600, 0],
+      "bounds": [200, 200]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [-33554430, 0, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-nephew-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-nephew-expected.txt
new file mode 100644
index 0000000..a8c89b33
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-nephew-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [40, 40],
+      "bounds": [180, 190]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [125, 125, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-transform-backing-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-transform-backing-expected.txt
new file mode 100644
index 0000000..d6c6619
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-onto-transform-backing-expected.txt
@@ -0,0 +1,51 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='compositedAndRotated box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [20, 20],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [50, 50],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-paint-invalidation-fixed-position-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-paint-invalidation-fixed-position-expected.txt
new file mode 100644
index 0000000..d2dde48
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-paint-invalidation-fixed-position-expected.txt
@@ -0,0 +1,111 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [100, 5000],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [8, 25],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='foo'",
+      "position": [8, 50],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='foo'",
+          "rect": [0, 0, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='foo'",
+      "reason": "style change"
+    }
+  ]
+}
+ {
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [100, 5000],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [8, 25],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='foo'",
+      "position": [8, 50],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='foo'",
+          "rect": [0, 0, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='foo'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-same-transform-ancestor-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-same-transform-ancestor-expected.txt
new file mode 100644
index 0000000..2e2f21b4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-same-transform-ancestor-expected.txt
@@ -0,0 +1,58 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='transform-parent'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='squashing'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='squashed')",
+      "position": [-100, 0],
+      "bounds": [200, 200],
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.866025403784439, -0.5, 0, 0],
+        [0.5, 0.866025403784439, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "origin": [50, 50]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-simple-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-simple-expected.txt
new file mode 100644
index 0000000..4a04fd52
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-simple-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [20, 20],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-three-layers-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-three-layers-expected.txt
new file mode 100644
index 0000000..6a2e3c2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-three-layers-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [40, 40],
+      "bounds": [180, 190]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-expected.txt
new file mode 100644
index 0000000..dd1faf9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [-1, -1],
+      "bounds": [281, 281]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-child-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-child-expected.txt
new file mode 100644
index 0000000..84e38c2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-child-expected.txt
@@ -0,0 +1,153 @@
+CASE 1, original layer tree
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [-1, -1],
+      "bounds": [281, 281]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, hovering over the outer div
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [-1, -1],
+      "bounds": [281, 281],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+          "rect": [0, 0, 142, 142],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+      "reason": "style change"
+    }
+  ]
+}
+CASE 3, hovering over the inner div
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [-1, -1],
+      "bounds": [281, 281],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+          "rect": [0, 0, 142, 142],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='smallbox'",
+          "rect": [32, 32, 71, 71],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='smallbox'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-transformed-child-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-transformed-child-expected.txt
new file mode 100644
index 0000000..6225ed0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squash-transform-repainting-transformed-child-expected.txt
@@ -0,0 +1,153 @@
+CASE 1, original layer tree
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [4, 4],
+      "bounds": [276, 276]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, hovering over the outer div
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [4, 4],
+      "bounds": [276, 276],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+          "rect": [0, 0, 132, 132],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+      "reason": "style change"
+    }
+  ]
+}
+CASE 3, hovering over the inner div
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='box middle')",
+      "position": [4, 4],
+      "bounds": [276, 276],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+          "rect": [0, 0, 132, 132],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='smallbox'",
+          "rect": [26, 28, 71, 72],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='box middle'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV class='smallbox'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-layer-loses-graphicslayer-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-layer-loses-graphicslayer-expected.txt
new file mode 100644
index 0000000..09f120a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-layer-loses-graphicslayer-expected.txt
@@ -0,0 +1,93 @@
+A squashing Layer that becomes non-composited should correctly send a repaint invalidation to the new container GraphicsLayer that it paints into. When run interactively, hovering over the force-composited gray div should not cause other layers to disappear.
+
+CASE 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='forceComposited' class='composited underneath'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, The original composited layer is no longer composited, which then also removes all squashing layers. The important point is that there should be an appropriate repaint to the root GraphicsLayer:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [300, 300, 100, 100],
+          "reason": "appeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [220, 220, 100, 100],
+          "reason": "appeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [140, 140, 100, 100],
+          "reason": "appeared"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='forceComposited' class='underneath'",
+          "rect": [60, 60, 100, 100],
+          "reason": "appeared"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='forceComposited' class='underneath'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "reason": "compositing update"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "compositing update"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-repaints-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-repaints-expected.txt
new file mode 100644
index 0000000..11032386
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashed-repaints-expected.txt
@@ -0,0 +1,277 @@
+Basic repaint test for squashed layers. The entire squashing layer should not need repainting when only a portion of it is invalidated. Test interactively by using --show-paint-rects and hovering over elements to change their color.
+
+CASE 1, original layer tree:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+CASE 2, overlap1 changes color:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [0, 0, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "reason": "style change"
+    }
+  ]
+}
+CASE 3, overlap1 and overlap2 change color:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [80, 80, 100, 100],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [0, 0, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "reason": "style change"
+    }
+  ]
+}
+CASE 4, overlap2 and overlap3 change color:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [160, 160, 100, 100],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+          "rect": [80, 80, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "style change"
+    }
+  ]
+}
+CASE 5, overlap3 and overlap1 change color:
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [260, 260],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+          "rect": [160, 160, 100, 100],
+          "reason": "style change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+          "rect": [0, 0, 100, 100],
+          "reason": "style change"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "reason": "style change"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) DIV id='C' class='overlap3'",
+      "reason": "style change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-inside-perspective-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-inside-perspective-expected.txt
new file mode 100644
index 0000000..c362684
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-inside-perspective-expected.txt
@@ -0,0 +1,71 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [8, 8],
+      "contentsOpaque": true,
+      "drawsContent": false
+    },
+    {
+      "name": "Child Transform Layer",
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [200, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#00008B",
+      "transform": 3
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV)",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, -0.001],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    },
+    {
+      "id": 3,
+      "parent": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 74, 200, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-print-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-print-expected.txt
new file mode 100644
index 0000000..21724937
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-print-expected.txt
@@ -0,0 +1,9 @@
+layer at (0,0) size 1066x799
+  LayoutView at (0,0) size 1066x799
+layer at (0,0) size 1066x118
+  LayoutBlockFlow {HTML} at (0,0) size 1066x118
+    LayoutBlockFlow {BODY} at (8,8) size 1050x102
+layer at (8,8) size 102x102
+  LayoutBlockFlow {DIV} at (0,0) size 102x102 [border: (1px solid #000000)]
+layer at (50,50) size 102x102
+  LayoutBlockFlow (positioned) {DIV} at (50,50) size 102x102 [border: (1px solid #000000)]
diff --git a/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-sparsity-heuristic-expected.txt b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-sparsity-heuristic-expected.txt
new file mode 100644
index 0000000..dc94aaa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/disable-rls/compositing/squashing/squashing-sparsity-heuristic-expected.txt
@@ -0,0 +1,55 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='A' class='overlap1')",
+      "position": [140, 140],
+      "bounds": [10, 10]
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "position": [220, 220],
+      "bounds": [10, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FF00"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='C' class='overlap3')",
+      "position": [220, 300],
+      "bounds": [25, 10]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn
index 663e69ad..f742c1ad6 100644
--- a/third_party/WebKit/Source/core/BUILD.gn
+++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -2055,6 +2055,7 @@
     "paint/compositing/CompositedLayerMappingTest.cpp",
     "paint/compositing/CompositingInputsUpdaterTest.cpp",
     "paint/compositing/CompositingLayerAssignerTest.cpp",
+    "paint/compositing/CompositingLayerPropertyUpdaterTest.cpp",
     "paint/compositing/CompositingReasonFinderTest.cpp",
     "paint/compositing/PaintLayerCompositorTest.cpp",
     "paint/ng/ng_paint_fragment_test.cc",
diff --git a/third_party/WebKit/Source/core/dom/CharacterData.cpp b/third_party/WebKit/Source/core/dom/CharacterData.cpp
index ae25297..f0fd6176 100644
--- a/third_party/WebKit/Source/core/dom/CharacterData.cpp
+++ b/third_party/WebKit/Source/core/dom/CharacterData.cpp
@@ -224,8 +224,4 @@
   probe::characterDataModified(this);
 }
 
-int CharacterData::MaxCharacterOffset() const {
-  return static_cast<int>(length());
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/CharacterData.h b/third_party/WebKit/Source/core/dom/CharacterData.h
index b4ed2639..0458aa2 100644
--- a/third_party/WebKit/Source/core/dom/CharacterData.h
+++ b/third_party/WebKit/Source/core/dom/CharacterData.h
@@ -81,7 +81,6 @@
   String nodeValue() const final;
   void setNodeValue(const String&) final;
   bool IsCharacterDataNode() const final { return true; }
-  int MaxCharacterOffset() const final;
   void SetDataAndUpdate(const String&,
                         unsigned offset_of_replaced_data,
                         unsigned old_length,
diff --git a/third_party/WebKit/Source/core/dom/Node.cpp b/third_party/WebKit/Source/core/dom/Node.cpp
index eb72c5e41..41c6fb5 100644
--- a/third_party/WebKit/Source/core/dom/Node.cpp
+++ b/third_party/WebKit/Source/core/dom/Node.cpp
@@ -1123,11 +1123,6 @@
              : nullptr;
 }
 
-int Node::MaxCharacterOffset() const {
-  NOTREACHED();
-  return 0;
-}
-
 // FIXME: Shouldn't these functions be in the editing code?  Code that asks
 // questions about HTML in the core DOM class is obviously misplaced.
 bool Node::CanStartSelection() const {
diff --git a/third_party/WebKit/Source/core/dom/Node.h b/third_party/WebKit/Source/core/dom/Node.h
index f3ddc60..b715d656 100644
--- a/third_party/WebKit/Source/core/dom/Node.h
+++ b/third_party/WebKit/Source/core/dom/Node.h
@@ -572,11 +572,6 @@
   Node* CommonAncestor(const Node&,
                        ContainerNode* (*parent)(const Node&)) const;
 
-  // Number of DOM 16-bit units contained in node. Note that laid out text
-  // length can be different - e.g. because of css-transform:capitalize breaking
-  // up precomposed characters and ligatures.
-  virtual int MaxCharacterOffset() const;
-
   // Whether or not a selection can be started in this object
   virtual bool CanStartSelection() const;
 
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
index adce6bb2..dcc7217f 100644
--- a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
@@ -9,6 +9,8 @@
 #include "core/editing/EphemeralRange.h"
 #include "core/editing/markers/SortedDocumentMarkerListEditor.h"
 #include "core/editing/markers/TextMatchMarker.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameView.h"
 #include "third_party/WebKit/Source/core/editing/VisibleUnits.h"
 
 namespace blink {
@@ -78,7 +80,13 @@
   const Position start_position(node, marker.StartOffset());
   const Position end_position(node, marker.EndOffset());
   EphemeralRange range(start_position, end_position);
-  marker.SetLayoutRect(LayoutRect(ComputeTextRect(range)));
+
+  DCHECK(node.GetDocument().GetFrame());
+  LocalFrameView* frame_view = node.GetDocument().GetFrame()->View();
+
+  DCHECK(frame_view);
+  marker.SetLayoutRect(
+      frame_view->AbsoluteToDocument(LayoutRect(ComputeTextRect(range))));
 }
 
 Vector<IntRect> TextMatchMarkerListImpl::LayoutRects(const Node& node) const {
diff --git a/third_party/WebKit/Source/core/exported/WebFrameTest.cpp b/third_party/WebKit/Source/core/exported/WebFrameTest.cpp
index cf60bca..608f8fae 100644
--- a/third_party/WebKit/Source/core/exported/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/core/exported/WebFrameTest.cpp
@@ -11667,6 +11667,57 @@
 
 INSTANTIATE_TEST_CASE_P(All, WebFrameSimTest, testing::Bool());
 
+TEST_P(WebFrameSimTest, TickmarksDocumentRelative) {
+  WebView().Resize(WebSize(500, 300));
+  WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
+
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        body, html {
+          width: 4000px;
+          height: 4000px;
+          margin: 0;
+        }
+        div {
+          position: absolute;
+          left: 800px;
+          top: 2000px;
+        }
+      </style>
+      <div>test</div>
+  )HTML");
+
+  Compositor().BeginFrame();
+
+  WebLocalFrameImpl* frame = ToWebLocalFrameImpl(WebView().MainFrame());
+  LocalFrameView* frame_view =
+      ToLocalFrame(WebView().GetPage()->MainFrame())->View();
+
+  frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(3000, 1000),
+                                                   kProgrammaticScroll);
+  WebFindOptions options;
+  WebString search_text = WebString::FromUTF8("test");
+  const int kFindIdentifier = 12345;
+  EXPECT_TRUE(frame->Find(kFindIdentifier, search_text, options, false));
+
+  frame->EnsureTextFinder().ResetMatchCount();
+  frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
+                                                      search_text, options);
+
+  RunPendingTasks();
+
+  // Get the tickmarks for the original find request.
+  Scrollbar* scrollbar = frame_view->CreateScrollbar(kVerticalScrollbar);
+  Vector<IntRect> original_tickmarks;
+  scrollbar->GetTickmarks(original_tickmarks);
+  EXPECT_EQ(1u, original_tickmarks.size());
+
+  EXPECT_EQ(IntPoint(800, 2000), original_tickmarks[0].Location());
+}
+
 TEST_P(WebFrameSimTest, TestScrollFocusedEditableElementIntoView) {
   WebView().Resize(WebSize(500, 300));
   WebView().SetDefaultPageScaleLimits(1.f, 4);
diff --git a/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.cpp
index a6301c36..67f3fca 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.cpp
@@ -1271,13 +1271,14 @@
       .build();
 }
 
-void InspectorNetworkAgent::DidCreateWebSocket(Document* document,
-                                               unsigned long identifier,
-                                               const KURL& request_url,
-                                               const String&) {
+void InspectorNetworkAgent::DidCreateWebSocket(
+    ExecutionContext* execution_context,
+    unsigned long identifier,
+    const KURL& request_url,
+    const String&) {
   std::unique_ptr<v8_inspector::protocol::Runtime::API::StackTrace>
       current_stack_trace =
-          SourceLocation::Capture(document)->BuildInspectorObject();
+          SourceLocation::Capture(execution_context)->BuildInspectorObject();
   if (!current_stack_trace) {
     GetFrontend()->webSocketCreated(
         IdentifiersFactory::SubresourceRequestId(identifier),
@@ -1296,7 +1297,7 @@
 }
 
 void InspectorNetworkAgent::WillSendWebSocketHandshakeRequest(
-    Document*,
+    ExecutionContext*,
     unsigned long identifier,
     const WebSocketHandshakeRequest* request) {
   DCHECK(request);
@@ -1310,7 +1311,7 @@
 }
 
 void InspectorNetworkAgent::DidReceiveWebSocketHandshakeResponse(
-    Document*,
+    ExecutionContext*,
     unsigned long identifier,
     const WebSocketHandshakeRequest* request,
     const WebSocketHandshakeResponse* response) {
@@ -1335,7 +1336,7 @@
       CurrentTimeTicksInSeconds(), std::move(response_object));
 }
 
-void InspectorNetworkAgent::DidCloseWebSocket(Document*,
+void InspectorNetworkAgent::DidCloseWebSocket(ExecutionContext*,
                                               unsigned long identifier) {
   GetFrontend()->webSocketClosed(
       IdentifiersFactory::SubresourceRequestId(identifier),
diff --git a/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.h b/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.h
index 4f0b467..4298b48 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.h
+++ b/third_party/WebKit/Source/core/inspector/InspectorNetworkAgent.h
@@ -169,18 +169,18 @@
   void FrameScheduledClientNavigation(LocalFrame*);
   void FrameClearedScheduledClientNavigation(LocalFrame*);
 
-  void DidCreateWebSocket(Document*,
+  void DidCreateWebSocket(ExecutionContext*,
                           unsigned long identifier,
                           const KURL& request_url,
                           const String&);
-  void WillSendWebSocketHandshakeRequest(Document*,
+  void WillSendWebSocketHandshakeRequest(ExecutionContext*,
                                          unsigned long identifier,
                                          const WebSocketHandshakeRequest*);
-  void DidReceiveWebSocketHandshakeResponse(Document*,
+  void DidReceiveWebSocketHandshakeResponse(ExecutionContext*,
                                             unsigned long identifier,
                                             const WebSocketHandshakeRequest*,
                                             const WebSocketHandshakeResponse*);
-  void DidCloseWebSocket(Document*, unsigned long identifier);
+  void DidCloseWebSocket(ExecutionContext*, unsigned long identifier);
   void DidReceiveWebSocketFrame(unsigned long identifier,
                                 int op_code,
                                 bool masked,
diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.json b/third_party/WebKit/Source/core/inspector/browser_protocol.json
index ab21ef8..34ff7118 100644
--- a/third_party/WebKit/Source/core/inspector/browser_protocol.json
+++ b/third_party/WebKit/Source/core/inspector/browser_protocol.json
@@ -10226,7 +10226,7 @@
                         },
                         {
                             "name": "headerTemplate",
-                            "description": "HTML template for the print header. Should be valid HTML markup with following\nclasses used to inject printing values into them:\n- date - formatted print date\n- title - document title\n- url - document location\n- pageNumber - current page number\n- totalPages - total pages in the document\n\nFor example, <span class=title></span> would generate span containing the title.",
+                            "description": "HTML template for the print header. Should be valid HTML markup with following\nclasses used to inject printing values into them:\n- `date`: formatted print date\n- `title`: document title\n- `url`: document location\n- `pageNumber`: current page number\n- `totalPages`: total pages in the document\n\nFor example, `<span class=title></span>` would generate span containing the title.",
                             "optional": true,
                             "type": "string"
                         },
@@ -11130,7 +11130,7 @@
                 },
                 {
                     "name": "setOverrideCertificateErrors",
-                    "description": "Enable/disable overriding certificate errors. If enabled, all certificate error events need to\nbe handled by the DevTools client and should be answered with handleCertificateError commands.",
+                    "description": "Enable/disable overriding certificate errors. If enabled, all certificate error events need to\nbe handled by the DevTools client and should be answered with `handleCertificateError` commands.",
                     "deprecated": true,
                     "parameters": [
                         {
@@ -11144,7 +11144,7 @@
             "events": [
                 {
                     "name": "certificateError",
-                    "description": "There is a certificate error. If overriding certificate errors is enabled, then it should be\nhandled with the handleCertificateError command. Note: this event does not fire if the\ncertificate error has been allowed internally. Only one client per target should override\ncertificate errors at the same time.",
+                    "description": "There is a certificate error. If overriding certificate errors is enabled, then it should be\nhandled with the `handleCertificateError` command. Note: this event does not fire if the\ncertificate error has been allowed internally. Only one client per target should override\ncertificate errors at the same time.",
                     "deprecated": true,
                     "parameters": [
                         {
diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.pdl b/third_party/WebKit/Source/core/inspector/browser_protocol.pdl
index 4cddd342..e7e47600 100644
--- a/third_party/WebKit/Source/core/inspector/browser_protocol.pdl
+++ b/third_party/WebKit/Source/core/inspector/browser_protocol.pdl
@@ -4676,13 +4676,13 @@
       optional boolean ignoreInvalidPageRanges
       # HTML template for the print header. Should be valid HTML markup with following
       # classes used to inject printing values into them:
-      # - date - formatted print date
-      # - title - document title
-      # - url - document location
-      # - pageNumber - current page number
-      # - totalPages - total pages in the document
+      # - `date`: formatted print date
+      # - `title`: document title
+      # - `url`: document location
+      # - `pageNumber`: current page number
+      # - `totalPages`: total pages in the document
       #
-      # For example, <span class=title></span> would generate span containing the title.
+      # For example, `<span class=title></span>` would generate span containing the title.
       optional string headerTemplate
       # HTML template for the print footer. Should use the same format as the `headerTemplate`.
       optional string footerTemplate
@@ -5118,14 +5118,14 @@
       CertificateErrorAction action
 
   # Enable/disable overriding certificate errors. If enabled, all certificate error events need to
-  # be handled by the DevTools client and should be answered with handleCertificateError commands.
+  # be handled by the DevTools client and should be answered with `handleCertificateError` commands.
   deprecated command setOverrideCertificateErrors
     parameters
       # If true, certificate errors will be overridden.
       boolean override
 
   # There is a certificate error. If overriding certificate errors is enabled, then it should be
-  # handled with the handleCertificateError command. Note: this event does not fire if the
+  # handled with the `handleCertificateError` command. Note: this event does not fire if the
   # certificate error has been allowed internally. Only one client per target should override
   # certificate errors at the same time.
   deprecated event certificateError
diff --git a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
index 44f1bbd..9e0ca16 100644
--- a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
+++ b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
@@ -274,11 +274,6 @@
   visitor->Trace(definition_);
 }
 
-void CSSLayoutDefinition::Instance::TraceWrappers(
-    const ScriptWrappableVisitor* visitor) const {
-  visitor->TraceWrappers(instance_.Cast<v8::Value>());
-}
-
 void CSSLayoutDefinition::TraceWrappers(
     const ScriptWrappableVisitor* visitor) const {
   visitor->TraceWrappers(constructor_.Cast<v8::Value>());
diff --git a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
index 12a74a4..162ffb9 100644
--- a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
+++ b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
@@ -7,6 +7,7 @@
 
 #include "core/css/cssom/CSSStyleValue.h"
 #include "core/css_property_names.h"
+#include "platform/bindings/ScopedPersistent.h"
 #include "platform/bindings/ScriptWrappable.h"
 #include "platform/bindings/TraceWrapperMember.h"
 #include "platform/bindings/TraceWrapperV8Reference.h"
@@ -39,8 +40,7 @@
 
   // This class represents an instance of the layout class defined by the
   // CSSLayoutDefinition.
-  class Instance final : public GarbageCollectedFinalized<Instance>,
-                         public TraceWrapperBase {
+  class Instance final : public GarbageCollectedFinalized<Instance> {
    public:
     Instance(CSSLayoutDefinition*, v8::Local<v8::Object> instance);
 
@@ -49,16 +49,12 @@
     bool Layout(const LayoutCustom&, FragmentResultOptions*);
 
     void Trace(blink::Visitor*);
-    void TraceWrappers(const ScriptWrappableVisitor*) const override;
-    const char* NameInHeapSnapshot() const override {
-      return "CSSLayoutDefinition::Instance";
-    }
 
    private:
     void ReportException(ExceptionState*);
 
     Member<CSSLayoutDefinition> definition_;
-    TraceWrapperV8Reference<v8::Object> instance_;
+    ScopedPersistent<v8::Object> instance_;
   };
 
   // Creates an instance of the web developer defined class. May return a
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_caret_rect.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_caret_rect.cc
index 17658b55..d643877 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_caret_rect.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_caret_rect.cc
@@ -264,17 +264,9 @@
       is_horizontal ? fragment.Size().height : fragment.Size().width;
   LayoutUnit caret_top;
 
-  LayoutUnit caret_left;
-  if (!fragment.IsLineBreak()) {
-    unsigned offset_in_fragment = offset - fragment.StartOffset();
-    const ShapeResult* shape_result = fragment.TextShapeResult();
-    DCHECK(shape_result);
-    CharacterRange character_range =
-        shape_result->GetCharacterRange(offset_in_fragment, offset_in_fragment);
-
-    LayoutUnit caret_center = LayoutUnit(character_range.start);
-    caret_left = caret_center - caret_width / 2;
-  }
+  LayoutUnit caret_left = fragment.InlinePositionForOffset(offset);
+  if (!fragment.IsLineBreak())
+    caret_left -= caret_width / 2;
 
   if (!is_horizontal) {
     std::swap(caret_top, caret_left);
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.cc
index 339710d..6f7d636 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -15,6 +15,24 @@
 
 namespace blink {
 
+LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
+    unsigned offset) const {
+  DCHECK_GE(offset, start_offset_);
+  DCHECK_LE(offset, end_offset_);
+
+  offset -= start_offset_;
+  if (shape_result_) {
+    return LayoutUnit::FromFloatRound(shape_result_->PositionForOffset(offset));
+  }
+
+  // All non-flow controls have ShapeResult.
+  DCHECK(IsFlowControl());
+  DCHECK_EQ(1u, Length());
+  if (!offset || UNLIKELY(IsRtl(Style().Direction())))
+    return LayoutUnit();
+  return IsHorizontal() ? Size().width : Size().height;
+}
+
 NGPhysicalOffsetRect NGPhysicalTextFragment::LocalRect(
     unsigned start_offset,
     unsigned end_offset) const {
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.h
index 923031f3..0c7773b3 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_physical_text_fragment.h
@@ -105,6 +105,10 @@
     return IsHorizontal() ? kAlphabeticBaseline : kIdeographicBaseline;
   }
 
+  // Compute the inline position from text offset, in logical coordinate
+  // relative to this fragment.
+  LayoutUnit InlinePositionForOffset(unsigned offset) const;
+
   // The layout box of text in (start, end) range in local coordinate.
   // Start and end offsets must be between StartOffset() and EndOffset().
   NGPhysicalOffsetRect LocalRect(unsigned start_offset,
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_layout_result.cc b/third_party/WebKit/Source/core/layout/ng/ng_layout_result.cc
index 4b8d353..f86d18f8 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_layout_result.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_layout_result.cc
@@ -60,8 +60,7 @@
   // TODO(layoutng) Replace this with DCHECK(exclusion_space_) when
   // callers guarantee exclusion_space_ != null.
   if (exclusion_space_) {
-    std::unique_ptr<const NGExclusionSpace> exclusion_space(
-        std::make_unique<NGExclusionSpace>(*exclusion_space_));
+    exclusion_space = std::make_unique<NGExclusionSpace>(*exclusion_space_);
   }
   return base::AdoptRef(new NGLayoutResult(
       physical_fragment_->CloneWithoutOffset(), oof_positioned_descendants,
diff --git a/third_party/WebKit/Source/core/paint/ObjectPaintProperties.h b/third_party/WebKit/Source/core/paint/ObjectPaintProperties.h
index 2419e9ec..b54aeb96 100644
--- a/third_party/WebKit/Source/core/paint/ObjectPaintProperties.h
+++ b/third_party/WebKit/Source/core/paint/ObjectPaintProperties.h
@@ -98,6 +98,8 @@
   // |    Clips to a fragment's bounds.
   // |    This is only present for content under a fragmentation
   // |    container.
+  // | NOTE: for composited SPv1/SPv175 clip path clips, we move clip path clip
+  // |       below mask.
   // +-[ clip path clip ]
   //   |  Clip created by path-based CSS clip-path. Only exists if the
   //  /   clip-path is "simple" that can be applied geometrically. This and
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinter.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinter.cpp
index 84d0a3c..fa7dddb 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinter.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinter.cpp
@@ -174,6 +174,7 @@
                "SvgLocalToBorderBoxTransform", object);
   SetDebugName(properties.ScrollTranslation(), "ScrollTranslation", object);
   SetDebugName(properties.FragmentClip(), "FragmentClip", object);
+  SetDebugName(properties.ClipPathClip(), "ClipPathClip", object);
   SetDebugName(properties.MaskClip(), "MaskClip", object);
   SetDebugName(properties.CssClip(), "CssClip", object);
   SetDebugName(properties.CssClipFixedPosition(), "CssClipFixedPosition",
@@ -186,6 +187,7 @@
   SetDebugName(properties.Effect(), "Effect", object);
   SetDebugName(properties.Filter(), "Filter", object);
   SetDebugName(properties.Mask(), "Mask", object);
+  SetDebugName(properties.ClipPath(), "ClipPath", object);
   SetDebugName(properties.Scroll(), "Scroll", object);
 }
 
diff --git a/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdater.cpp b/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdater.cpp
index 7f70d5b..96fe816c 100644
--- a/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdater.cpp
+++ b/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdater.cpp
@@ -90,10 +90,17 @@
     // any control clips on the squashing layer's object which should not apply
     // on squashed layers.
     const auto* clipping_container = paint_layer->ClippingContainer();
-    state.SetClip(
-        clipping_container
-            ? clipping_container->FirstFragment().ContentsProperties().Clip()
-            : ClipPaintPropertyNode::Root());
+    if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
+      state.SetClip(
+          clipping_container
+              ? clipping_container->FirstFragment().ContentsProperties().Clip()
+              : ClipPaintPropertyNode::Root());
+    } else {
+      state.SetClip(
+          clipping_container
+              ? clipping_container->FirstFragment().ContentsProperties().Clip()
+              : paint_layer->GetLayoutObject().GetFrameView()->ContentClip());
+    }
     squashing_layer->SetLayerState(
         state,
         snapped_paint_offset + mapping->SquashingLayerOffsetFromLayoutObject());
@@ -104,6 +111,8 @@
     const auto* properties = fragment_data.PaintProperties();
     DCHECK(properties && properties->Mask());
     state.SetEffect(properties->Mask());
+    state.SetClip(properties->MaskClip());
+
     mask_layer->SetLayerState(
         state, snapped_paint_offset + mask_layer->OffsetFromLayoutObject());
   }
diff --git a/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdaterTest.cpp b/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdaterTest.cpp
new file mode 100644
index 0000000..f1f4eeb
--- /dev/null
+++ b/third_party/WebKit/Source/core/paint/compositing/CompositingLayerPropertyUpdaterTest.cpp
@@ -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.
+
+#include "core/paint/PaintLayer.h"
+#include "core/paint/compositing/CompositedLayerMapping.h"
+#include "core/paint/compositing/CompositingLayerPropertyUpdater.h"
+#include "core/testing/CoreUnitTestHelper.h"
+#include "platform/graphics/GraphicsLayer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+class CompositingLayerPropertyUpdaterTest : public RenderingTest {
+ private:
+  void SetUp() override {
+    RenderingTest::SetUp();
+    EnableCompositing();
+  }
+};
+
+TEST_F(CompositingLayerPropertyUpdaterTest, MaskLayerState) {
+  SetBodyInnerHTML(R"HTML(
+    <div id=target style="position: absolute;
+        clip-path: polygon(-1px -1px, 86px 400px);
+        clip: rect(9px, -1px, -1px, 96px); will-change: transform">
+    </div>
+    )HTML");
+
+  PaintLayer* target =
+      ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
+  EXPECT_EQ(kPaintsIntoOwnBacking, target->GetCompositingState());
+  auto* mapping = target->GetCompositedLayerMapping();
+  auto* mask_layer = mapping->MaskLayer();
+
+  auto* paint_properties =
+      target->GetLayoutObject().FirstFragment().PaintProperties();
+  EXPECT_TRUE(paint_properties->CssClip());
+  EXPECT_TRUE(paint_properties->MaskClip());
+  EXPECT_EQ(paint_properties->MaskClip(),
+            mask_layer->layer_state_->state.Clip());
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/probe/CoreProbes.pidl b/third_party/WebKit/Source/core/probe/CoreProbes.pidl
index 280f92cf9..cb01f41 100644
--- a/third_party/WebKit/Source/core/probe/CoreProbes.pidl
+++ b/third_party/WebKit/Source/core/probe/CoreProbes.pidl
@@ -132,13 +132,13 @@
   void frameClearedScheduledClientNavigation([Keep] LocalFrame*);
   void didStartWorker(ExecutionContext*, WorkerInspectorProxy* proxy, bool waitingForDebugger);
   void workerTerminated(ExecutionContext*, WorkerInspectorProxy* proxy);
-  void didCreateWebSocket([Keep] Document*, unsigned long identifier, const KURL& requestURL, const String& protocol);
-  void willSendWebSocketHandshakeRequest([Keep] Document*, unsigned long identifier, const WebSocketHandshakeRequest* request);
-  void didReceiveWebSocketHandshakeResponse([Keep] Document*, unsigned long identifier, const WebSocketHandshakeRequest* request, const WebSocketHandshakeResponse* response);
-  void didCloseWebSocket([Keep] Document*, unsigned long identifier);
-  void didReceiveWebSocketFrame(Document*, unsigned long identifier, int opCode, bool masked, const char* payload, size_t payloadLength);
-  void didSendWebSocketFrame(Document*, unsigned long identifier, int opCode, bool masked, const char* payload, size_t payloadLength);
-  void didReceiveWebSocketFrameError(Document*, unsigned long identifier, const String& errorMessage);
+  void didCreateWebSocket([Keep] ExecutionContext*, unsigned long identifier, const KURL& requestURL, const String& protocol);
+  void willSendWebSocketHandshakeRequest([Keep] ExecutionContext*, unsigned long identifier, const WebSocketHandshakeRequest* request);
+  void didReceiveWebSocketHandshakeResponse([Keep] ExecutionContext*, unsigned long identifier, const WebSocketHandshakeRequest* request, const WebSocketHandshakeResponse* response);
+  void didCloseWebSocket([Keep] ExecutionContext*, unsigned long identifier);
+  void didReceiveWebSocketFrame(ExecutionContext*, unsigned long identifier, int opCode, bool masked, const char* payload, size_t payloadLength);
+  void didSendWebSocketFrame(ExecutionContext*, unsigned long identifier, int opCode, bool masked, const char* payload, size_t payloadLength);
+  void didReceiveWebSocketFrameError(ExecutionContext*, unsigned long identifier, const String& errorMessage);
   void networkStateChanged([Keep] LocalFrame*, bool online);
   void updateApplicationCacheStatus([Keep] LocalFrame*);
   void layerTreeDidChange(LocalFrame*);
diff --git a/third_party/WebKit/Source/devtools/front_end/text_editor/TextEditorAutocompleteController.js b/third_party/WebKit/Source/devtools/front_end/text_editor/TextEditorAutocompleteController.js
index a20023a..56fbe6c 100644
--- a/third_party/WebKit/Source/devtools/front_end/text_editor/TextEditorAutocompleteController.js
+++ b/third_party/WebKit/Source/devtools/front_end/text_editor/TextEditorAutocompleteController.js
@@ -266,7 +266,7 @@
    */
   _setHint(hint) {
     const query = this._textEditor.text(this._queryRange);
-    if (!this._isCursorAtEndOfLine() || !hint.startsWith(query)) {
+    if (!hint || !this._isCursorAtEndOfLine() || !hint.startsWith(query)) {
       this._clearHint();
       return;
     }
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
index c83290c..e3cc151f 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
@@ -172,17 +172,12 @@
       this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermediateSuggestion);
       return true;
     }
-
-    if (!this.visible() || !this._list.selectedItem())
-      return false;
-
-    const suggestion = this._list.selectedItem().text;
-    if (!suggestion)
-      return false;
-
-    UI.ARIAUtils.alert(ls`${suggestion}, suggestion`, this._element);
+    const suggestion = this._list.selectedItem() ? this._list.selectedItem().text : '';
+    if (suggestion)
+      UI.ARIAUtils.alert(ls`${suggestion}, suggestion`, this._element);
     this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestion);
-    return true;
+
+    return this.visible() && !!suggestion;
   }
 
   /**
@@ -265,8 +260,6 @@
       if (fromElement || this._userInteracted || !this._defaultSelectionIsDimmed)
         toElement.classList.add('force-white-icons');
     }
-    if (!to)
-      return;
     this._applySuggestion(true);
   }
 
@@ -333,6 +326,8 @@
           }
         }
         this._list.selectItem(highestPriorityItem, true);
+      } else {
+        this._list.selectItem(null);
       }
     } else {
       if (completions.length === 1) {
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js b/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
index 7aac5f2e..5d4430d4 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
@@ -360,7 +360,7 @@
   }
 
   _refreshGhostText() {
-    if (this._queryRange && this._isCaretAtEndOfPrompt() &&
+    if (this._queryRange && this._currentSuggestion && this._isCaretAtEndOfPrompt() &&
         this._currentSuggestion.startsWith(this.text().substring(this._queryRange.startColumn))) {
       this._ghostTextElement.textContent =
           this._currentSuggestion.substring(this._queryRange.endColumn - this._queryRange.startColumn);
@@ -391,9 +391,8 @@
 
   /**
    * @param {boolean=} force
-   * @param {boolean=} reverse
    */
-  complete(force, reverse) {
+  async complete(force) {
     this._clearAutocompleteTimeout();
     const selection = this._element.getComponentSelection();
     const selectionRange = selection && selection.rangeCount ? selection.getRangeAt(0) : null;
@@ -402,18 +401,11 @@
 
     let shouldExit;
 
-    if (!force && !this._isCaretAtEndOfPrompt() && !this._isSuggestBoxVisible()) {
+    if (!force && !this._isCaretAtEndOfPrompt() && !this._isSuggestBoxVisible())
       shouldExit = true;
-    } else if (!selection.isCollapsed) {
+    else if (!selection.isCollapsed)
       shouldExit = true;
-    } else if (!force) {
-      // BUG72018: Do not show suggest box if caret is followed by a non-stop character.
-      const wordSuffixRange = selectionRange.startContainer.rangeOfWord(
-          selectionRange.endOffset, this._completionStopCharacters, this._element, 'forward');
-      const autocompleteTextLength = this._ghostTextElement.parentNode ? this._ghostTextElement.textContent.length : 0;
-      if (wordSuffixRange.toString().length !== autocompleteTextLength)
-        shouldExit = true;
-    }
+
     if (shouldExit) {
       this.clearAutocomplete();
       return;
@@ -425,9 +417,9 @@
     const expressionRange = wordQueryRange.cloneRange();
     expressionRange.collapse(true);
     expressionRange.setStartBefore(this._proxyElement);
-    this._loadCompletions(expressionRange.toString(), wordQueryRange.toString(), !!force)
-        .then(this._completionsReady.bind(
-            this, ++this._completionRequestId, selection, wordQueryRange, !!reverse, !!force));
+    const completionRequestId = ++this._completionRequestId;
+    const completions = await this._loadCompletions(expressionRange.toString(), wordQueryRange.toString(), !!force);
+    this._completionsReady(completionRequestId, selection, wordQueryRange, !!force, completions);
   }
 
   disableDefaultSuggestionForEmptyInput() {
@@ -470,11 +462,10 @@
    * @param {number} completionRequestId
    * @param {!Selection} selection
    * @param {!Range} originalWordQueryRange
-   * @param {boolean} reverse
    * @param {boolean} force
    * @param {!UI.SuggestBox.Suggestions} completions
    */
-  _completionsReady(completionRequestId, selection, originalWordQueryRange, reverse, force, completions) {
+  _completionsReady(completionRequestId, selection, originalWordQueryRange, force, completions) {
     if (this._completionRequestId !== completionRequestId)
       return;
 
@@ -525,8 +516,6 @@
    * @param {boolean=} isIntermediateSuggestion
    */
   applySuggestion(suggestion, isIntermediateSuggestion) {
-    if (!this._queryRange)
-      return;
     this._currentSuggestion = suggestion;
     this._refreshGhostText();
     if (isIntermediateSuggestion)
diff --git a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
index aac3528..a426fc9 100644
--- a/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
+++ b/third_party/WebKit/Source/modules/indexeddb/IDBValueWrapping.h
@@ -163,7 +163,11 @@
   // storage for IndexedDB, was not designed with large values in mind. At the
   // very least, large values will slow down compaction, causing occasional I/O
   // spikes.
-  static constexpr unsigned kWrapThreshold = 64 * 1024;
+  //
+  // TODO(crbug.com/756447): 128MB is the maximum IPC size, so this threshold
+  // effectively disables wrapping. Set the threshold back to 64 * 1024 after
+  // the IDB Blob reading error is fixed.
+  static constexpr unsigned kWrapThreshold = 128 * 1024 * 1024;
 
   // MIME type used for Blobs that wrap IDBValues.
   static constexpr const char* kWrapMimeType =
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
index 173cc3a..7b77eb1 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.cpp
@@ -206,15 +206,17 @@
   if (!handle_)
     return false;
 
-  if (GetDocument() && GetDocument()->GetFrame()) {
-    if (MixedContentChecker::ShouldBlockWebSocket(GetDocument()->GetFrame(),
-                                                  url)) {
+  // TODO(nhiroki): Remove dependencies on LocalFrame.
+  // (https://crbug.com/825740)
+  LocalFrame* frame = nullptr;
+  if (GetExecutionContext()->IsDocument())
+    frame = ToDocument(GetExecutionContext())->GetFrame();
+
+  if (frame) {
+    if (MixedContentChecker::ShouldBlockWebSocket(frame, url))
       return false;
-    }
-    connection_handle_for_scheduler_ = GetDocument()
-                                           ->GetFrame()
-                                           ->GetFrameScheduler()
-                                           ->OnActiveConnectionCreated();
+    connection_handle_for_scheduler_ =
+        frame->GetFrameScheduler()->OnActiveConnectionCreated();
   }
 
   if (MixedContentChecker::IsMixedContent(
@@ -257,14 +259,11 @@
                        ->GetTaskRunner(TaskType::kNetworking)
                        .get());
 
-  // TODO(ricea): Maybe lookup GetDocument()->GetFrame() less often?
-  if (handshake_throttle_ && GetDocument() && GetDocument()->GetFrame() &&
-      GetDocument()->GetFrame()->GetPage()) {
+  if (handshake_throttle_ && frame && frame->GetPage()) {
     // TODO(ricea): We may need to do something special here for SharedWorkers
     // and ServiceWorkers
     // TODO(ricea): Figure out who owns this WebFrame object and how long it can
     // be expected to live.
-    LocalFrame* frame = GetDocument()->GetFrame();
     WebLocalFrame* web_frame = WebLocalFrameImpl::FromFrame(frame);
     handshake_throttle_->ThrottleHandshake(url, web_frame, this);
   } else {
@@ -275,8 +274,8 @@
   TRACE_EVENT_INSTANT1("devtools.timeline", "WebSocketCreate",
                        TRACE_EVENT_SCOPE_THREAD, "data",
                        InspectorWebSocketCreateEvent::Data(
-                           GetDocument(), identifier_, url, protocol));
-  probe::didCreateWebSocket(GetDocument(), identifier_, url, protocol);
+                           GetExecutionContext(), identifier_, url, protocol));
+  probe::didCreateWebSocket(GetExecutionContext(), identifier_, url, protocol);
   return true;
 }
 
@@ -284,11 +283,10 @@
                                        const String& protocol) {
   network::mojom::blink::WebSocketPtr socket_ptr;
   auto socket_request = mojo::MakeRequest(&socket_ptr);
-  if (GetDocument() && GetDocument()->GetFrame()) {
-    GetDocument()->GetFrame()->GetInterfaceProvider().GetInterface(
-        std::move(socket_request));
-  }
-
+  service_manager::InterfaceProvider* interface_provider =
+      GetExecutionContext()->GetInterfaceProvider();
+  if (interface_provider)
+    interface_provider->GetInterface(std::move(socket_request));
   return Connect(url, protocol, std::move(socket_ptr));
 }
 
@@ -296,7 +294,7 @@
   NETWORK_DVLOG(1) << this << " Send(" << message << ") (CString argument)";
   // FIXME: Change the inspector API to show the entire message instead
   // of individual frames.
-  probe::didSendWebSocketFrame(GetDocument(), identifier_,
+  probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
                                WebSocketOpCode::kOpCodeText, true,
                                message.data(), message.length());
   messages_.push_back(new Message(message));
@@ -314,7 +312,7 @@
   // FIXME: We can't access the data here.
   // Since Binary data are not displayed in Inspector, this does not
   // affect actual behavior.
-  probe::didSendWebSocketFrame(GetDocument(), identifier_,
+  probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
                                WebSocketOpCode::kOpCodeBinary, true, "", 0);
   messages_.push_back(new Message(std::move(blob_data_handle)));
   ProcessSendQueue();
@@ -329,7 +327,7 @@
   // FIXME: Change the inspector API to show the entire message instead
   // of individual frames.
   probe::didSendWebSocketFrame(
-      GetDocument(), identifier_, WebSocketOpCode::kOpCodeBinary, true,
+      GetExecutionContext(), identifier_, WebSocketOpCode::kOpCodeBinary, true,
       static_cast<const char*>(buffer.Data()) + byte_offset, byte_length);
   // buffer.slice copies its contents.
   // FIXME: Reduce copy by sending the data immediately when we don't need to
@@ -346,7 +344,7 @@
                    << ")";
   // FIXME: Change the inspector API to show the entire message instead
   // of individual frames.
-  probe::didSendWebSocketFrame(GetDocument(), identifier_,
+  probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
                                WebSocketOpCode::kOpCodeText, true, data->data(),
                                data->size());
   messages_.push_back(
@@ -361,7 +359,7 @@
                    << ")";
   // FIXME: Change the inspector API to show the entire message instead
   // of individual frames.
-  probe::didSendWebSocketFrame(GetDocument(), identifier_,
+  probe::didSendWebSocketFrame(GetExecutionContext(), identifier_,
                                WebSocketOpCode::kOpCodeBinary, true,
                                data->data(), data->size());
   messages_.push_back(
@@ -382,8 +380,8 @@
                                     MessageLevel level,
                                     std::unique_ptr<SourceLocation> location) {
   NETWORK_DVLOG(1) << this << " Fail(" << reason << ")";
-  if (GetDocument())
-    probe::didReceiveWebSocketFrameError(GetDocument(), identifier_, reason);
+  probe::didReceiveWebSocketFrameError(GetExecutionContext(), identifier_,
+                                       reason);
   const String message =
       "WebSocket connection to '" + url_.ElidedString() + "' failed: " + reason;
   GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
@@ -398,8 +396,9 @@
   if (identifier_) {
     TRACE_EVENT_INSTANT1(
         "devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD,
-        "data", InspectorWebSocketEvent::Data(GetDocument(), identifier_));
-    probe::didCloseWebSocket(GetDocument(), identifier_);
+        "data",
+        InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
+    probe::didCloseWebSocket(GetExecutionContext(), identifier_);
   }
   connection_handle_for_scheduler_.reset();
   AbortAsyncOperations();
@@ -553,14 +552,7 @@
   // client->DidClose may delete this object.
 }
 
-Document* DocumentWebSocketChannel::GetDocument() {
-  ExecutionContext* context = GetExecutionContext();
-  if (context->IsDocument())
-    return ToDocument(context);
-  return nullptr;
-}
-
-ExecutionContext* DocumentWebSocketChannel::GetExecutionContext() {
+ExecutionContext* DocumentWebSocketChannel::GetExecutionContext() const {
   return loading_context_->GetExecutionContext();
 }
 
@@ -596,14 +588,12 @@
   DCHECK(handle_);
   DCHECK_EQ(handle, handle_.get());
 
-  if (GetDocument()) {
-    TRACE_EVENT_INSTANT1(
-        "devtools.timeline", "WebSocketSendHandshakeRequest",
-        TRACE_EVENT_SCOPE_THREAD, "data",
-        InspectorWebSocketEvent::Data(GetDocument(), identifier_));
-    probe::willSendWebSocketHandshakeRequest(GetDocument(), identifier_,
-                                             request.get());
-  }
+  TRACE_EVENT_INSTANT1(
+      "devtools.timeline", "WebSocketSendHandshakeRequest",
+      TRACE_EVENT_SCOPE_THREAD, "data",
+      InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
+  probe::willSendWebSocketHandshakeRequest(GetExecutionContext(), identifier_,
+                                           request.get());
   handshake_request_ = std::move(request);
 }
 
@@ -615,14 +605,12 @@
   DCHECK(handle_);
   DCHECK_EQ(handle, handle_.get());
 
-  if (GetDocument()) {
-    TRACE_EVENT_INSTANT1(
-        "devtools.timeline", "WebSocketReceiveHandshakeResponse",
-        TRACE_EVENT_SCOPE_THREAD, "data",
-        InspectorWebSocketEvent::Data(GetDocument(), identifier_));
-    probe::didReceiveWebSocketHandshakeResponse(
-        GetDocument(), identifier_, handshake_request_.get(), response);
-  }
+  TRACE_EVENT_INSTANT1(
+      "devtools.timeline", "WebSocketReceiveHandshakeResponse",
+      TRACE_EVENT_SCOPE_THREAD, "data",
+      InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
+  probe::didReceiveWebSocketHandshakeResponse(
+      GetExecutionContext(), identifier_, handshake_request_.get(), response);
   handshake_request_ = nullptr;
 }
 
@@ -678,16 +666,14 @@
   if (!fin) {
     return;
   }
-  if (GetDocument()) {
-    // FIXME: Change the inspector API to show the entire message instead
-    // of individual frames.
-    auto opcode = receiving_message_type_is_text_
-                      ? WebSocketOpCode::kOpCodeText
-                      : WebSocketOpCode::kOpCodeBinary;
-    probe::didReceiveWebSocketFrame(GetDocument(), identifier_, opcode, false,
-                                    receiving_message_data_.data(),
-                                    receiving_message_data_.size());
-  }
+  // FIXME: Change the inspector API to show the entire message instead
+  // of individual frames.
+  auto opcode = receiving_message_type_is_text_
+                    ? WebSocketOpCode::kOpCodeText
+                    : WebSocketOpCode::kOpCodeBinary;
+  probe::didReceiveWebSocketFrame(GetExecutionContext(), identifier_, opcode,
+                                  false, receiving_message_data_.data(),
+                                  receiving_message_data_.size());
   if (receiving_message_type_is_text_) {
     String message = receiving_message_data_.IsEmpty()
                          ? g_empty_string
@@ -722,11 +708,12 @@
 
   handle_.reset();
 
-  if (identifier_ && GetDocument()) {
+  if (identifier_) {
     TRACE_EVENT_INSTANT1(
         "devtools.timeline", "WebSocketDestroy", TRACE_EVENT_SCOPE_THREAD,
-        "data", InspectorWebSocketEvent::Data(GetDocument(), identifier_));
-    probe::didCloseWebSocket(GetDocument(), identifier_);
+        "data",
+        InspectorWebSocketEvent::Data(GetExecutionContext(), identifier_));
+    probe::didCloseWebSocket(GetExecutionContext(), identifier_);
     identifier_ = 0;
   }
 
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
index a2c0b02..c0a66a25 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannel.h
@@ -154,10 +154,7 @@
                       unsigned short code,
                       const String& reason);
 
-  // This may return nullptr.
-  // TODO(nhiroki): Remove dependency to document (https://crbug.com/825740).
-  Document* GetDocument();
-  ExecutionContext* GetExecutionContext();
+  ExecutionContext* GetExecutionContext() const;
 
   // WebSocketHandleClient functions.
   void DidConnect(WebSocketHandle*,
diff --git a/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.cpp b/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.cpp
index 0b9fefd..12ea9cbd 100644
--- a/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.cpp
+++ b/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.cpp
@@ -4,21 +4,30 @@
 
 #include "modules/websockets/InspectorWebSocketEvents.h"
 
-#include "core/dom/Document.h"
-#include "platform/weborigin/KURL.h"
 #include <memory>
+#include "core/dom/Document.h"
+#include "core/dom/ExecutionContext.h"
+#include "core/frame/LocalFrame.h"
+#include "core/inspector/IdentifiersFactory.h"
+#include "platform/weborigin/KURL.h"
 
 namespace blink {
 
 std::unique_ptr<TracedValue> InspectorWebSocketCreateEvent::Data(
-    Document* document,
+    ExecutionContext* execution_context,
     unsigned long identifier,
     const KURL& url,
     const String& protocol) {
   std::unique_ptr<TracedValue> value = TracedValue::Create();
   value->SetInteger("identifier", identifier);
   value->SetString("url", url.GetString());
-  value->SetString("frame", ToHexString(document->GetFrame()));
+  if (execution_context->IsDocument()) {
+    value->SetString("frame", IdentifiersFactory::FrameId(
+                                  ToDocument(execution_context)->GetFrame()));
+  } else {
+    // TODO(nhiroki): Support WorkerGlobalScope (https://crbug.com/825740).
+    NOTREACHED();
+  }
   if (!protocol.IsNull())
     value->SetString("webSocketProtocol", protocol);
   SetCallStack(value.get());
@@ -26,11 +35,17 @@
 }
 
 std::unique_ptr<TracedValue> InspectorWebSocketEvent::Data(
-    Document* document,
+    ExecutionContext* execution_context,
     unsigned long identifier) {
   std::unique_ptr<TracedValue> value = TracedValue::Create();
   value->SetInteger("identifier", identifier);
-  value->SetString("frame", ToHexString(document->GetFrame()));
+  if (execution_context->IsDocument()) {
+    value->SetString("frame", IdentifiersFactory::FrameId(
+                                  ToDocument(execution_context)->GetFrame()));
+  } else {
+    // TODO(nhiroki): Support WorkerGlobalScope (https://crbug.com/825740).
+    NOTREACHED();
+  }
   SetCallStack(value.get());
   return value;
 }
diff --git a/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.h b/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.h
index f1051bc..326d1ea 100644
--- a/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.h
+++ b/third_party/WebKit/Source/modules/websockets/InspectorWebSocketEvents.h
@@ -15,14 +15,14 @@
 
 namespace blink {
 
-class Document;
+class ExecutionContext;
 class KURL;
 
 class InspectorWebSocketCreateEvent {
   STATIC_ONLY(InspectorWebSocketCreateEvent);
 
  public:
-  static std::unique_ptr<TracedValue> Data(Document*,
+  static std::unique_ptr<TracedValue> Data(ExecutionContext*,
                                            unsigned long identifier,
                                            const KURL&,
                                            const String& protocol);
@@ -32,7 +32,8 @@
   STATIC_ONLY(InspectorWebSocketEvent);
 
  public:
-  static std::unique_ptr<TracedValue> Data(Document*, unsigned long identifier);
+  static std::unique_ptr<TracedValue> Data(ExecutionContext*,
+                                           unsigned long identifier);
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.cpp
index 5a9d840..b8ee2c2 100644
--- a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.cpp
@@ -605,6 +605,7 @@
 }
 
 bool Canvas2DLayerBridge::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) {
   if (destruction_in_progress_) {
diff --git a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.h b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.h
index cef7d835..e70ab19 100644
--- a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.h
+++ b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridge.h
@@ -86,9 +86,11 @@
   ~Canvas2DLayerBridge() override;
 
   // cc::TextureLayerClient implementation.
-  bool PrepareTransferableResource(viz::TransferableResource* out_resource,
-                                   std::unique_ptr<viz::SingleReleaseCallback>*
-                                       out_release_callback) override;
+  bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
+      viz::TransferableResource* out_resource,
+      std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback)
+      override;
 
   void FinalizeFrame();
   void SetIsHidden(bool);
diff --git a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp
index 2a3f365..b05a72c 100644
--- a/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp
@@ -295,8 +295,8 @@
 
     viz::TransferableResource resource;
     std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-    EXPECT_FALSE(
-        bridge->PrepareTransferableResource(&resource, &release_callback));
+    EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                     &release_callback));
   }
 
   void PrepareMailboxWhenContextIsLostWithFailedRestore() {
@@ -319,8 +319,8 @@
 
     viz::TransferableResource resource;
     std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-    EXPECT_FALSE(
-        bridge->PrepareTransferableResource(&resource, &release_callback));
+    EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                     &release_callback));
   }
 
   void ReleaseCallbackWithNullContextProviderWrapperTest() {
@@ -333,8 +333,8 @@
           Canvas2DLayerBridge::kForceAccelerationForTesting,
           CanvasColorParams()));
       bridge->FinalizeFrame();
-      EXPECT_TRUE(
-          bridge->PrepareTransferableResource(&resource, &release_callback));
+      EXPECT_TRUE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                      &release_callback));
     }
 
     bool lost_resource = true;
@@ -357,8 +357,8 @@
       bridge->FinalizeFrame();
       viz::TransferableResource resource;
       std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-      EXPECT_TRUE(
-          bridge->PrepareTransferableResource(&resource, &release_callback));
+      EXPECT_TRUE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                      &release_callback));
 
       bool lost_resource = true;
       release_callback->Run(gpu::SyncToken(), lost_resource);
@@ -375,7 +375,8 @@
             Canvas2DLayerBridge::kForceAccelerationForTesting,
             CanvasColorParams()));
         bridge->FinalizeFrame();
-        bridge->PrepareTransferableResource(&resource, &release_callback);
+        bridge->PrepareTransferableResource(nullptr, &resource,
+                                            &release_callback);
         // |bridge| goes out of scope and would normally be destroyed, but
         // object is kept alive by self references.
       }
@@ -1061,8 +1062,8 @@
   // Test PrepareTransferableResource() while hibernating
   viz::TransferableResource resource;
   std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-  EXPECT_FALSE(
-      bridge->PrepareTransferableResource(&resource, &release_callback));
+  EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                   &release_callback));
   EXPECT_TRUE(bridge->IsValid());
 
   // Tear down the bridge on the thread so that 'bridge' can go out of scope
@@ -1116,8 +1117,8 @@
   // Test prepareMailbox while background rendering
   viz::TransferableResource resource;
   std::unique_ptr<viz::SingleReleaseCallback> release_callback;
-  EXPECT_FALSE(
-      bridge->PrepareTransferableResource(&resource, &release_callback));
+  EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource,
+                                                   &release_callback));
   EXPECT_TRUE(bridge->IsValid());
 }
 
@@ -1147,14 +1148,14 @@
   EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id1));
   EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id1));
   DrawSomething(bridge);
-  bridge->PrepareTransferableResource(&resource1, &release_callback1);
+  bridge->PrepareTransferableResource(nullptr, &resource1, &release_callback1);
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
 
   EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id2));
   EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id2));
   DrawSomething(bridge);
-  bridge->PrepareTransferableResource(&resource2, &release_callback2);
+  bridge->PrepareTransferableResource(nullptr, &resource2, &release_callback2);
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
 
@@ -1211,14 +1212,14 @@
   EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id1));
   EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id1));
   DrawSomething(bridge);
-  bridge->PrepareTransferableResource(&resource1, &release_callback1);
+  bridge->PrepareTransferableResource(nullptr, &resource1, &release_callback1);
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
 
   EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id2));
   EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id2));
   DrawSomething(bridge);
-  bridge->PrepareTransferableResource(&resource2, &release_callback2);
+  bridge->PrepareTransferableResource(nullptr, &resource2, &release_callback2);
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
 
@@ -1270,7 +1271,7 @@
   EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id));
   EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id));
   DrawSomething(bridge);
-  bridge->PrepareTransferableResource(&resource, &release_callback);
+  bridge->PrepareTransferableResource(nullptr, &resource, &release_callback);
 
   testing::Mock::VerifyAndClearExpectations(&gl_);
 
diff --git a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h
index af74800..6457a63 100644
--- a/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h
+++ b/third_party/WebKit/Source/platform/graphics/GraphicsLayer.h
@@ -433,6 +433,8 @@
   std::unique_ptr<CompositedLayerRasterInvalidator> raster_invalidator_;
 
   base::WeakPtrFactory<GraphicsLayer> weak_ptr_factory_;
+
+  FRIEND_TEST_ALL_PREFIXES(CompositingLayerPropertyUpdaterTest, MaskLayerState);
 };
 
 // ObjectPaintInvalidatorWithContext::InvalidatePaintRectangleWithContext uses
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
index 2b69ce7..6b4521a 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
@@ -35,7 +35,9 @@
 #include <utility>
 
 #include "build/build_config.h"
+#include "cc/layers/texture_layer.h"
 #include "components/viz/common/quads/shared_bitmap.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/transferable_resource.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
@@ -251,32 +253,43 @@
   return !want_alpha_channel_ && have_alpha_channel_;
 }
 
-std::unique_ptr<viz::SharedBitmap> DrawingBuffer::CreateOrRecycleBitmap() {
-  auto it = std::remove_if(
-      recycled_bitmaps_.begin(), recycled_bitmaps_.end(),
-      [this](const RecycledBitmap& bitmap) { return bitmap.size != size_; });
+DrawingBuffer::RegisteredBitmap DrawingBuffer::CreateOrRecycleBitmap(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar) {
+  auto it = std::remove_if(recycled_bitmaps_.begin(), recycled_bitmaps_.end(),
+                           [this](const RegisteredBitmap& registered) {
+                             return registered.bitmap->size() != size_;
+                           });
   recycled_bitmaps_.Shrink(it - recycled_bitmaps_.begin());
 
   if (!recycled_bitmaps_.IsEmpty()) {
-    RecycledBitmap recycled = std::move(recycled_bitmaps_.back());
+    RegisteredBitmap recycled = std::move(recycled_bitmaps_.back());
     recycled_bitmaps_.pop_back();
-    DCHECK(recycled.size == size_);
-    return std::move(recycled.bitmap);
+    DCHECK(recycled.bitmap->size() == size_);
+    return recycled;
   }
 
-  return Platform::Current()->AllocateSharedBitmap(size_, viz::RGBA_8888);
+  viz::SharedBitmapId id = viz::SharedBitmap::GenerateId();
+  std::unique_ptr<base::SharedMemory> shm =
+      viz::bitmap_allocation::AllocateMappedBitmap(size_, viz::RGBA_8888);
+  auto bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
+      id, std::move(shm), size_, viz::RGBA_8888);
+  RegisteredBitmap registered = {
+      bitmap, bitmap_registrar->RegisterSharedBitmapId(id, bitmap)};
+  return registered;
 }
 
 bool DrawingBuffer::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) {
   ScopedStateRestorer scoped_state_restorer(this);
   bool force_gpu_result = false;
-  return PrepareTransferableResourceInternal(out_resource, out_release_callback,
-                                             force_gpu_result);
+  return PrepareTransferableResourceInternal(
+      bitmap_registrar, out_resource, out_release_callback, force_gpu_result);
 }
 
 bool DrawingBuffer::PrepareTransferableResourceInternal(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback,
     bool force_gpu_result) {
@@ -305,25 +318,25 @@
   ResolveIfNeeded();
 
   if (!using_gpu_compositing_ && !force_gpu_result) {
-    return FinishPrepareTransferableResourceSoftware(out_resource,
-                                                     out_release_callback);
+    FinishPrepareTransferableResourceSoftware(bitmap_registrar, out_resource,
+                                              out_release_callback);
   } else {
-    return FinishPrepareTransferableResourceGpu(out_resource,
-                                                out_release_callback);
+    FinishPrepareTransferableResourceGpu(out_resource, out_release_callback);
   }
+  return true;
 }
 
-bool DrawingBuffer::FinishPrepareTransferableResourceSoftware(
+void DrawingBuffer::FinishPrepareTransferableResourceSoftware(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) {
   DCHECK(state_restorer_);
-  std::unique_ptr<viz::SharedBitmap> bitmap = CreateOrRecycleBitmap();
-  if (!bitmap)
-    return false;
+  RegisteredBitmap registered = CreateOrRecycleBitmap(bitmap_registrar);
 
   // Read the framebuffer into |bitmap|.
   {
-    unsigned char* pixels = bitmap->pixels();
+    unsigned char* pixels = static_cast<unsigned char*>(
+        registered.bitmap->shared_memory()->memory());
     DCHECK(pixels);
     bool need_premultiply = want_alpha_channel_ && !premultiplied_alpha_;
     WebGLImageConversion::AlphaOp op =
@@ -336,7 +349,7 @@
   }
 
   *out_resource = viz::TransferableResource::MakeSoftware(
-      bitmap->id(), bitmap->sequence_number(), size_, viz::RGBA_8888);
+      registered.bitmap->id(), /*sequence_number=*/0, size_, viz::RGBA_8888);
   out_resource->color_space = storage_color_space_;
 
   // This holds a ref on the DrawingBuffer that will keep it alive until the
@@ -344,17 +357,15 @@
   // owns the SharedBitmap.
   auto func = WTF::Bind(&DrawingBuffer::MailboxReleasedSoftware,
                         scoped_refptr<DrawingBuffer>(this),
-                        WTF::Passed(std::move(bitmap)), size_);
+                        WTF::Passed(std::move(registered)));
   *out_release_callback = viz::SingleReleaseCallback::Create(std::move(func));
 
   if (preserve_drawing_buffer_ == kDiscard) {
     SetBufferClearNeeded(true);
   }
-
-  return true;
 }
 
-bool DrawingBuffer::FinishPrepareTransferableResourceGpu(
+void DrawingBuffer::FinishPrepareTransferableResourceGpu(
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) {
   DCHECK(state_restorer_);
@@ -447,7 +458,6 @@
 
   contents_changed_ = false;
   SetBufferClearNeeded(true);
-  return true;
 }
 
 void DrawingBuffer::MailboxReleasedGpu(scoped_refptr<ColorBuffer> color_buffer,
@@ -479,17 +489,18 @@
   recycled_color_buffer_queue_.push_front(color_buffer);
 }
 
-void DrawingBuffer::MailboxReleasedSoftware(
-    std::unique_ptr<viz::SharedBitmap> bitmap,
-    const IntSize& size,
-    const gpu::SyncToken& sync_token,
-    bool lost_resource) {
+void DrawingBuffer::MailboxReleasedSoftware(RegisteredBitmap registered,
+                                            const gpu::SyncToken& sync_token,
+                                            bool lost_resource) {
   DCHECK(!sync_token.HasData());  // No sync tokens for software resources.
-  if (destruction_in_progress_ || lost_resource || is_hidden_ || size != size_)
-    return;  // Just delete the bitmap.
+  if (destruction_in_progress_ || lost_resource || is_hidden_ ||
+      registered.bitmap->size() != size_) {
+    // Just delete the RegisteredBitmap, which will free the memory and
+    // unregister it with the compositor.
+    return;
+  }
 
-  RecycledBitmap recycled = {std::move(bitmap), size_};
-  recycled_bitmaps_.push_back(std::move(recycled));
+  recycled_bitmaps_.push_back(std::move(registered));
 }
 
 scoped_refptr<StaticBitmapImage> DrawingBuffer::TransferToStaticBitmapImage(
@@ -499,8 +510,9 @@
   viz::TransferableResource transferable_resource;
   std::unique_ptr<viz::SingleReleaseCallback> release_callback;
   constexpr bool force_gpu_result = true;
-  if (!PrepareTransferableResourceInternal(
-          &transferable_resource, &release_callback, force_gpu_result)) {
+  if (!PrepareTransferableResourceInternal(nullptr, &transferable_resource,
+                                           &release_callback,
+                                           force_gpu_result)) {
     // If we can't get a mailbox, return an transparent black ImageBitmap.
     // The only situation in which this could happen is when two or more calls
     // to transferToImageBitmap are made back-to-back, or when the context gets
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
index 2eb2e8686..1eda98c8 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
@@ -32,7 +32,10 @@
 #define DrawingBuffer_h
 
 #include <memory>
+
 #include "cc/layers/texture_layer_client.h"
+#include "cc/resources/cross_thread_shared_bitmap.h"
+#include "cc/resources/shared_bitmap_id_registrar.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/sync_token.h"
 #include "platform/PlatformExport.h"
@@ -57,10 +60,6 @@
 }
 }
 
-namespace viz {
-class SharedBitmap;
-}
-
 namespace blink {
 class CanvasColorParams;
 class Extensions3DUtil;
@@ -196,9 +195,11 @@
   bool destroyed() const { return destruction_in_progress_; }
 
   // cc::TextureLayerClient implementation.
-  bool PrepareTransferableResource(viz::TransferableResource* out_resource,
-                                   std::unique_ptr<viz::SingleReleaseCallback>*
-                                       out_release_callback) override;
+  bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
+      viz::TransferableResource* out_resource,
+      std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback)
+      override;
 
   // Returns a StaticBitmapImage backed by a texture containing the current
   // contents of the front buffer. This is done without any pixel copies. The
@@ -263,13 +264,17 @@
 
   bool Initialize(const IntSize&, bool use_multisampling);
 
+  struct RegisteredBitmap {
+    scoped_refptr<cc::CrossThreadSharedBitmap> bitmap;
+    cc::SharedBitmapIdRegistration registration;
+
+    // Explicitly move-only.
+    RegisteredBitmap(RegisteredBitmap&&) = default;
+    RegisteredBitmap& operator=(RegisteredBitmap&&) = default;
+  };
   // Shared memory bitmaps that were released by the compositor and can be used
   // again by this DrawingBuffer.
-  struct RecycledBitmap {
-    std::unique_ptr<viz::SharedBitmap> bitmap;
-    IntSize size;
-  };
-  Vector<RecycledBitmap> recycled_bitmaps_;
+  Vector<RegisteredBitmap> recycled_bitmaps_;
 
  private:
   friend class ScopedRGBEmulationForBlitFramebuffer;
@@ -363,15 +368,17 @@
   void ResolveIfNeeded();
 
   bool PrepareTransferableResourceInternal(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* out_resource,
       std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback,
       bool force_gpu_result);
 
   // Helper functions to be called only by PrepareTransferableResourceInternal.
-  bool FinishPrepareTransferableResourceGpu(
+  void FinishPrepareTransferableResourceGpu(
       viz::TransferableResource* out_resource,
       std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback);
-  bool FinishPrepareTransferableResourceSoftware(
+  void FinishPrepareTransferableResourceSoftware(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
       viz::TransferableResource* out_resource,
       std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback);
 
@@ -380,8 +387,7 @@
   void MailboxReleasedGpu(scoped_refptr<ColorBuffer>,
                           const gpu::SyncToken&,
                           bool lost_resource);
-  void MailboxReleasedSoftware(std::unique_ptr<viz::SharedBitmap>,
-                               const IntSize&,
+  void MailboxReleasedSoftware(RegisteredBitmap,
                                const gpu::SyncToken&,
                                bool lost_resource);
 
@@ -391,7 +397,8 @@
 
   void ClearPlatformLayer();
 
-  std::unique_ptr<viz::SharedBitmap> CreateOrRecycleBitmap();
+  RegisteredBitmap CreateOrRecycleBitmap(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar);
 
   // Updates the current size of the buffer, ensuring that
   // s_currentResourceUsePixels is updated.
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
index adc549a6..0ef2660 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferSoftwareRenderingTest.cpp
@@ -4,6 +4,7 @@
 
 #include "platform/graphics/gpu/DrawingBuffer.h"
 
+#include "cc/resources/shared_bitmap_id_registrar.h"
 #include "components/viz/common/resources/single_release_callback.h"
 #include "components/viz/common/resources/transferable_resource.h"
 #include "gpu/command_buffer/client/gles2_interface_stub.h"
@@ -19,6 +20,14 @@
 namespace blink {
 namespace {
 
+class TestSharedBitmapIdRegistar : public cc::SharedBitmapIdRegistrar {
+  virtual cc::SharedBitmapIdRegistration RegisterSharedBitmapId(
+      const viz::SharedBitmapId& id,
+      scoped_refptr<cc::CrossThreadSharedBitmap> bitmap) {
+    return {};
+  }
+};
+
 class DrawingBufferSoftwareCompositingTest : public testing::Test {
  protected:
   void SetUp() override {
@@ -36,6 +45,7 @@
   }
 
   scoped_refptr<DrawingBufferForTests> drawing_buffer_;
+  TestSharedBitmapIdRegistar test_shared_bitmap_id_registrar_;
 };
 
 TEST_F(DrawingBufferSoftwareCompositingTest, BitmapRecycling) {
@@ -49,7 +59,8 @@
   drawing_buffer_->Resize(initial_size);
   drawing_buffer_->MarkContentsChanged();
   drawing_buffer_->PrepareTransferableResource(
-      &resource, &release_callback1);  // create a bitmap.
+      &test_shared_bitmap_id_registrar_, &resource,
+      &release_callback1);  // create a bitmap.
   EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount());
   release_callback1->Run(
       gpu::SyncToken(),
@@ -57,7 +68,8 @@
   EXPECT_EQ(1, drawing_buffer_->RecycledBitmapCount());
   drawing_buffer_->MarkContentsChanged();
   drawing_buffer_->PrepareTransferableResource(
-      &resource, &release_callback2);  // recycle a bitmap.
+      &test_shared_bitmap_id_registrar_, &resource,
+      &release_callback2);  // recycle a bitmap.
   EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount());
   release_callback2->Run(
       gpu::SyncToken(),
@@ -67,7 +79,7 @@
   drawing_buffer_->MarkContentsChanged();
   // Regression test for crbug.com/647896 - Next line must not crash
   drawing_buffer_->PrepareTransferableResource(
-      &resource,
+      &test_shared_bitmap_id_registrar_, &resource,
       &release_callback3);  // cause recycling queue to be purged due to resize
   EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount());
   release_callback3->Run(gpu::SyncToken(), false /* lostResource */);
@@ -90,7 +102,8 @@
   gl_->SaveState();
   drawing_buffer_->Resize(initial_size);
   drawing_buffer_->MarkContentsChanged();
-  drawing_buffer_->PrepareTransferableResource(&resource, &release_callback);
+  drawing_buffer_->PrepareTransferableResource(
+      &test_shared_bitmap_id_registrar_, &resource, &release_callback);
   gl_->GetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawBinding);
   gl_->GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readBinding);
   EXPECT_EQ(static_cast<GLint>(draw_framebuffer_binding), drawBinding);
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
index 2a79f32..0ecfcd3 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
@@ -164,7 +164,7 @@
 
   // Produce one resource at size 100x100.
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   VerifyStateWasRestored();
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -177,7 +177,7 @@
 
   // Produce a resource at this size.
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize());
   VerifyStateWasRestored();
@@ -191,7 +191,7 @@
 
   // Prepare another resource and verify that it's the correct size.
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
   VerifyStateWasRestored();
@@ -199,7 +199,7 @@
   // Prepare one final resource and verify that it's the correct size.
   release_callback->Run(gpu::SyncToken(), false /* lostResource */);
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   VerifyStateWasRestored();
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
@@ -224,17 +224,17 @@
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_STENCIL_BUFFER_BIT);
   VerifyStateWasRestored();
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource1,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1,
                                                            &release_callback1));
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_DEPTH_BUFFER_BIT);
   VerifyStateWasRestored();
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource2,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2,
                                                            &release_callback2));
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   drawing_buffer_->ClearFramebuffers(GL_COLOR_BUFFER_BIT);
   VerifyStateWasRestored();
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource3,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3,
                                                            &release_callback3));
 
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
@@ -268,15 +268,15 @@
   std::unique_ptr<viz::SingleReleaseCallback> release_callback3;
 
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource1,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1,
                                                            &release_callback1));
   VerifyStateWasRestored();
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource2,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2,
                                                            &release_callback2));
   VerifyStateWasRestored();
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource3,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3,
                                                            &release_callback3));
   VerifyStateWasRestored();
 
@@ -310,13 +310,13 @@
 
   // Produce resources.
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource1,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1,
                                                            &release_callback1));
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource2,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2,
                                                            &release_callback2));
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource3,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3,
                                                            &release_callback3));
 
   // Release resources by specific order; 1, 3, 2.
@@ -333,7 +333,7 @@
   std::unique_ptr<viz::SingleReleaseCallback> recycled_release_callback1;
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(
-      &recycled_resource1, &recycled_release_callback1));
+      nullptr, &recycled_resource1, &recycled_release_callback1));
   EXPECT_EQ(resource2.mailbox_holder.mailbox,
             recycled_resource1.mailbox_holder.mailbox);
 
@@ -342,7 +342,7 @@
   std::unique_ptr<viz::SingleReleaseCallback> recycled_release_callback2;
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
   EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(
-      &recycled_resource2, &recycled_release_callback2));
+      nullptr, &recycled_resource2, &recycled_release_callback2));
   EXPECT_NE(resource1.mailbox_holder.mailbox,
             recycled_resource2.mailbox_holder.mailbox);
   EXPECT_NE(resource2.mailbox_holder.mailbox,
@@ -363,7 +363,7 @@
   // Produce resources.
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
   EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   // PrepareTransferableResource() does not wait for any sync point.
   EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken());
@@ -375,7 +375,7 @@
   EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken());
 
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   // m_drawingBuffer waits for the sync point when recycling in
   // PrepareTransferableResource().
@@ -435,7 +435,7 @@
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id1)).Times(1);
   // Produce one resource at size 100x100.
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
   EXPECT_TRUE(resource.is_overlay_candidate);
@@ -460,7 +460,7 @@
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id3)).Times(1);
   // Produce a resource at this size.
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize());
   EXPECT_TRUE(resource.is_overlay_candidate);
@@ -484,7 +484,7 @@
   EXPECT_CALL(*gl_, BindTexImage2DMock(image_id5)).Times(1);
   // Prepare another resource and verify that it's the correct size.
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
   EXPECT_TRUE(resource.is_overlay_candidate);
@@ -494,7 +494,7 @@
   // Prepare one final resource and verify that it's the correct size.
   release_callback->Run(gpu::SyncToken(), false /* lostResource */);
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
   EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize());
   EXPECT_TRUE(resource.is_overlay_candidate);
@@ -523,7 +523,7 @@
   EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1);
   IntSize initial_size(kInitialWidth, kInitialHeight);
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource1,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1,
                                                            &release_callback1));
   EXPECT_TRUE(resource1.is_overlay_candidate);
   testing::Mock::VerifyAndClearExpectations(gl_);
@@ -533,7 +533,7 @@
   // still be provided, but this time with allowOverlay = false.
   gl_->SetCreateImageChromiumFail(true);
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource2,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2,
                                                            &release_callback2));
   EXPECT_FALSE(resource2.is_overlay_candidate);
   VerifyStateWasRestored();
@@ -543,7 +543,7 @@
   EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1);
   gl_->SetCreateImageChromiumFail(false);
   EXPECT_TRUE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource3,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3,
                                                            &release_callback3));
   EXPECT_TRUE(resource3.is_overlay_candidate);
   testing::Mock::VerifyAndClearExpectations(gl_);
@@ -715,7 +715,7 @@
 
   // Produce resources.
   EXPECT_FALSE(drawing_buffer_->MarkContentsChanged());
-  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(&resource,
+  EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource,
                                                            &release_callback));
 
   gpu::SyncToken wait_sync_token;
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
index c95e59c..088f8f464 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
@@ -73,6 +73,7 @@
 }
 
 bool ImageLayerBridge::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registrar,
     viz::TransferableResource* out_resource,
     std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) {
   if (disposed_)
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.h b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.h
index 0239416e..52dd033f 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.h
@@ -34,9 +34,11 @@
   void Dispose();
 
   // cc::TextureLayerClient implementation.
-  bool PrepareTransferableResource(viz::TransferableResource* out_resource,
-                                   std::unique_ptr<viz::SingleReleaseCallback>*
-                                       out_release_callback) override;
+  bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registrar,
+      viz::TransferableResource* out_resource,
+      std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback)
+      override;
 
   void ResourceReleasedGpu(scoped_refptr<StaticBitmapImage>,
                            const gpu::SyncToken&,
diff --git a/third_party/WebKit/public/BUILD.gn b/third_party/WebKit/public/BUILD.gn
index 8f26bf6..3df9e52 100644
--- a/third_party/WebKit/public/BUILD.gn
+++ b/third_party/WebKit/public/BUILD.gn
@@ -768,7 +768,6 @@
     "platform/modules/permissions/permission.mojom",
     "platform/modules/permissions/permission_status.mojom",
     "platform/modules/presentation/presentation.mojom",
-    "platform/modules/webauth/authenticator.mojom",
     "platform/modules/webdatabase/web_database.mojom",
     "platform/oom_intervention.mojom",
     "platform/referrer.mojom",
@@ -831,6 +830,7 @@
     "platform/modules/installedapp/related_application.mojom",
     "platform/modules/mediasession/media_session.mojom",
     "platform/modules/payments/payment_request.mojom",
+    "platform/modules/webauth/authenticator.mojom",
     "platform/modules/webshare/webshare.mojom",
     "web/remote_objects.mojom",
   ]
diff --git a/third_party/closure_compiler/interfaces/language_settings_private_interface.js b/third_party/closure_compiler/interfaces/language_settings_private_interface.js
index 9a67ba11..19e0c33 100644
--- a/third_party/closure_compiler/interfaces/language_settings_private_interface.js
+++ b/third_party/closure_compiler/interfaces/language_settings_private_interface.js
@@ -112,6 +112,13 @@
    * @see https://developer.chrome.com/extensions/languageSettingsPrivate#method-removeInputMethod
    */
   removeInputMethod: assertNotReached,
+
+  /**
+   * Tries to download the dictionary after a failed download.
+   * @param {string} languageCode
+   * @see https://developer.chrome.com/extensions/languageSettingsPrivate#method-retryDownloadDictionary
+   */
+  retryDownloadDictionary: assertNotReached,
 };
 
 /**
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 4d6903a..51499cc 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 58e4bbecc246543b60e4bc4bbdccd2e24d3157c9
+Revision: 246ecc6686f3d8d6d03b8acb7c0812a37e842231
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/tools/generate_dump.cc b/third_party/crashpad/crashpad/tools/generate_dump.cc
index c1fbde8..cce0823 100644
--- a/third_party/crashpad/crashpad/tools/generate_dump.cc
+++ b/third_party/crashpad/crashpad/tools/generate_dump.cc
@@ -47,7 +47,7 @@
 #include "util/win/xp_compat.h"
 #elif defined(OS_FUCHSIA)
 #include "snapshot/fuchsia/process_snapshot_fuchsia.h"
-#elif defined(OS_LINUX)
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
 #include "snapshot/linux/process_snapshot_linux.h"
 #endif  // OS_MACOSX
 
@@ -201,7 +201,7 @@
     if (!process_snapshot.Initialize(ZX_HANDLE_INVALID)) {
       return EXIT_FAILURE;
     }
-#elif defined(OS_LINUX)
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
     // TODO(jperaza): https://crashpad.chromium.org/bug/30.
     ProcessSnapshotLinux process_snapshot;
     if (!process_snapshot.Initialize(nullptr)) {
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn
index a703492..1e2c968 100644
--- a/third_party/crashpad/crashpad/util/BUILD.gn
+++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -17,7 +17,7 @@
 declare_args() {
   if (crashpad_is_linux) {
     # Whether the libcurl-based HTTPTransport implementation should be built.
-    enable_http_transport_libcurl = true
+    crashpad_enable_http_transport_libcurl = !crashpad_is_in_chromium
   }
 }
 
@@ -251,8 +251,8 @@
     sources += get_target_outputs(":mig")
   }
 
-  if (crashpad_is_linux) {
-    if (enable_http_transport_libcurl) {
+  if (crashpad_is_linux || crashpad_is_android) {
+    if (crashpad_is_linux && crashpad_enable_http_transport_libcurl) {
       sources += [ "net/http_transport_libcurl.cc" ]
     } else {
       sources += [ "net/http_transport_none.cc" ]
@@ -420,7 +420,7 @@
     include_dirs += [ "$root_build_dir/gen" ]
   }
 
-  if (crashpad_is_linux && enable_http_transport_libcurl) {
+  if (crashpad_is_linux && crashpad_enable_http_transport_libcurl) {
     libs = [ "curl" ]
   }
 
@@ -497,7 +497,7 @@
   }
 
   if (!crashpad_is_android && !crashpad_is_fuchsia &&
-      (!crashpad_is_linux || enable_http_transport_libcurl)) {
+      (!crashpad_is_linux || crashpad_enable_http_transport_libcurl)) {
     # Android and Fuchsia will each require an HTTPTransport implementation
     # (libcurl isn’t in either’s SDK) and a solution to
     # http_transport_test_server.py, because Python isn’t available on either.
diff --git a/third_party/crashpad/crashpad/util/util.gyp b/third_party/crashpad/crashpad/util/util.gyp
index e0394e2..45de7a62 100644
--- a/third_party/crashpad/crashpad/util/util.gyp
+++ b/third_party/crashpad/crashpad/util/util.gyp
@@ -177,6 +177,7 @@
         'net/http_transport.h',
         'net/http_transport_libcurl.cc',
         'net/http_transport_mac.mm',
+        'net/http_transport_none.cc',
         'net/http_transport_win.cc',
         'net/url.cc',
         'net/url.h',
@@ -393,6 +394,11 @@
             'net/http_transport_libcurl.cc',
           ],
         }],
+        ['OS!="android"', {
+          'sources!': [
+            'net/http_transport_none.cc',
+          ],
+        }],
         ['OS!="linux" and OS!="android"', {
           'sources/': [
             ['exclude', '^process/'],
diff --git a/third_party/feed/BUILD.gn b/third_party/feed/BUILD.gn
index 55ff591..67752fc 100644
--- a/third_party/feed/BUILD.gn
+++ b/third_party/feed/BUILD.gn
@@ -14,12 +14,20 @@
   custom_package = "com.google.android.libraries.feed.piet"
 }
 
+android_resources("hostimpl_resources") {
+  resource_dirs = [
+    "src/src/main/java/com/google/android/libraries/feed/hostimpl/stream/res",
+  ]
+  custom_package = "com.google.android.libraries.feed.hostimpl.stream"
+}
+
 android_library("feed_lib_java") {
   chromium_code = false
   java_files = feed_lib_java_sources
 
   deps = [
     ":feed_lib_proto_java",
+    ":hostimpl_resources",
     ":piet_resources",
     "//third_party/android_tools:android_support_annotations_java",
     "//third_party/android_tools:android_support_cardview_java",
diff --git a/third_party/feed/README.chromium b/third_party/feed/README.chromium
index 0e7d954..a63bf1a 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: c9c8ed449e6a4a15715c4bb2e9be3a12a1e01250
+Revision: 31ad36da1a0f992ca482506ad1ff7f94dd94573b
 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 567f123..b7b9ca5 100644
--- a/third_party/feed/java_sources.gni
+++ b/third_party/feed/java_sources.gni
@@ -30,7 +30,6 @@
   "src/src/main/java/com/google/android/libraries/feed/api/store/ContentMutation.java",
   "src/src/main/java/com/google/android/libraries/feed/api/store/SessionMutation.java",
   "src/src/main/java/com/google/android/libraries/feed/api/store/Store.java",
-  "src/src/main/java/com/google/android/libraries/feed/api/stream/CardConfiguration.java",
   "src/src/main/java/com/google/android/libraries/feed/api/stream/ContentChangedListener.java",
   "src/src/main/java/com/google/android/libraries/feed/api/stream/ScrollListener.java",
   "src/src/main/java/com/google/android/libraries/feed/api/stream/Stream.java",
@@ -46,7 +45,10 @@
   "src/src/main/java/com/google/android/libraries/feed/common/Dumpable.java",
   "src/src/main/java/com/google/android/libraries/feed/common/Dumper.java",
   "src/src/main/java/com/google/android/libraries/feed/common/Logger.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/SettableSupplier.java",
   "src/src/main/java/com/google/android/libraries/feed/common/SimpleSettableFuture.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/Supplier.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/SynchronousConsumerLatch.java",
   "src/src/main/java/com/google/android/libraries/feed/common/SystemClockImpl.java",
   "src/src/main/java/com/google/android/libraries/feed/common/TimingUtils.java",
   "src/src/main/java/com/google/android/libraries/feed/common/UiRunnableHandlerImpl.java",
@@ -80,7 +82,6 @@
   "src/src/main/java/com/google/android/libraries/feed/feedstore/internal/SessionState.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/common/Callback.java",
   "src/src/main/java/com/google/android/libraries/feed/host/common/ProtoExtensionProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/host/config/ConfigManager.java",
   "src/src/main/java/com/google/android/libraries/feed/host/imageloader/ImageLoaderApi.java",
@@ -97,10 +98,15 @@
   "src/src/main/java/com/google/android/libraries/feed/host/storage/JournalMutation.java",
   "src/src/main/java/com/google/android/libraries/feed/host/storage/JournalOperation.java",
   "src/src/main/java/com/google/android/libraries/feed/host/storage/JournalStorage.java",
+  "src/src/main/java/com/google/android/libraries/feed/host/stream/CardConfiguration.java",
+  "src/src/main/java/com/google/android/libraries/feed/host/stream/StreamConfiguration.java",
   "src/src/main/java/com/google/android/libraries/feed/hostimpl/logging/LoggingApiImpl.java",
   "src/src/main/java/com/google/android/libraries/feed/hostimpl/scheduler/SchedulerApiImpl.java",
   "src/src/main/java/com/google/android/libraries/feed/hostimpl/storage/InMemoryContentStorage.java",
   "src/src/main/java/com/google/android/libraries/feed/hostimpl/storage/InMemoryJournalStorage.java",
+  "src/src/main/java/com/google/android/libraries/feed/hostimpl/storage/PersistentContentStorage.java",
+  "src/src/main/java/com/google/android/libraries/feed/hostimpl/stream/DefaultCardConfiguration.java",
+  "src/src/main/java/com/google/android/libraries/feed/hostimpl/stream/DefaultStreamConfiguration.java",
   "src/src/main/java/com/google/android/libraries/feed/mocknetworkclient/MockServerNetworkClient.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/AdapterFactory.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/AdapterParameters.java",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f49acc2..0d6ac847 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -6096,6 +6096,7 @@
   <int value="-402" label="CACHE_WRITE_FAILURE"/>
   <int value="-401" label="CACHE_READ_FAILURE"/>
   <int value="-400" label="CACHE_MISS"/>
+  <int value="-375" label="TOO_MANY_RETRIES"/>
   <int value="-374" label="SPDY_CLAIMED_PUSHED_STREAM_RESET_BY_SERVER"/>
   <int value="-373" label="SPDY_PUSHED_STREAM_NOT_AVAILABLE"/>
   <int value="-372" label="SPDY_RST_STREAM_NO_ERROR_RECEIVED"/>
@@ -30204,6 +30205,7 @@
   <int value="372" label="SPDY_RST_STREAM_NO_ERROR_RECEIVED"/>
   <int value="373" label="SPDY_PUSHED_STREAM_NOT_AVAILABLE"/>
   <int value="374" label="SPDY_CLAIMED_PUSHED_STREAM_RESET_BY_SERVER"/>
+  <int value="375" label="TOO_MANY_RETRIES"/>
   <int value="400" label="CACHE_MISS"/>
   <int value="401" label="CACHE_READ_FAILURE"/>
   <int value="402" label="CACHE_WRITE_FAILURE"/>
@@ -41018,6 +41020,12 @@
   <int value="2" label="On browser window available"/>
 </enum>
 
+<enum name="SoftwareReporterRebootPromptType">
+  <int value="1" label="SettingsPageOpened"/>
+  <int value="2" label="ModalDialogShown"/>
+  <int value="3" label="NonModalDialogShown"/>
+</enum>
+
 <enum name="SoftwareReporterSequenceResult">
   <int value="1" label="Not scheduled"/>
   <int value="2" label="Timed-out"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 3bbd4b0..2ce9d98 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -14544,6 +14544,9 @@
 </histogram>
 
 <histogram name="DataReductionProxy.UserViewedSavingsSize" units="KB">
+  <obsolete>
+    Deprecated March 2018. Percent no longer displayed.
+  </obsolete>
   <owner>tbansal@chromium.org</owner>
   <summary>
     The total data saved displayed to the user. Computed over the last 30 days.
@@ -36741,6 +36744,15 @@
   </summary>
 </histogram>
 
+<histogram name="Media.PreloadMetadataSuspendWasIdeal" enum="Boolean">
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Indicates if a suspend initiated for preload=metadata was ideal. I.e. we did
+    not immediately resume after completing the suspend when signaling
+    ReadyState::HAVE_FUTURE_DATA.
+  </summary>
+</histogram>
+
 <histogram name="Media.RebuffersCount" units="rebuffers">
   <owner>dalecurtis@chromium.org</owner>
   <summary>
@@ -55822,6 +55834,17 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.NotificationHelper.ServerRuntime" units="ms">
+  <owner>chengx@chromium.org</owner>
+  <summary>
+    The runtime of the notification_helper server process. This process is
+    created when a notification is activated from the Windows Action Center.
+    Once created, this process does some COM class registration work, so that
+    Windows can call certain APIs to handle the notification activation. Upon
+    finishing the API calls, Windows signals this process to exit.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.PerNotificationActions"
     enum="NotificationActionType">
   <owner>dewittj@chromium.org</owner>
@@ -57435,6 +57458,11 @@
 
 <histogram name="OfflinePages.DeletePage.TotalPageSizeAsPercentageOfFreeSpace"
     units="%">
+  <obsolete>
+    Deprecated as of 03/2018. Replaced by similar metric
+    OfflinePages.StorageInfo.InternalUsagePercentage and
+    OfflinePages.StorageInfo.ExternalUsagePercentage.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>jianli@chromium.org</owner>
   <summary>
@@ -58040,6 +58068,10 @@
 </histogram>
 
 <histogram name="OfflinePages.SavePage.FreeSpaceMB" units="MB">
+  <obsolete>
+    Deprecated as of 03/2018. Replaced by similar metric
+    OfflinePages.StorageInfo.InternalFreeSpaceMB.
+  </obsolete>
   <owner>jianli@chromium.org</owner>
   <summary>
     The amount of free space available, in megabytes, on the user's device after
@@ -58139,6 +58171,58 @@
   </summary>
 </histogram>
 
+<histogram name="OfflinePages.StorageInfo.ExternalArchiveSizeMiB" units="MiB">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The storage space used by the archive files of offline pages, including all
+    files in public directory with mhtml/mht extensions. Collected after every
+    time an offline page is saved successfully.
+  </summary>
+</histogram>
+
+<histogram name="OfflinePages.StorageInfo.ExternalFreeSpaceMiB" units="MiB">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The amount of free space on the external drive (which contains the public
+    download directory). Collected after every time an offline page is saved
+    successfully.
+  </summary>
+</histogram>
+
+<histogram name="OfflinePages.StorageInfo.ExternalUsagePercentage" units="%">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The percentage of storage space on the external volume (that contains public
+    download directory) used by offline pages archives.
+  </summary>
+</histogram>
+
+<histogram name="OfflinePages.StorageInfo.InternalArchiveSizeMiB" units="MiB">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The storage space used by the archive files of offline pages, including the
+    files in temporary and private directory. Collected after every time an
+    offline page is saved successfully.
+  </summary>
+</histogram>
+
+<histogram name="OfflinePages.StorageInfo.InternalFreeSpaceMiB" units="MiB">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The amount of free space on the internal drive (which contains the app
+    directory). Collected after every time an offline page is saved
+    successfully.
+  </summary>
+</histogram>
+
+<histogram name="OfflinePages.StorageInfo.InternalUsagePercentage" units="%">
+  <owner>romax@chromium.org</owner>
+  <summary>
+    The percentage of storage space on internal volume (that contains the app
+    directory) used by offline pages archives.
+  </summary>
+</histogram>
+
 <histogram name="OfflinePages.TabRestore" enum="OfflinePagesTabRestoreType">
   <owner>carlosk@chromium.org</owner>
   <summary>
@@ -58152,6 +58236,10 @@
 </histogram>
 
 <histogram name="OfflinePages.TotalPageSize" units="MB">
+  <obsolete>
+    Deprecated as of 03/2018. Replaced by similar metric
+    OfflinePages.StorageInfo.TotalArchiveSize.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>jianli@chromium.org</owner>
   <summary>
@@ -86964,6 +87052,15 @@
   </summary>
 </histogram>
 
+<histogram name="SoftwareReporter.Cleaner.RebootPromptShown"
+    enum="SoftwareReporterRebootPromptType">
+  <owner>veranika@chromium.org</owner>
+  <summary>
+    Indicates how the user was prompted to reboot the machine to complete a run
+    of the Chrome Cleanup Tool.
+  </summary>
+</histogram>
+
 <histogram name="SoftwareReporter.Cleaner.RebootResponse" enum="Boolean">
   <owner>ftirelo@chromium.org</owner>
   <summary>
@@ -113035,6 +113132,8 @@
   <suffix name="BrowserMetrics" label="For browser process metrics."/>
   <suffix name="FieldTrialAllocator" label="For field-trial allocator."/>
   <suffix name="GpuMetrics" label="For GPU process metrics."/>
+  <suffix name="NotificationHelperMetrics"
+      label="For notification_helper process metrics."/>
   <suffix name="PpapiBrokerMetrics"
       label="For &quot;PPAPI broker&quot; process metrics."/>
   <suffix name="PpapiPluginMetrics"
@@ -113055,6 +113154,8 @@
   <suffix name="CrashpadMetrics" label="For metrics from Crashpad."/>
   <suffix name="FieldTrialAllocator" label="For field-trial allocator."/>
   <suffix name="GpuMetrics" label="For GPU process metrics."/>
+  <suffix name="NotificationHelperMetrics"
+      label="For notification_helper process metrics."/>
   <suffix name="PpapiBrokerMetrics"
       label="For &quot;PPAPI broker&quot; process metrics."/>
   <suffix name="PpapiPluginMetrics"
@@ -113074,6 +113175,8 @@
   <suffix name="BrowserMetrics" label="For browser process metrics."/>
   <suffix name="FieldTrialAllocator" label="For field-trial allocator."/>
   <suffix name="GpuMetrics" label="For GPU process metrics."/>
+  <suffix name="NotificationHelperMetrics"
+      label="For notification_helper process metrics."/>
   <suffix name="PpapiBrokerMetrics"
       label="For &quot;PPAPI broker&quot; process metrics."/>
   <suffix name="PpapiPluginMetrics"
@@ -116311,6 +116414,13 @@
   <affected-histogram name="WebFont.DownloadTime.1.10KBTo50KB"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="WebMediaPlayerContentTypes" separator=".">
+  <suffix name="Audio"/>
+  <suffix name="AudioVideo"/>
+  <suffix name="Video"/>
+  <affected-histogram name="Media.PreloadMetadataSuspendWasIdeal"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="WebMediaPlayerImplTypes" separator=".">
   <affected-histogram name="Media.TimeToFirstFrame"/>
   <affected-histogram name="Media.TimeToMetadata"/>
diff --git a/ui/app_list/app_list_constants.cc b/ui/app_list/app_list_constants.cc
index 65209f39..494cadf 100644
--- a/ui/app_list/app_list_constants.cc
+++ b/ui/app_list/app_list_constants.cc
@@ -114,7 +114,7 @@
 const float kDragDropAppIconScale = 1.2f;
 
 // The drag and drop icon scaling up or down animation transition duration.
-const int kDragDropAppIconScaleTransitionInMs = 20;
+const int kDragDropAppIconScaleTransitionInMs = 200;
 
 // The number of apps shown in the start page app grid.
 const int kNumStartPageTiles = 5;
diff --git a/ui/app_list/views/search_result_tile_item_view.cc b/ui/app_list/views/search_result_tile_item_view.cc
index 5d0f270..798e8f6 100644
--- a/ui/app_list/views/search_result_tile_item_view.cc
+++ b/ui/app_list/views/search_result_tile_item_view.cc
@@ -310,6 +310,7 @@
   }
   SetBackgroundHighlighted(true);
   UpdateBackgroundColor();
+  NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
 }
 
 void SearchResultTileItemView::OnBlur() {
diff --git a/ui/base/material_design/material_design_controller.cc b/ui/base/material_design/material_design_controller.cc
index bfd3312..d4dd4b1 100644
--- a/ui/base/material_design/material_design_controller.cc
+++ b/ui/base/material_design/material_design_controller.cc
@@ -14,17 +14,14 @@
 #include "ui/base/ui_base_switches.h"
 
 #if defined(OS_CHROMEOS)
-#include "ui/base/touch/touch_device.h"
-#include "ui/events/devices/device_data_manager.h"
-
-#if defined(USE_OZONE)
 #include <fcntl.h>
 
 #include "base/files/file_enumerator.h"
+#include "base/files/scoped_file.h"
 #include "base/threading/thread_restrictions.h"
+#include "ui/base/touch/touch_device.h"
+#include "ui/events/devices/device_data_manager.h"
 #include "ui/events/ozone/evdev/event_device_info.h"  // nogncheck
-#endif  // defined(USE_OZONE)
-
 #endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_WIN)
@@ -33,6 +30,48 @@
 #endif
 
 namespace ui {
+namespace {
+
+#if defined(OS_CHROMEOS)
+
+// Whether to use MATERIAL_TOUCH_OPTIMIZED when a touch device is detected.
+// Enabled by default on ChromeOS.
+const base::Feature kTouchOptimizedUi = {"TouchOptimizedUi",
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
+
+MaterialDesignController::Mode GetDefaultTouchDeviceMode() {
+  return base::FeatureList::IsEnabled(kTouchOptimizedUi)
+             ? MaterialDesignController::MATERIAL_TOUCH_OPTIMIZED
+             : MaterialDesignController::MATERIAL_HYBRID;
+}
+
+bool HasTouchscreen() {
+  // If a scan of available devices has already completed, use that.
+  if (DeviceDataManager::HasInstance() &&
+      DeviceDataManager::GetInstance()->AreDeviceListsComplete())
+    return GetTouchScreensAvailability() == TouchScreensAvailability::ENABLED;
+
+  // Otherwise perform our own scan to determine the presence of a touchscreen.
+  // Note this is a one-time call that occurs during device startup or restart.
+  base::FileEnumerator file_enum(
+      base::FilePath(FILE_PATH_LITERAL("/dev/input")), false,
+      base::FileEnumerator::FILES, FILE_PATH_LITERAL("event*[0-9]"));
+  for (base::FilePath path = file_enum.Next(); !path.empty();
+       path = file_enum.Next()) {
+    EventDeviceInfo devinfo;
+    base::ScopedFD fd(
+        open(path.value().c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
+    if (fd.is_valid() && devinfo.Initialize(fd.get(), path) &&
+        devinfo.HasTouchscreen())
+      return true;
+  }
+
+  return false;
+}
+
+#endif  // OS_CHROMEOS
+
+}  // namespace
 
 bool MaterialDesignController::is_mode_initialized_ = false;
 
@@ -97,35 +136,11 @@
 // static
 MaterialDesignController::Mode MaterialDesignController::DefaultMode() {
 #if defined(OS_CHROMEOS)
-  // If a scan of available devices has already completed, use material-hybrid
-  // if a touchscreen is present.
-  if (DeviceDataManager::HasInstance() &&
-      DeviceDataManager::GetInstance()->AreDeviceListsComplete()) {
-    return GetTouchScreensAvailability() == TouchScreensAvailability::ENABLED
-               ? MATERIAL_HYBRID
-               : MATERIAL_NORMAL;
-  }
-
-#if defined(USE_OZONE)
-  // Otherwise perform our own scan to determine the presence of a touchscreen.
-  // Note this is a one-time call that occurs during device startup or restart.
-  base::ThreadRestrictions::ScopedAllowIO allow_io;
-  base::FileEnumerator file_enum(
-      base::FilePath(FILE_PATH_LITERAL("/dev/input")), false,
-      base::FileEnumerator::FILES, FILE_PATH_LITERAL("event*[0-9]"));
-  for (base::FilePath path = file_enum.Next(); !path.empty();
-       path = file_enum.Next()) {
-    EventDeviceInfo devinfo;
-    int fd = open(path.value().c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
-    if (fd >= 0) {
-      if (devinfo.Initialize(fd, path) && devinfo.HasTouchscreen()) {
-        close(fd);
-        return MATERIAL_HYBRID;
-      }
-      close(fd);
-    }
-  }
-#endif  // defined(USE_OZONE)
+  // This is called (once) early in device startup to initialize core UI, so
+  // the UI thread should be blocked to perform the device query.
+  base::ScopedAllowBlocking allow_io;
+  if (HasTouchscreen())
+    return GetDefaultTouchDeviceMode();
 #endif  // defined(OS_CHROMEOS)
 
   return MATERIAL_NORMAL;
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index c0489bd2..28bec44 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -1026,6 +1026,7 @@
 }
 
 bool Layer::PrepareTransferableResource(
+    cc::SharedBitmapIdRegistrar* bitmap_registar,
     viz::TransferableResource* resource,
     std::unique_ptr<viz::SingleReleaseCallback>* release_callback) {
   if (!transfer_release_callback_)
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 5a0eb3f..8d084046 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -396,6 +396,7 @@
 
   // TextureLayerClient implementation.
   bool PrepareTransferableResource(
+      cc::SharedBitmapIdRegistrar* bitmap_registar,
       viz::TransferableResource* resource,
       std::unique_ptr<viz::SingleReleaseCallback>* release_callback) override;
 
diff --git a/ui/gfx/color_space.cc b/ui/gfx/color_space.cc
index 6ef4a870..188cc48 100644
--- a/ui/gfx/color_space.cc
+++ b/ui/gfx/color_space.cc
@@ -38,6 +38,10 @@
 base::LazyInstance<base::Lock>::Leaky g_sk_color_space_cache_lock =
     LAZY_INSTANCE_INITIALIZER;
 
+static bool IsAlmostZero(float value) {
+  return std::abs(value) < std::numeric_limits<float>::epsilon();
+}
+
 }  // namespace
 
 ColorSpace::ColorSpace() {}
@@ -306,7 +310,8 @@
 std::string ColorSpace::ToString() const {
   std::stringstream ss;
   ss << std::fixed << std::setprecision(4);
-  ss << "{primaries:";
+  if (primaries_ != PrimaryID::CUSTOM)
+    ss << "{primaries:";
   switch (primaries_) {
     PRINT_ENUM_CASE(PrimaryID, INVALID)
     PRINT_ENUM_CASE(PrimaryID, BT709)
@@ -324,14 +329,22 @@
     PRINT_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB)
     PRINT_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN)
     case PrimaryID::CUSTOM:
-      ss << "[";
-      for (size_t i = 0; i < 3; ++i) {
-        ss << "[";
-        for (size_t j = 0; j < 3; ++j)
-          ss << custom_primary_matrix_[3 * i + j] << ",";
-        ss << "],";
-      }
-      ss << "]";
+      // |custom_primary_matrix_| is in column-major order.
+      const float sum_X = custom_primary_matrix_[0] +
+                          custom_primary_matrix_[3] + custom_primary_matrix_[6];
+      const float sum_Y = custom_primary_matrix_[1] +
+                          custom_primary_matrix_[4] + custom_primary_matrix_[7];
+      const float sum_Z = custom_primary_matrix_[2] +
+                          custom_primary_matrix_[5] + custom_primary_matrix_[8];
+      if (IsAlmostZero(sum_X) || IsAlmostZero(sum_Y) || IsAlmostZero(sum_Z))
+        break;
+
+      ss << "{primaries_d50_referred: [[" << (custom_primary_matrix_[0] / sum_X)
+         << ", " << (custom_primary_matrix_[3] / sum_X) << "], "
+         << " [" << (custom_primary_matrix_[1] / sum_Y) << ", "
+         << (custom_primary_matrix_[4] / sum_Y) << "], "
+         << " [" << (custom_primary_matrix_[2] / sum_Z) << ", "
+         << (custom_primary_matrix_[5] / sum_Z) << "]]";
       break;
   }
   ss << ", transfer:";
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index b24db3f..b0796b56 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -211,7 +211,7 @@
   MusClient::Get()->AddObserver(this);
   MusClient::Get()->window_tree_client()->focus_synchronizer()->AddObserver(
       this);
-  desktop_native_widget_aura_->content_window()->AddObserver(this);
+  content_window()->AddObserver(this);
   // DesktopNativeWidgetAura registers the association between |content_window_|
   // and Widget, but code may also want to go from the root (window()) to the
   // Widget. This call enables that.
@@ -226,7 +226,7 @@
   // the cursor-client needs to be unset on the root-window before
   // |cursor_manager_| is destroyed.
   aura::client::SetCursorClient(window(), nullptr);
-  desktop_native_widget_aura_->content_window()->RemoveObserver(this);
+  content_window()->RemoveObserver(this);
   MusClient::Get()->RemoveObserver(this);
   MusClient::Get()->window_tree_client()->focus_synchronizer()->RemoveObserver(
       this);
@@ -293,8 +293,7 @@
   return type == WIP::TYPE_WINDOW || type == WIP::TYPE_PANEL;
 }
 
-void DesktopWindowTreeHostMus::Init(aura::Window* content_window,
-                                    const Widget::InitParams& params) {
+void DesktopWindowTreeHostMus::Init(const Widget::InitParams& params) {
   // |TYPE_WINDOW| and |TYPE_PANEL| are forced to transparent as otherwise the
   // window is opaque and the client decorations drawn by the window manager
   // would not be seen.
@@ -302,7 +301,7 @@
       params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW ||
       params.type == Widget::InitParams::TYPE_WINDOW ||
       params.type == Widget::InitParams::TYPE_PANEL;
-  content_window->SetTransparent(transparent);
+  content_window()->SetTransparent(transparent);
   window()->SetTransparent(transparent);
 
   window()->SetProperty(aura::client::kShowStateKey, params.show_state);
@@ -325,18 +324,16 @@
         params.parent->GetHost()->window(), window());
   }
 
-  if (!params.accept_events) {
+  if (!params.accept_events)
     window()->SetEventTargetingPolicy(ui::mojom::EventTargetingPolicy::NONE);
-  } else {
-    aura::WindowPortMus::Get(content_window)->SetCanAcceptDrops(true);
-  }
+  else
+    aura::WindowPortMus::Get(content_window())->SetCanAcceptDrops(true);
 }
 
 void DesktopWindowTreeHostMus::OnNativeWidgetCreated(
     const Widget::InitParams& params) {
   window()->SetName(params.name);
-  desktop_native_widget_aura_->content_window()->SetName(
-      "DesktopNativeWidgetAura - content window");
+  content_window()->SetName("DesktopNativeWidgetAura - content window");
   if (params.parent && params.parent->GetHost()) {
     parent_ = static_cast<DesktopWindowTreeHostMus*>(params.parent->GetHost());
     parent_->children_.insert(this);
@@ -495,10 +492,9 @@
   gfx::Rect bounds_to_center_in = GetWorkAreaBoundsInScreen();
 
   // If there is a transient parent and it fits |size|, then center over it.
-  aura::Window* content_window = desktop_native_widget_aura_->content_window();
-  if (wm::GetTransientParent(content_window)) {
+  if (wm::GetTransientParent(content_window())) {
     gfx::Rect transient_parent_bounds =
-        wm::GetTransientParent(content_window)->GetBoundsInScreen();
+        wm::GetTransientParent(content_window())->GetBoundsInScreen();
     if (transient_parent_bounds.height() >= size.height() &&
         transient_parent_bounds.width() >= size.width()) {
       bounds_to_center_in = transient_parent_bounds;
@@ -792,7 +788,7 @@
 void DesktopWindowTreeHostMus::OnWindowPropertyChanged(aura::Window* window,
                                                        const void* key,
                                                        intptr_t old) {
-  DCHECK_EQ(window, desktop_native_widget_aura_->content_window());
+  DCHECK_EQ(window, content_window());
   DCHECK(!window->GetRootWindow() || this->window() == window->GetRootWindow());
   if (!this->window())
     return;
@@ -851,4 +847,8 @@
   }
 }
 
+aura::Window* DesktopWindowTreeHostMus::content_window() {
+  return desktop_native_widget_aura_->content_window();
+}
+
 }  // namespace views
diff --git a/ui/views/mus/desktop_window_tree_host_mus.h b/ui/views/mus/desktop_window_tree_host_mus.h
index 2884d650..e1dca1c6 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.h
+++ b/ui/views/mus/desktop_window_tree_host_mus.h
@@ -61,8 +61,7 @@
   bool ShouldSendClientAreaToServer() const;
 
   // DesktopWindowTreeHost:
-  void Init(aura::Window* content_window,
-            const Widget::InitParams& params) override;
+  void Init(const Widget::InitParams& params) override;
   void OnNativeWidgetCreated(const Widget::InitParams& params) override;
   void OnActiveWindowChanged(bool active) override;
   void OnWidgetInitDone() override;
@@ -143,6 +142,9 @@
   void HideImpl() override;
   void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override;
 
+  // Accessor for DesktopNativeWidgetAura::content_window().
+  aura::Window* content_window();
+
   internal::NativeWidgetDelegate* native_widget_delegate_;
 
   DesktopNativeWidgetAura* desktop_native_widget_aura_;
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 73514706..3f553a2 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -433,7 +433,7 @@
     }
     host_.reset(desktop_window_tree_host_->AsWindowTreeHost());
   }
-  desktop_window_tree_host_->Init(content_window_, params);
+  desktop_window_tree_host_->Init(params);
 
   host_->window()->AddChild(content_window_);
   host_->window()->SetProperty(kDesktopNativeWidgetAuraKey, this);
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
index 834ab5d2..437a0294 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
@@ -48,8 +48,7 @@
 
   // Sets up resources needed before the WindowEventDispatcher has been created.
   // It is expected this calls InitHost() on the WindowTreeHost.
-  virtual void Init(aura::Window* content_window,
-                    const Widget::InitParams& params) = 0;
+  virtual void Init(const Widget::InitParams& params) = 0;
 
   // Invoked once the DesktopNativeWidgetAura has been created.
   virtual void OnNativeWidgetCreated(const Widget::InitParams& params) = 0;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 6ef1ae76..31eacf5 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -81,7 +81,6 @@
     : message_handler_(new HWNDMessageHandler(this)),
       native_widget_delegate_(native_widget_delegate),
       desktop_native_widget_aura_(desktop_native_widget_aura),
-      content_window_(NULL),
       drag_drop_client_(NULL),
       should_animate_window_close_(false),
       pending_close_(false),
@@ -90,7 +89,6 @@
 }
 
 DesktopWindowTreeHostWin::~DesktopWindowTreeHostWin() {
-  // WARNING: |content_window_| has been destroyed by the time we get here.
   desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this);
   DestroyDispatcher();
 }
@@ -108,16 +106,13 @@
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation:
 
-void DesktopWindowTreeHostWin::Init(aura::Window* content_window,
-                                    const Widget::InitParams& params) {
-  // TODO(beng): SetInitParams().
-  content_window_ = content_window;
+void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
   wants_mouse_events_when_inactive_ = params.wants_mouse_events_when_inactive;
 
-  wm::SetAnimationHost(content_window_, this);
+  wm::SetAnimationHost(content_window(), this);
   if (params.type == Widget::InitParams::TYPE_WINDOW &&
       !params.remove_standard_frame)
-    content_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
+    content_window()->SetProperty(aura::client::kAnimationsDisabledKey, true);
 
   ConfigureWindowStyles(message_handler_.get(), params,
                         GetWidget()->widget_delegate(),
@@ -148,12 +143,12 @@
   if (cursor_client)
     is_cursor_visible_ = cursor_client->IsCursorVisible();
 
-  window()->SetProperty(kContentWindowForRootWindow, content_window_);
+  window()->SetProperty(kContentWindowForRootWindow, content_window());
   window()->SetProperty(kDesktopWindowTreeHostKey, this);
 
   should_animate_window_close_ =
-      content_window_->type() != aura::client::WINDOW_TYPE_NORMAL &&
-      !wm::WindowAnimationsDisabled(content_window_);
+      content_window()->type() != aura::client::WINDOW_TYPE_NORMAL &&
+      !wm::WindowAnimationsDisabled(content_window());
 
   // TODO this is not invoked *after* Init(), but should be ok.
   SetWindowTransparency();
@@ -181,7 +176,7 @@
   if (should_animate_window_close_) {
     pending_close_ = true;
     const bool is_animating =
-        content_window_->layer()->GetAnimator()->IsAnimatingProperty(
+        content_window()->layer()->GetAnimator()->IsAnimatingProperty(
             ui::LayerAnimationElement::VISIBILITY);
     // Animation may not start for a number of reasons.
     if (!is_animating)
@@ -401,7 +396,7 @@
 void DesktopWindowTreeHostWin::SetVisibilityChangedAnimationsEnabled(
     bool value) {
   message_handler_->SetVisibilityChangedAnimationsEnabled(value);
-  content_window_->SetProperty(aura::client::kAnimationsDisabledKey, !value);
+  content_window()->SetProperty(aura::client::kAnimationsDisabledKey, !value);
 }
 
 NonClientFrameView* DesktopWindowTreeHostWin::CreateNonClientFrameView() {
@@ -434,10 +429,10 @@
   // TODO(sky): workaround for ScopedFullscreenVisibility showing window
   // directly. Instead of this should listen for visibility changes and then
   // update window.
-  if (message_handler_->IsVisible() && !content_window_->TargetVisibility()) {
+  if (message_handler_->IsVisible() && !content_window()->TargetVisibility()) {
     if (compositor())
       compositor()->SetVisible(true);
-    content_window_->Show();
+    content_window()->Show();
   }
   SetWindowTransparency();
 }
@@ -447,7 +442,7 @@
 }
 
 void DesktopWindowTreeHostWin::SetOpacity(float opacity) {
-  content_window_->layer()->SetOpacity(opacity);
+  content_window()->layer()->SetOpacity(opacity);
 }
 
 void DesktopWindowTreeHostWin::SetWindowIcons(
@@ -1014,7 +1009,7 @@
   compositor()->SetBackgroundColor(transparent ? SK_ColorTRANSPARENT
                                                : SK_ColorWHITE);
   window()->SetTransparent(transparent);
-  content_window_->SetTransparent(transparent);
+  content_window()->SetTransparent(transparent);
 }
 
 bool DesktopWindowTreeHostWin::IsModalWindowActive() const {
@@ -1043,6 +1038,10 @@
   OnHostDisplayChanged();
 }
 
+aura::Window* DesktopWindowTreeHostWin::content_window() {
+  return desktop_native_widget_aura_->content_window();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHost, public:
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index ec5cfdd..a5f7bd34d 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -56,8 +56,7 @@
 
  protected:
   // Overridden from DesktopWindowTreeHost:
-  void Init(aura::Window* content_window,
-            const Widget::InitParams& params) override;
+  void Init(const Widget::InitParams& params) override;
   void OnNativeWidgetCreated(const Widget::InitParams& params) override;
   void OnActiveWindowChanged(bool active) override;
   void OnWidgetInitDone() override;
@@ -233,6 +232,9 @@
   // has changed, and, if so, inform the aura::WindowTreeHost.
   void CheckForMonitorChange();
 
+  // Accessor for DesktopNativeWidgetAura::content_window().
+  aura::Window* content_window();
+
   HMONITOR last_monitor_from_window_ = nullptr;
 
   std::unique_ptr<HWNDMessageHandler> message_handler_;
@@ -244,8 +246,6 @@
 
   DesktopNativeWidgetAura* desktop_native_widget_aura_;
 
-  aura::Window* content_window_;
-
   // Owned by DesktopNativeWidgetAura.
   DesktopDragDropClientWin* drag_drop_client_;
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 8ce777a..e2bf9239 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -148,7 +148,6 @@
       drag_drop_client_(NULL),
       native_widget_delegate_(native_widget_delegate),
       desktop_native_widget_aura_(desktop_native_widget_aura),
-      content_window_(NULL),
       window_parent_(NULL),
       custom_window_shape_(false),
       urgency_hint_set_(false),
@@ -388,13 +387,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHostX11, DesktopWindowTreeHost implementation:
 
-void DesktopWindowTreeHostX11::Init(aura::Window* content_window,
-                                    const Widget::InitParams& params) {
-  content_window_ = content_window;
+void DesktopWindowTreeHostX11::Init(const Widget::InitParams& params) {
   activatable_ = (params.activatable == Widget::InitParams::ACTIVATABLE_YES);
 
   if (params.type == Widget::InitParams::TYPE_WINDOW)
-    content_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
+    content_window()->SetProperty(aura::client::kAnimationsDisabledKey, true);
 
   // TODO(erg): Check whether we *should* be building a WindowTreeHost here, or
   // whether we should be proxying requests to another DRWHL.
@@ -414,7 +411,7 @@
 
 void DesktopWindowTreeHostX11::OnNativeWidgetCreated(
     const Widget::InitParams& params) {
-  window()->SetProperty(kViewsWindowForRootWindow, content_window_);
+  window()->SetProperty(kViewsWindowForRootWindow, content_window());
   window()->SetProperty(kHostForRootWindow, this);
 
   // Ensure that the X11DesktopHandler exists so that it tracks create/destroy
@@ -605,9 +602,9 @@
 
   // If |window_|'s transient parent bounds are big enough to contain |size|,
   // use them instead.
-  if (wm::GetTransientParent(content_window_)) {
+  if (wm::GetTransientParent(content_window())) {
     gfx::Rect transient_parent_rect =
-        wm::GetTransientParent(content_window_)->GetBoundsInScreen();
+        wm::GetTransientParent(content_window())->GetBoundsInScreen();
     if (transient_parent_rect.height() >= size.height() &&
         transient_parent_rect.width() >= size.width()) {
       parent_bounds_in_pixels = ToPixelRect(transient_parent_rect);
@@ -939,12 +936,13 @@
 
 void DesktopWindowTreeHostX11::ClearNativeFocus() {
   // This method is weird and misnamed. Instead of clearing the native focus,
-  // it sets the focus to our |content_window_|, which will trigger a cascade
+  // it sets the focus to our content_window(), which will trigger a cascade
   // of focus changes into views.
-  if (content_window_ && aura::client::GetFocusClient(content_window_) &&
-      content_window_->Contains(
-          aura::client::GetFocusClient(content_window_)->GetFocusedWindow())) {
-    aura::client::GetFocusClient(content_window_)->FocusWindow(content_window_);
+  if (content_window() && aura::client::GetFocusClient(content_window()) &&
+      content_window()->Contains(
+          aura::client::GetFocusClient(content_window())->GetFocusedWindow())) {
+    aura::client::GetFocusClient(content_window())
+        ->FocusWindow(content_window());
   }
 }
 
@@ -955,7 +953,7 @@
   wm::WindowMoveSource window_move_source =
       source == Widget::MOVE_LOOP_SOURCE_MOUSE ? wm::WINDOW_MOVE_SOURCE_MOUSE
                                                : wm::WINDOW_MOVE_SOURCE_TOUCH;
-  if (x11_window_move_client_->RunMoveLoop(content_window_, drag_offset,
+  if (x11_window_move_client_->RunMoveLoop(content_window(), drag_offset,
                                            window_move_source) ==
       wm::MOVE_SUCCESSFUL)
     return Widget::MOVE_LOOP_SUCCESSFUL;
@@ -1635,9 +1633,9 @@
   if (is_minimized != was_minimized) {
     if (is_minimized) {
       SetVisible(false);
-      content_window_->Hide();
+      content_window()->Hide();
     } else {
-      content_window_->Show();
+      content_window()->Show();
       SetVisible(true);
     }
   }
@@ -1765,10 +1763,10 @@
   // events on the ash desktop are clicking in what Windows considers to be a
   // non client area.) Likewise, we won't want to do the following in any
   // WindowTreeHost that hosts ash.
-  if (content_window_ && content_window_->delegate()) {
+  if (content_window() && content_window()->delegate()) {
     int flags = event->flags();
     int hit_test_code =
-        content_window_->delegate()->GetNonClientComponent(event->location());
+        content_window()->delegate()->GetNonClientComponent(event->location());
     if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE)
       flags |= ui::EF_IS_NON_CLIENT;
     event->set_flags(flags);
@@ -1933,7 +1931,7 @@
   compositor()->SetBackgroundColor(use_argb_visual_ ? SK_ColorTRANSPARENT
                                                     : SK_ColorWHITE);
   window()->SetTransparent(use_argb_visual_);
-  content_window_->SetTransparent(use_argb_visual_);
+  content_window()->SetTransparent(use_argb_visual_);
 }
 
 void DesktopWindowTreeHostX11::Relayout() {
@@ -2344,6 +2342,10 @@
       FROM_HERE, delayed_resize_task_.callback());
 }
 
+aura::Window* DesktopWindowTreeHostX11::content_window() {
+  return desktop_native_widget_aura_->content_window();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHost, public:
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
index 043965a..8dbf794 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
@@ -51,7 +51,8 @@
       DesktopNativeWidgetAura* desktop_native_widget_aura);
   ~DesktopWindowTreeHostX11() override;
 
-  // A way of converting an X11 |xid| host window into a |content_window_|.
+  // A way of converting an X11 |xid| host window into the content_window()
+  // of the associated DesktopNativeWidgetAura.
   static aura::Window* GetContentWindowForXID(XID xid);
 
   // A way of converting an X11 |xid| host window into this object.
@@ -88,8 +89,7 @@
 
  protected:
   // Overridden from DesktopWindowTreeHost:
-  void Init(aura::Window* content_window,
-            const Widget::InitParams& params) override;
+  void Init(const Widget::InitParams& params) override;
   void OnNativeWidgetCreated(const Widget::InitParams& params) override;
   void OnWidgetInitDone() override;
   void OnActiveWindowChanged(bool active) override;
@@ -188,7 +188,7 @@
   // initialization related to talking to the X11 server.
   void InitX11Window(const Widget::InitParams& params);
 
-  // Creates an aura::WindowEventDispatcher to contain the |content_window|,
+  // Creates an aura::WindowEventDispatcher to contain the content_window()
   // along with all aura client objects that direct behavior.
   aura::WindowEventDispatcher* InitDispatcher(const Widget::InitParams& params);
 
@@ -285,6 +285,9 @@
   // Set visibility and fire OnNativeWidgetVisibilityChanged() if it changed.
   void SetVisible(bool visible);
 
+  // Accessor for DesktopNativeWidgetAura::content_window().
+  aura::Window* content_window();
+
   // X11 things
   // The display and the native X window hosting the root window.
   XDisplay* xdisplay_;
@@ -355,8 +358,6 @@
 
   DesktopNativeWidgetAura* desktop_native_widget_aura_;
 
-  aura::Window* content_window_;
-
   // We can optionally have a parent which can order us to close, or own
   // children who we're responsible for closing when we CloseNow().
   DesktopWindowTreeHostX11* window_parent_;
diff --git a/webrunner/BUILD.gn b/webrunner/BUILD.gn
new file mode 100644
index 0000000..ee4c5b2a
--- /dev/null
+++ b/webrunner/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_fuchsia)
+
+import("//build/config/fuchsia/rules.gni")
+
+executable("webrunner") {
+  deps = [
+    "//build/config:exe_and_shlib_deps",
+  ]
+
+  public_deps = [
+    "//content/public/app:both",
+    "//content/public/browser",
+    "//content/public/child",
+    "//content/public/common",
+    "//content/public/renderer",
+  ]
+
+  sources = [
+    "app/webrunner_main.cc",
+    "app/webrunner_main_delegate.cc",
+    "app/webrunner_main_delegate.h",
+    "browser/webrunner_browser_main.cc",
+    "browser/webrunner_browser_main.h",
+  ]
+}
+
+fuchsia_executable_runner("webrunner_fuchsia") {
+  exe_target = ":webrunner"
+}
diff --git a/webrunner/DEPS b/webrunner/DEPS
new file mode 100644
index 0000000..7891ede
--- /dev/null
+++ b/webrunner/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/common",
+]
\ No newline at end of file
diff --git a/webrunner/OWNERS b/webrunner/OWNERS
new file mode 100644
index 0000000..e7034ea
--- /dev/null
+++ b/webrunner/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/webrunner/README.md b/webrunner/README.md
new file mode 100644
index 0000000..01e6e680
--- /dev/null
+++ b/webrunner/README.md
@@ -0,0 +1,2 @@
+This directory contains WebRunner implementation. WebRunner allows to run
+web applications on Fuchsia.
\ No newline at end of file
diff --git a/webrunner/app/DEPS b/webrunner/app/DEPS
new file mode 100644
index 0000000..e7cb291
--- /dev/null
+++ b/webrunner/app/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+content/public/app",
+  "+ui/base",
+]
\ No newline at end of file
diff --git a/webrunner/app/webrunner_main.cc b/webrunner/app/webrunner_main.cc
new file mode 100644
index 0000000..eed0edf8
--- /dev/null
+++ b/webrunner/app/webrunner_main.cc
@@ -0,0 +1,14 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/app/content_main.h"
+#include "webrunner/app/webrunner_main_delegate.h"
+
+int main(int argc, const char** argv) {
+  webrunner::WebRunnerMainDelegate delegate;
+  content::ContentMainParams params(&delegate);
+  params.argc = argc;
+  params.argv = argv;
+  return content::ContentMain(params);
+}
diff --git a/webrunner/app/webrunner_main_delegate.cc b/webrunner/app/webrunner_main_delegate.cc
new file mode 100644
index 0000000..97b5b18
--- /dev/null
+++ b/webrunner/app/webrunner_main_delegate.cc
@@ -0,0 +1,69 @@
+// 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/app/webrunner_main_delegate.h"
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "webrunner/browser/webrunner_browser_main.h"
+
+namespace webrunner {
+
+namespace {
+
+void InitLoggingFromCommandLine(const base::CommandLine& command_line) {
+  base::FilePath log_filename;
+  std::string filename = command_line.GetSwitchValueASCII(switches::kLogFile);
+  if (filename.empty()) {
+    PathService::Get(base::DIR_EXE, &log_filename);
+    log_filename = log_filename.AppendASCII("webrunner.log");
+  } else {
+    log_filename = base::FilePath::FromUTF8Unsafe(filename);
+  }
+
+  logging::LoggingSettings settings;
+  settings.logging_dest = logging::LOG_TO_ALL;
+  settings.log_file = log_filename.value().c_str();
+  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+  logging::InitLogging(settings);
+  logging::SetLogItems(true /* Process ID */, true /* Thread ID */,
+                       true /* Timestamp */, false /* Tick count */);
+}
+
+void InitializeResourceBundle() {
+  base::FilePath pak_file;
+  bool result = base::PathService::Get(base::DIR_ASSETS, &pak_file);
+  DCHECK(result);
+  pak_file = pak_file.Append(FILE_PATH_LITERAL("webrunner.pak"));
+  ui::ResourceBundle::InitSharedInstanceWithPakPath(pak_file);
+}
+
+}  // namespace
+
+WebRunnerMainDelegate::WebRunnerMainDelegate() = default;
+WebRunnerMainDelegate::~WebRunnerMainDelegate() = default;
+
+bool WebRunnerMainDelegate::BasicStartupComplete(int* exit_code) {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  InitLoggingFromCommandLine(*command_line);
+  return false;
+}
+
+void WebRunnerMainDelegate::PreSandboxStartup() {
+  InitializeResourceBundle();
+}
+
+int WebRunnerMainDelegate::RunProcess(
+    const std::string& process_type,
+    const content::MainFunctionParams& main_function_params) {
+  if (!process_type.empty())
+    return -1;
+
+  return WebRunnerBrowserMain(main_function_params);
+}
+
+}  // namespace webrunner
diff --git a/webrunner/app/webrunner_main_delegate.h b/webrunner/app/webrunner_main_delegate.h
new file mode 100644
index 0000000..4b88a08
--- /dev/null
+++ b/webrunner/app/webrunner_main_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBRUNNER_APP_WEBRUNNER_MAIN_DELEGATE_H_
+#define WEBRUNNER_APP_WEBRUNNER_MAIN_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "content/public/app/content_main_delegate.h"
+
+namespace webrunner {
+
+class WebRunnerMainDelegate : public content::ContentMainDelegate {
+ public:
+  WebRunnerMainDelegate();
+  ~WebRunnerMainDelegate() override;
+
+  // ContentMainDelegate implementation.
+  bool BasicStartupComplete(int* exit_code) override;
+  void PreSandboxStartup() override;
+  int RunProcess(
+      const std::string& process_type,
+      const content::MainFunctionParams& main_function_params) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WebRunnerMainDelegate);
+};
+
+}  // namespace webrunner
+
+#endif  // WEBRUNNER_APP_WEBRUNNER_MAIN_DELEGATE_H_
diff --git a/webrunner/browser/DEPS b/webrunner/browser/DEPS
new file mode 100644
index 0000000..e6a7598
--- /dev/null
+++ b/webrunner/browser/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/browser",
+]
\ No newline at end of file
diff --git a/webrunner/browser/webrunner_browser_main.cc b/webrunner/browser/webrunner_browser_main.cc
new file mode 100644
index 0000000..fe0ec37a
--- /dev/null
+++ b/webrunner/browser/webrunner_browser_main.cc
@@ -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.
+
+#include "webrunner/browser/webrunner_browser_main.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_main_runner.h"
+
+namespace webrunner {
+
+int WebRunnerBrowserMain(const content::MainFunctionParams& parameters) {
+  std::unique_ptr<content::BrowserMainRunner> main_runner(
+      content::BrowserMainRunner::Create());
+  int exit_code = main_runner->Initialize(parameters);
+  if (exit_code >= 0)
+    return exit_code;
+
+  exit_code = main_runner->Run();
+
+  main_runner->Shutdown();
+
+  return exit_code;
+}
+
+}  // namespace webrunner
\ No newline at end of file
diff --git a/webrunner/browser/webrunner_browser_main.h b/webrunner/browser/webrunner_browser_main.h
new file mode 100644
index 0000000..37428a1
--- /dev/null
+++ b/webrunner/browser/webrunner_browser_main.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 WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+#define WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+
+#include <memory>
+
+namespace content {
+struct MainFunctionParams;
+}  // namespace content
+
+namespace webrunner {
+int WebRunnerBrowserMain(const content::MainFunctionParams& parameters);
+}  // namespace webrunner
+
+#endif  // WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_