diff --git a/.eslintrc.js b/.eslintrc.js
index 9b4b1037..79c117d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -11,6 +11,7 @@
   'rules': {
     // Enabled checks.
     'no-extra-semi': 'error',
+    'semi': ['error', 'always'],
     // TODO(dpapad): Add more checks according to our styleguide.
   },
 };
diff --git a/DEPS b/DEPS
index 92cfa10..adaabf6 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '2a69daf1572f8920d156ba3f9cf60e72955d6bf2',
+  'v8_revision': '20ee3efa8a86d2e9ceb835861d2251939443c7c7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '9a505796f3fe8c1d0cbc3f7e4e7db41fc1fde137',
+  'pdfium_revision': '26cb2fa42b1a90146f9ab5c0b83ee8b48703baf4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -96,7 +96,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': 'e18c0f9c7950833973f15a784ff39cab7a5673c5',
+  'catapult_revision': '32a3f0b8f49bb6a3e5c7f158ff7aea5584843294',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -889,6 +889,20 @@
                 'src/third_party/catapult/telemetry/bin/fetch_telemetry_binary_dependencies',
     ],
   },
+
+  # Download checkstyle for use in PRESUBMIT for Java changes.
+  # TODO(jbudorick): Move this back down to the android section of hooks_os
+  # once it's no longer necessary for the chromium_presubmit bot.
+  {
+    'name': 'checkstyle',
+    'pattern': '.',
+    'action': [ 'download_from_google_storage',
+                '--no_resume',
+                '--no_auth',
+                '--bucket', 'chromium-android-tools/checkstyle',
+                '-s', 'src/third_party/checkstyle/checkstyle-7.6.1-all.jar.sha1'
+    ],
+  },
 ]
 
 # Note: These are keyed off target os, not host os. So don't move things here
@@ -1088,16 +1102,6 @@
       ],
     },
     {
-      'name': 'checkstyle',
-      'pattern': '.',
-      'action': [ 'download_from_google_storage',
-                  '--no_resume',
-                  '--no_auth',
-                  '--bucket', 'chromium-android-tools/checkstyle',
-                  '-s', 'src/third_party/checkstyle/checkstyle-7.6.1-all.jar.sha1'
-      ],
-    },
-    {
       'name': 'gvr_static_shim_android_arm',
       'pattern': '\\.sha1',
       'action': [ 'download_from_google_storage',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 215ffbc..7f09ce51 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -212,6 +212,8 @@
         r"^content[\\\/]shell[\\\/]browser[\\\/]shell_browser_main\.cc$",
         r"^content[\\\/]shell[\\\/]browser[\\\/]shell_message_filter\.cc$",
         r"^content[\\\/]test[\\\/]ppapi[\\\/]ppapi_test\.cc$",
+        r"^media[\\\/]cast[\\\/]test[\\\/]utility[\\\/]" +
+            r"standalone_cast_environment\.cc$",
         r"^mojo[\\\/]edk[\\\/]embedder[\\\/]" +
             r"simple_platform_shared_buffer_posix\.cc$",
         r"^net[\\\/]disk_cache[\\\/]cache_util\.cc$",
diff --git a/ash/frame/caption_buttons/frame_caption_button.cc b/ash/frame/caption_buttons/frame_caption_button.cc
index ff7e5fe4..e3bd36e5 100644
--- a/ash/frame/caption_buttons/frame_caption_button.cc
+++ b/ash/frame/caption_buttons/frame_caption_button.cc
@@ -101,7 +101,30 @@
   return kViewClassName;
 }
 
-void FrameCaptionButton::OnPaint(gfx::Canvas* canvas) {
+void FrameCaptionButton::OnGestureEvent(ui::GestureEvent* event) {
+  // CustomButton does not become pressed when the user drags off and then back
+  // onto the button. Make FrameCaptionButton pressed in this case because this
+  // behavior is more consistent with AlternateFrameSizeButton.
+  if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
+      event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
+    if (HitTestPoint(event->location())) {
+      SetState(STATE_PRESSED);
+      RequestFocus();
+      event->StopPropagation();
+    } else {
+      SetState(STATE_NORMAL);
+    }
+  } else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
+    if (HitTestPoint(event->location())) {
+      SetState(STATE_HOVERED);
+      NotifyClick(*event);
+      event->StopPropagation();
+    }
+  }
+  CustomButton::OnGestureEvent(event);
+}
+
+void FrameCaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
   SkAlpha bg_alpha = SK_AlphaTRANSPARENT;
   if (hover_animation().is_animating())
     bg_alpha = hover_animation().CurrentValueBetween(0, kHoveredAlpha);
@@ -145,29 +168,6 @@
   }
 }
 
-void FrameCaptionButton::OnGestureEvent(ui::GestureEvent* event) {
-  // CustomButton does not become pressed when the user drags off and then back
-  // onto the button. Make FrameCaptionButton pressed in this case because this
-  // behavior is more consistent with AlternateFrameSizeButton.
-  if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
-      event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
-    if (HitTestPoint(event->location())) {
-      SetState(STATE_PRESSED);
-      RequestFocus();
-      event->StopPropagation();
-    } else {
-      SetState(STATE_NORMAL);
-    }
-  } else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
-    if (HitTestPoint(event->location())) {
-      SetState(STATE_HOVERED);
-      NotifyClick(*event);
-      event->StopPropagation();
-    }
-  }
-  CustomButton::OnGestureEvent(event);
-}
-
 int FrameCaptionButton::GetAlphaForIcon(int base_alpha) const {
   if (paint_as_active_)
     return base_alpha;
diff --git a/ash/frame/caption_buttons/frame_caption_button.h b/ash/frame/caption_buttons/frame_caption_button.h
index 9b80522e..2d05f6ed 100644
--- a/ash/frame/caption_buttons/frame_caption_button.h
+++ b/ash/frame/caption_buttons/frame_caption_button.h
@@ -48,7 +48,7 @@
 
   // views::View overrides:
   const char* GetClassName() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void OnGestureEvent(ui::GestureEvent* event) override;
 
   void set_paint_as_active(bool paint_as_active) {
     paint_as_active_ = paint_as_active;
@@ -60,7 +60,7 @@
 
  protected:
   // views::CustomButton override:
-  void OnGestureEvent(ui::GestureEvent* event) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:
   // Determines what alpha to use for the icon based on animation and
diff --git a/ash/shelf/app_list_button.cc b/ash/shelf/app_list_button.cc
index 4c4a057..584d9d8 100644
--- a/ash/shelf/app_list_button.cc
+++ b/ash/shelf/app_list_button.cc
@@ -144,43 +144,6 @@
   return true;
 }
 
-void AppListButton::OnPaint(gfx::Canvas* canvas) {
-  // Call the base class first to paint any background/borders.
-  View::OnPaint(canvas);
-
-  gfx::PointF circle_center(GetCenterPoint());
-
-  // Paint the circular background.
-  cc::PaintFlags bg_flags;
-  bg_flags.setColor(background_color_);
-  bg_flags.setAntiAlias(true);
-  bg_flags.setStyle(cc::PaintFlags::kFill_Style);
-  canvas->DrawCircle(circle_center, kAppListButtonRadius, bg_flags);
-
-  // Paint a white ring as the foreground. The ceil/dsf math assures that the
-  // ring draws sharply and is centered at all scale factors.
-  const float kRingOuterRadiusDp = 7.f;
-  const float kRingThicknessDp = 1.5f;
-
-  {
-    gfx::ScopedCanvas scoped_canvas(canvas);
-    const float dsf = canvas->UndoDeviceScaleFactor();
-    circle_center.Scale(dsf);
-
-    cc::PaintFlags fg_flags;
-    fg_flags.setAntiAlias(true);
-    fg_flags.setStyle(cc::PaintFlags::kStroke_Style);
-    fg_flags.setColor(kShelfIconColor);
-    const float thickness = std::ceil(kRingThicknessDp * dsf);
-    const float radius = std::ceil(kRingOuterRadiusDp * dsf) - thickness / 2;
-    fg_flags.setStrokeWidth(thickness);
-    // Make sure the center of the circle lands on pixel centers.
-    canvas->DrawCircle(circle_center, radius, fg_flags);
-  }
-
-  views::Painter::PaintFocusPainter(this, canvas, focus_painter());
-}
-
 void AppListButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ui::AX_ROLE_BUTTON;
   node_data->SetName(shelf_view_->GetTitleForView(this));
@@ -224,6 +187,38 @@
                                                     kAppListButtonRadius);
 }
 
+void AppListButton::PaintButtonContents(gfx::Canvas* canvas) {
+  gfx::PointF circle_center(GetCenterPoint());
+
+  // Paint the circular background.
+  cc::PaintFlags bg_flags;
+  bg_flags.setColor(background_color_);
+  bg_flags.setAntiAlias(true);
+  bg_flags.setStyle(cc::PaintFlags::kFill_Style);
+  canvas->DrawCircle(circle_center, kAppListButtonRadius, bg_flags);
+
+  // Paint a white ring as the foreground. The ceil/dsf math assures that the
+  // ring draws sharply and is centered at all scale factors.
+  const float kRingOuterRadiusDp = 7.f;
+  const float kRingThicknessDp = 1.5f;
+
+  {
+    gfx::ScopedCanvas scoped_canvas(canvas);
+    const float dsf = canvas->UndoDeviceScaleFactor();
+    circle_center.Scale(dsf);
+
+    cc::PaintFlags fg_flags;
+    fg_flags.setAntiAlias(true);
+    fg_flags.setStyle(cc::PaintFlags::kStroke_Style);
+    fg_flags.setColor(kShelfIconColor);
+    const float thickness = std::ceil(kRingThicknessDp * dsf);
+    const float radius = std::ceil(kRingOuterRadiusDp * dsf) - thickness / 2;
+    fg_flags.setStrokeWidth(thickness);
+    // Make sure the center of the circle lands on pixel centers.
+    canvas->DrawCircle(circle_center, radius, fg_flags);
+  }
+}
+
 gfx::Point AppListButton::GetCenterPoint() const {
   // For a bottom-aligned shelf, the button bounds could have a larger height
   // than width (in the case of touch-dragging the shelf updwards) or a larger
diff --git a/ash/shelf/app_list_button.h b/ash/shelf/app_list_button.h
index 31faff2..02c69c2 100644
--- a/ash/shelf/app_list_button.h
+++ b/ash/shelf/app_list_button.h
@@ -42,13 +42,13 @@
   void OnMouseReleased(const ui::MouseEvent& event) override;
   void OnMouseCaptureLost() override;
   bool OnMouseDragged(const ui::MouseEvent& event) override;
-  void OnPaint(gfx::Canvas* canvas) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   void NotifyClick(const ui::Event& event) override;
   bool ShouldEnterPushedState(const ui::Event& event) override;
   std::unique_ptr<views::InkDrop> CreateInkDrop() override;
   std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:
   // Get the center point of the app list button used to draw its background and
diff --git a/ash/shelf/overflow_bubble_view.cc b/ash/shelf/overflow_bubble_view.cc
index 22af1f0..de12a49 100644
--- a/ash/shelf/overflow_bubble_view.cc
+++ b/ash/shelf/overflow_bubble_view.cc
@@ -11,7 +11,6 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shell.h"
-#include "ash/wm_window.h"
 #include "base/i18n/rtl.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
@@ -163,8 +162,7 @@
     views::Widget::InitParams* params,
     views::Widget* bubble_widget) const {
   // Place the bubble in the same root window as the anchor.
-  WmWindow::Get(anchor_widget()->GetNativeWindow())
-      ->GetRootWindowController()
+  RootWindowController::ForWindow(anchor_widget()->GetNativeWindow())
       ->ConfigureWidgetInitParamsForContainer(
           bubble_widget, kShellWindowId_ShelfBubbleContainer, params);
 }
diff --git a/ash/shelf/overflow_button.cc b/ash/shelf/overflow_button.cc
index 0b96ec9..5941924 100644
--- a/ash/shelf/overflow_button.cc
+++ b/ash/shelf/overflow_button.cc
@@ -109,12 +109,6 @@
   SchedulePaint();
 }
 
-void OverflowButton::OnPaint(gfx::Canvas* canvas) {
-  gfx::Rect bounds = CalculateButtonBounds();
-  PaintBackground(canvas, bounds);
-  PaintForeground(canvas, bounds);
-}
-
 std::unique_ptr<views::InkDrop> OverflowButton::CreateInkDrop() {
   std::unique_ptr<views::InkDropImpl> ink_drop =
       CreateDefaultFloodFillInkDropImpl();
@@ -149,6 +143,12 @@
       size(), insets, kOverflowButtonCornerRadius);
 }
 
+void OverflowButton::PaintButtonContents(gfx::Canvas* canvas) {
+  gfx::Rect bounds = CalculateButtonBounds();
+  PaintBackground(canvas, bounds);
+  PaintForeground(canvas, bounds);
+}
+
 void OverflowButton::PaintBackground(gfx::Canvas* canvas,
                                      const gfx::Rect& bounds) {
   cc::PaintFlags flags;
diff --git a/ash/shelf/overflow_button.h b/ash/shelf/overflow_button.h
index 88bb6d2..a189406 100644
--- a/ash/shelf/overflow_button.h
+++ b/ash/shelf/overflow_button.h
@@ -46,12 +46,12 @@
   void UpdateChevronImage();
 
   // views::CustomButton:
-  void OnPaint(gfx::Canvas* canvas) override;
   std::unique_ptr<views::InkDrop> CreateInkDrop() override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   bool ShouldEnterPushedState(const ui::Event& event) override;
   void NotifyClick(const ui::Event& event) override;
   std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // Helper functions to paint the background and foreground of the button
   // at |bounds|.
diff --git a/ash/shelf/shelf.cc b/ash/shelf/shelf.cc
index 3119297..dce85c9 100644
--- a/ash/shelf/shelf.cc
+++ b/ash/shelf/shelf.cc
@@ -16,7 +16,6 @@
 #include "ash/shelf/shelf_observer.h"
 #include "ash/shelf/shelf_widget.h"
 #include "ash/shell.h"
-#include "ash/wm_window.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "ui/display/types/display_constants.h"
diff --git a/ash/shelf/shelf_button.cc b/ash/shelf/shelf_button.cc
index 53cf800..0fc01bf 100644
--- a/ash/shelf/shelf_button.cc
+++ b/ash/shelf/shelf_button.cc
@@ -11,6 +11,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shelf/shelf_view.h"
+#include "ash/system/tray/tray_popup_utils.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
 #include "skia/ext/image_operations.h"
@@ -26,6 +27,7 @@
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/square_ink_drop_ripple.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/painter.h"
 
 namespace {
 
@@ -233,6 +235,8 @@
 
   AddChildView(indicator_);
   AddChildView(icon_view_);
+
+  SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
 }
 
 ShelfButton::~ShelfButton() {
@@ -432,14 +436,6 @@
   CustomButton::OnBlur();
 }
 
-void ShelfButton::OnPaint(gfx::Canvas* canvas) {
-  CustomButton::OnPaint(canvas);
-  if (HasFocus()) {
-    canvas->DrawSolidFocusRect(gfx::RectF(GetLocalBounds()), kFocusBorderColor,
-                               kFocusBorderThickness);
-  }
-}
-
 void ShelfButton::OnGestureEvent(ui::GestureEvent* event) {
   switch (event->type()) {
     case ui::ET_GESTURE_TAP_DOWN:
diff --git a/ash/shelf/shelf_button.h b/ash/shelf/shelf_button.h
index 540d20ac..7d254c8 100644
--- a/ash/shelf/shelf_button.h
+++ b/ash/shelf/shelf_button.h
@@ -79,7 +79,6 @@
   void ChildPreferredSizeChanged(views::View* child) override;
   void OnFocus() override;
   void OnBlur() override;
-  void OnPaint(gfx::Canvas* canvas) override;
 
   // ui::EventHandler overrides:
   void OnGestureEvent(ui::GestureEvent* event) override;
diff --git a/ash/shelf/shelf_controller.cc b/ash/shelf/shelf_controller.cc
index 2bc855b..fda0675 100644
--- a/ash/shelf/shelf_controller.cc
+++ b/ash/shelf/shelf_controller.cc
@@ -10,7 +10,6 @@
 #include "ash/shelf/app_list_shelf_item_delegate.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
-#include "ash/wm_window.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/display/display.h"
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index b8c65bc..62a52fa 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -27,7 +27,6 @@
 #include "ash/shell_port.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/wm/root_window_finder.h"
-#include "ash/wm_window.h"
 #include "base/auto_reset.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
@@ -1040,10 +1039,9 @@
   DCHECK_NE(-1, current_index);
   std::string dragged_app_id = model_->items()[current_index].id.app_id;
 
-  gfx::Point screen_location =
-      WmWindow::Get(GetWidget()->GetNativeWindow())
-          ->GetRootWindow()
-          ->ConvertPointToScreen(event.root_location());
+  gfx::Point screen_location = event.root_location();
+  ::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(),
+                             &screen_location);
 
   // To avoid ugly forwards and backwards flipping we use different constants
   // for ripping off / re-inserting the items.
diff --git a/ash/shelf/shelf_widget_unittest.cc b/ash/shelf/shelf_widget_unittest.cc
index e55d689..0b81b03 100644
--- a/ash/shelf/shelf_widget_unittest.cc
+++ b/ash/shelf/shelf_widget_unittest.cc
@@ -17,7 +17,6 @@
 #include "ash/test/shelf_view_test_api.h"
 #include "ash/test/test_shell_delegate.h"
 #include "ash/wm/window_util.h"
-#include "ash/wm_window.h"
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/display/display.h"
 #include "ui/events/event_utils.h"
diff --git a/ash/shelf/shelf_window_watcher.cc b/ash/shelf/shelf_window_watcher.cc
index c1112f4..f5efa167 100644
--- a/ash/shelf/shelf_window_watcher.cc
+++ b/ash/shelf/shelf_window_watcher.cc
@@ -17,7 +17,6 @@
 #include "ash/shell_port.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
-#include "ash/wm_window.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
diff --git a/ash/shelf/shelf_window_watcher_unittest.cc b/ash/shelf/shelf_window_watcher_unittest.cc
index 0b303c57..1da5df1 100644
--- a/ash/shelf/shelf_window_watcher_unittest.cc
+++ b/ash/shelf/shelf_window_watcher_unittest.cc
@@ -16,7 +16,6 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/window_resizer.h"
 #include "ash/wm/window_state.h"
-#include "ash/wm_window.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/aura/window.h"
 #include "ui/base/hit_test.h"
diff --git a/ash/system/bluetooth/tray_bluetooth.cc b/ash/system/bluetooth/tray_bluetooth.cc
index 58cd1aa..ffc8b2642 100644
--- a/ash/system/bluetooth/tray_bluetooth.cc
+++ b/ash/system/bluetooth/tray_bluetooth.cc
@@ -310,8 +310,8 @@
 
     // Show user Bluetooth state if there is no bluetooth devices in list.
     if (device_map_.size() == 0 && bluetooth_available && bluetooth_enabled) {
-      scroll_content()->AddChildView(TrayPopupUtils::CreateInfoLabelRowView(
-          IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
+      scroll_content()->AddChildView(
+          new InfoLabel(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
     }
 
     // Focus the device which was focused before the device-list update.
diff --git a/ash/system/network/network_list.cc b/ash/system/network/network_list.cc
index b462bbd..21ee550 100644
--- a/ash/system/network/network_list.cc
+++ b/ash/system/network/network_list.cc
@@ -669,30 +669,22 @@
 
 void NetworkListView::UpdateInfoLabel(int message_id,
                                       int insertion_index,
-                                      views::Label** label_ptr) {
-  views::Label* label = *label_ptr;
+                                      InfoLabel** info_label_ptr) {
+  InfoLabel* info_label = *info_label_ptr;
   if (!message_id) {
-    if (label) {
+    if (info_label) {
       needs_relayout_ = true;
-      delete label;
-      *label_ptr = nullptr;
+      delete info_label;
+      *info_label_ptr = nullptr;
     }
     return;
   }
-  base::string16 text = l10n_util::GetStringUTF16(message_id);
-  if (!label) {
-    // TODO(mohsen): Update info label to follow MD specs. See
-    // https://crbug.com/687778.
-    label = new views::Label();
-    label->SetBorder(views::CreateEmptyBorder(
-        kTrayPopupPaddingBetweenItems, kTrayPopupPaddingHorizontal,
-        kTrayPopupPaddingBetweenItems, 0));
-    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0));
-  }
-  label->SetText(text);
-  PlaceViewAtIndex(label, insertion_index);
-  *label_ptr = label;
+  if (!info_label)
+    info_label = new InfoLabel(message_id);
+  else
+    info_label->SetMessage(message_id);
+  PlaceViewAtIndex(info_label, insertion_index);
+  *info_label_ptr = info_label;
 }
 
 int NetworkListView::UpdateSectionHeaderRow(NetworkTypePattern pattern,
diff --git a/ash/system/network/network_list.h b/ash/system/network/network_list.h
index 98436f3..1de25cd 100644
--- a/ash/system/network/network_list.h
+++ b/ash/system/network/network_list.h
@@ -19,7 +19,6 @@
 #include "chromeos/network/network_type_pattern.h"
 
 namespace views {
-class Label;
 class Separator;
 class View;
 }
@@ -88,23 +87,24 @@
       int child_index);
   void UpdateNetworkChild(int index, const NetworkInfo* info);
 
-  // Reorders children of |container()| as necessary placing |view| at |index|.
+  // Reorders children of |scroll_content()| as necessary placing |view| at
+  // |index|.
   void PlaceViewAtIndex(views::View* view, int index);
 
-  // Creates a Label with text specified by |message_id| and adds it to
-  // |container()| if necessary or updates the text and reorders the
-  // |container()| placing the label at |insertion_index|. When |message_id| is
-  // zero removes the |*label_ptr| from the |container()| and destroys it.
-  // |label_ptr| is an in / out parameter and is only modified if the Label is
-  // created or destroyed.
+  // Creates an info label with text specified by |message_id| and adds it to
+  // |scroll_content()| if necessary or updates the text and reorders the
+  // |scroll_content()| placing the info label at |insertion_index|. When
+  // |message_id| is zero removes the |*info_label_ptr| from the
+  // |scroll_content()| and destroys it. |info_label_ptr| is an in/out parameter
+  // and is only modified if the info label is created or destroyed.
   void UpdateInfoLabel(int message_id,
                        int insertion_index,
-                       views::Label** label_ptr);
+                       InfoLabel** info_label_ptr);
 
-  // Creates a cellular/Wi-Fi header row |view| and adds it to |container()| if
-  // necessary and reorders the |container()| placing the |view| at
-  // |child_index|. Returns the index where the next child should be inserted,
-  // i.e., the index directly after the last inserted child.
+  // Creates a cellular/tether/Wi-Fi header row |view| and adds it to
+  // |scroll_content()| if necessary and reorders the |scroll_content()| placing
+  // the |view| at |child_index|. Returns the index where the next child should
+  // be inserted, i.e., the index directly after the last inserted child.
   int UpdateSectionHeaderRow(chromeos::NetworkTypePattern pattern,
                              bool enabled,
                              int child_index,
@@ -120,8 +120,8 @@
 
   bool needs_relayout_;
 
-  views::Label* no_wifi_networks_view_;
-  views::Label* no_cellular_networks_view_;
+  InfoLabel* no_wifi_networks_view_;
+  InfoLabel* no_cellular_networks_view_;
   SectionHeaderRowView* cellular_header_view_;
   SectionHeaderRowView* tether_header_view_;
   SectionHeaderRowView* wifi_header_view_;
diff --git a/ash/system/tray/actionable_view.cc b/ash/system/tray/actionable_view.cc
index ccb3d86..d169f9a8 100644
--- a/ash/system/tray/actionable_view.cc
+++ b/ash/system/tray/actionable_view.cc
@@ -17,6 +17,7 @@
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_mask.h"
+#include "ui/views/painter.h"
 
 namespace ash {
 
@@ -34,6 +35,7 @@
   set_ink_drop_visible_opacity(kTrayPopupInkDropRippleOpacity);
   set_has_ink_drop_action_on_click(false);
   set_notify_enter_exit_on_child(true);
+  SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
 }
 
 ActionableView::~ActionableView() {
@@ -41,11 +43,6 @@
     *destroyed_ = true;
 }
 
-void ActionableView::OnPaintFocus(gfx::Canvas* canvas) {
-  gfx::RectF rect(GetLocalBounds());
-  canvas->DrawSolidFocusRect(rect, kFocusBorderColor, kFocusBorderThickness);
-}
-
 void ActionableView::HandlePerformActionResult(bool action_performed,
                                                const ui::Event& event) {
   AnimateInkDrop(action_performed ? views::InkDropState::ACTION_TRIGGERED
@@ -74,24 +71,6 @@
   node_data->SetName(accessible_name_);
 }
 
-void ActionableView::OnPaint(gfx::Canvas* canvas) {
-  CustomButton::OnPaint(canvas);
-  if (HasFocus())
-    OnPaintFocus(canvas);
-}
-
-void ActionableView::OnFocus() {
-  CustomButton::OnFocus();
-  // We render differently when focused.
-  SchedulePaint();
-}
-
-void ActionableView::OnBlur() {
-  CustomButton::OnBlur();
-  // We render differently when focused.
-  SchedulePaint();
-}
-
 std::unique_ptr<views::InkDrop> ActionableView::CreateInkDrop() {
   return TrayPopupUtils::CreateInkDrop(ink_drop_style_, this);
 }
diff --git a/ash/system/tray/actionable_view.h b/ash/system/tray/actionable_view.h
index 95d43b4..7e21efd 100644
--- a/ash/system/tray/actionable_view.h
+++ b/ash/system/tray/actionable_view.h
@@ -43,11 +43,6 @@
  protected:
   SystemTrayItem* owner() { return owner_; }
 
-  // Draws focus rectangle on the canvas.
-  // Default implementation draws the focus rectangle with certain inset and
-  // color. Subclasses can override to change the default settings.
-  virtual void OnPaintFocus(gfx::Canvas* canvas);
-
   // Performs an action when user clicks on the view (on mouse-press event), or
   // presses a key when this view is in focus. Returns true if the event has
   // been handled and an action was performed. Returns false otherwise.
@@ -65,9 +60,6 @@
   const char* GetClassName() const override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  void OnPaint(gfx::Canvas* canvas) override;
-  void OnFocus() override;
-  void OnBlur() override;
   std::unique_ptr<views::InkDrop> CreateInkDrop() override;
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
diff --git a/ash/system/tray/system_menu_button.cc b/ash/system/tray/system_menu_button.cc
index f5e6fd3..b0a913b 100644
--- a/ash/system/tray/system_menu_button.cc
+++ b/ash/system/tray/system_menu_button.cc
@@ -36,7 +36,6 @@
 
   SetTooltipText(l10n_util::GetStringUTF16(accessible_name_id));
 
-  SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
   TrayPopupUtils::ConfigureTrayPopupButton(this);
 }
 
diff --git a/ash/system/tray/tray_background_view.cc b/ash/system/tray/tray_background_view.cc
index c16c335..d360779 100644
--- a/ash/system/tray/tray_background_view.cc
+++ b/ash/system/tray/tray_background_view.cc
@@ -31,6 +31,7 @@
 #include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/background.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/painter.h"
 #include "ui/wm/core/window_animations.h"
 
 namespace {
@@ -277,8 +278,44 @@
   return highlight;
 }
 
+void TrayBackgroundView::PaintButtonContents(gfx::Canvas* canvas) {
+  if (shelf()->GetBackgroundType() ==
+          ShelfBackgroundType::SHELF_BACKGROUND_DEFAULT ||
+      !separator_visible_) {
+    return;
+  }
+  // In the given |canvas|, for a horizontal shelf draw a separator line to the
+  // right or left of the TrayBackgroundView when the system is LTR or RTL
+  // aligned, respectively. For a vertical shelf draw the separator line
+  // underneath the items instead.
+  const gfx::Rect local_bounds = GetLocalBounds();
+  const SkColor color = SkColorSetA(SK_ColorWHITE, 0x4D);
+
+  if (shelf_->IsHorizontalAlignment()) {
+    const gfx::PointF point(
+        base::i18n::IsRTL() ? 0 : (local_bounds.width() - kSeparatorWidth),
+        (kShelfSize - kTrayItemSize) / 2);
+    const gfx::Vector2dF vector(0, kTrayItemSize);
+    canvas->Draw1pxLine(point, point + vector, color);
+  } else {
+    const gfx::PointF point((kShelfSize - kTrayItemSize) / 2,
+                            local_bounds.height() - kSeparatorWidth);
+    const gfx::Vector2dF vector(kTrayItemSize, 0);
+    canvas->Draw1pxLine(point, point + vector, color);
+  }
+}
+
 void TrayBackgroundView::UpdateAfterShelfAlignmentChange() {
   tray_container_->UpdateAfterShelfAlignmentChange();
+
+  // The tray itself expands to the right and bottom edge of the screen to make
+  // sure clicking on the edges brings up the popup. However, the focus border
+  // should be only around the container.
+  gfx::Rect paint_bounds(GetBackgroundBounds());
+  paint_bounds.Inset(gfx::Insets(-kFocusBorderThickness));
+  SetFocusPainter(views::Painter::CreateSolidFocusPainter(
+      kFocusBorderColor, kFocusBorderThickness,
+      GetLocalBounds().InsetsFrom(paint_bounds)));
 }
 
 void TrayBackgroundView::OnImplicitAnimationsCompleted() {
@@ -385,44 +422,6 @@
   ActionableView::HandlePerformActionResult(action_performed, event);
 }
 
-void TrayBackgroundView::OnPaintFocus(gfx::Canvas* canvas) {
-  // The tray itself expands to the right and bottom edge of the screen to make
-  // sure clicking on the edges brings up the popup. However, the focus border
-  // should be only around the container.
-  gfx::RectF paint_bounds(GetBackgroundBounds());
-  paint_bounds.Inset(gfx::Insets(-kFocusBorderThickness));
-  canvas->DrawSolidFocusRect(paint_bounds, kFocusBorderColor,
-                             kFocusBorderThickness);
-}
-
-void TrayBackgroundView::OnPaint(gfx::Canvas* canvas) {
-  ActionableView::OnPaint(canvas);
-  if (shelf()->GetBackgroundType() ==
-          ShelfBackgroundType::SHELF_BACKGROUND_DEFAULT ||
-      !separator_visible_) {
-    return;
-  }
-  // In the given |canvas|, for a horizontal shelf draw a separator line to the
-  // right or left of the TrayBackgroundView when the system is LTR or RTL
-  // aligned, respectively. For a vertical shelf draw the separator line
-  // underneath the items instead.
-  const gfx::Rect local_bounds = GetLocalBounds();
-  const SkColor color = SkColorSetA(SK_ColorWHITE, 0x4D);
-
-  if (shelf_->IsHorizontalAlignment()) {
-    const gfx::PointF point(
-        base::i18n::IsRTL() ? 0 : (local_bounds.width() - kSeparatorWidth),
-        (kShelfSize - kTrayItemSize) / 2);
-    const gfx::Vector2dF vector(0, kTrayItemSize);
-    canvas->Draw1pxLine(point, point + vector, color);
-  } else {
-    const gfx::PointF point((kShelfSize - kTrayItemSize) / 2,
-                            local_bounds.height() - kSeparatorWidth);
-    const gfx::Vector2dF vector(kTrayItemSize, 0);
-    canvas->Draw1pxLine(point, point + vector, color);
-  }
-}
-
 gfx::Insets TrayBackgroundView::GetBackgroundInsets() const {
   gfx::Insets insets =
       GetMirroredBackgroundInsets(shelf_->IsHorizontalAlignment());
diff --git a/ash/system/tray/tray_background_view.h b/ash/system/tray/tray_background_view.h
index 8a3b1f3..0429417 100644
--- a/ash/system/tray/tray_background_view.h
+++ b/ash/system/tray/tray_background_view.h
@@ -46,12 +46,12 @@
   void ChildPreferredSizeChanged(views::View* child) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void AboutToRequestFocusFromTabTraversal(bool reverse) override;
-  void OnPaint(gfx::Canvas* canvas) override;
 
   // ActionableView:
   std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
   std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
       const override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // Called whenever the shelf alignment changes.
   virtual void UpdateAfterShelfAlignmentChange();
@@ -109,7 +109,6 @@
   bool PerformAction(const ui::Event& event) override;
   void HandlePerformActionResult(bool action_performed,
                                  const ui::Event& event) override;
-  void OnPaintFocus(gfx::Canvas* canvas) override;
 
  private:
   class TrayWidgetObserver;
diff --git a/ash/system/tray/tray_details_view.cc b/ash/system/tray/tray_details_view.cc
index f334fc5..4aaf772 100644
--- a/ash/system/tray/tray_details_view.cc
+++ b/ash/system/tray/tray_details_view.cc
@@ -35,6 +35,7 @@
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
 #include "ui/views/view_targeter.h"
 #include "ui/views/view_targeter_delegate.h"
 
@@ -236,6 +237,39 @@
 
 }  // namespace
 
+////////////////////////////////////////////////////////////////////////////////
+// TrayDetailsView::InfoLabel:
+
+TrayDetailsView::InfoLabel::InfoLabel(int message_id)
+    : label_(TrayPopupUtils::CreateDefaultLabel()) {
+  SetLayoutManager(new views::FillLayout);
+
+  TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::SYSTEM_INFO);
+  style.SetupLabel(label_);
+
+  TriView* tri_view = TrayPopupUtils::CreateMultiTargetRowView();
+  tri_view->SetInsets(gfx::Insets(0,
+                                  kMenuExtraMarginFromLeftEdge +
+                                      kTrayPopupPaddingHorizontal -
+                                      kTrayPopupLabelHorizontalPadding,
+                                  0, kTrayPopupPaddingHorizontal));
+  tri_view->SetContainerVisible(TriView::Container::START, false);
+  tri_view->SetContainerVisible(TriView::Container::END, false);
+  tri_view->AddView(TriView::Container::CENTER, label_);
+  AddChildView(tri_view);
+
+  SetMessage(message_id);
+}
+
+TrayDetailsView::InfoLabel::~InfoLabel() {}
+
+void TrayDetailsView::InfoLabel::SetMessage(int message_id) {
+  label_->SetText(l10n_util::GetStringUTF16(message_id));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TrayDetailsView:
+
 TrayDetailsView::TrayDetailsView(SystemTrayItem* owner)
     : owner_(owner),
       box_layout_(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)),
diff --git a/ash/system/tray/tray_details_view.h b/ash/system/tray/tray_details_view.h
index 3761ba7..f046fcdc 100644
--- a/ash/system/tray/tray_details_view.h
+++ b/ash/system/tray/tray_details_view.h
@@ -22,6 +22,7 @@
 namespace views {
 class BoxLayout;
 class CustomButton;
+class Label;
 class ProgressBar;
 class ScrollView;
 }  // namespace views
@@ -54,6 +55,22 @@
   SystemTrayItem* owner() { return owner_; }
 
  protected:
+  // A view containing only a label, which is to be inserted as a non-targetable
+  // row within a system menu detailed view (e.g., the "Scanning for devices..."
+  // message that can appear at the top of the Bluetooth detailed view).
+  class InfoLabel : public View {
+   public:
+    explicit InfoLabel(int message_id);
+    ~InfoLabel() override;
+
+    void SetMessage(int message_id);
+
+   private:
+    views::Label* const label_;
+
+    DISALLOW_COPY_AND_ASSIGN(InfoLabel);
+  };
+
   // views::View:
   void Layout() override;
   int GetHeightForWidth(int width) const override;
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index 08bf25c..820647d 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -185,23 +185,6 @@
   return tri_view;
 }
 
-views::View* TrayPopupUtils::CreateInfoLabelRowView(int message_id) {
-  views::Label* label = TrayPopupUtils::CreateDefaultLabel();
-  label->SetText(l10n_util::GetStringUTF16(message_id));
-  TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::SYSTEM_INFO);
-  style.SetupLabel(label);
-
-  TriView* tri_view = CreateMultiTargetRowView();
-  tri_view->SetInsets(
-      gfx::Insets(0, kMenuExtraMarginFromLeftEdge + kTrayPopupPaddingHorizontal,
-                  0, kTrayPopupPaddingHorizontal));
-  tri_view->SetContainerVisible(TriView::Container::START, false);
-  tri_view->SetContainerVisible(TriView::Container::END, false);
-  tri_view->AddView(TriView::Container::CENTER, label);
-
-  return tri_view;
-}
-
 TriView* TrayPopupUtils::CreateMultiTargetRowView() {
   TriView* tri_view = new TriView(0 /* padding_between_items */);
 
@@ -275,10 +258,7 @@
 }
 
 void TrayPopupUtils::ConfigureTrayPopupButton(views::CustomButton* button) {
-  // All buttons that call into here want this focus painter, but
-  // SetFocusPainter is defined separately on derived classes and isn't part of
-  // CustomButton. TODO(estade): Address this.
-  // button->SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
+  button->SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
   button->SetFocusForPlatform();
 
   button->SetInkDropMode(views::InkDropHostView::InkDropMode::ON);
diff --git a/ash/system/tray/tray_popup_utils.h b/ash/system/tray/tray_popup_utils.h
index db268693..e42ff8d9 100644
--- a/ash/system/tray/tray_popup_utils.h
+++ b/ash/system/tray/tray_popup_utils.h
@@ -67,13 +67,6 @@
   // once network and VPN alse use TrayDetailsView::AddScrollListSubHeader().
   static TriView* CreateSubHeaderRowView(bool start_visible);
 
-  // Creates a view containing only a label (corresponding to |message_id|),
-  // which is to be inserted as a non-targetable row within a system menu
-  // detailed view (e.g., the "Scanning for devices..." message that can appear
-  // at the top of the Bluetooth detailed view). The caller takes over ownership
-  // of the created view.
-  static views::View* CreateInfoLabelRowView(int message_id);
-
   // Creates a container view to be used by system menu rows that want to embed
   // a targetable area within one (or more) of the containers OR by any row
   // that requires a non-default layout within the container views. The returned
diff --git a/ash/system/user/button_from_view.cc b/ash/system/user/button_from_view.cc
index 0772fc2..3218703 100644
--- a/ash/system/user/button_from_view.cc
+++ b/ash/system/user/button_from_view.cc
@@ -12,13 +12,13 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/accessibility/ax_node_data.h"
-#include "ui/gfx/canvas.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/painter.h"
 
 namespace ash {
 namespace tray {
@@ -41,6 +41,8 @@
   // Only make it focusable when we are active/interested in clicks.
   if (listener)
     SetFocusForPlatform();
+
+  SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
 }
 
 ButtonFromView::~ButtonFromView() {}
@@ -53,26 +55,6 @@
   button_hovered_ = false;
 }
 
-void ButtonFromView::OnPaint(gfx::Canvas* canvas) {
-  View::OnPaint(canvas);
-  if (HasFocus()) {
-    gfx::RectF rect(GetLocalBounds());
-    canvas->DrawSolidFocusRect(rect, kFocusBorderColor, kFocusBorderThickness);
-  }
-}
-
-void ButtonFromView::OnFocus() {
-  View::OnFocus();
-  // Adding focus frame.
-  SchedulePaint();
-}
-
-void ButtonFromView::OnBlur() {
-  View::OnBlur();
-  // Removing focus frame.
-  SchedulePaint();
-}
-
 void ButtonFromView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   views::CustomButton::GetAccessibleNodeData(node_data);
   // If no label has been explicitly set via CustomButton::SetAccessibleName(),
diff --git a/ash/system/user/button_from_view.h b/ash/system/user/button_from_view.h
index d50f10e0..f57f978f 100644
--- a/ash/system/user/button_from_view.h
+++ b/ash/system/user/button_from_view.h
@@ -32,9 +32,6 @@
   // views::View:
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
-  void OnPaint(gfx::Canvas* canvas) override;
-  void OnFocus() override;
-  void OnBlur() override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void Layout() override;
 
diff --git a/base/sequence_checker_impl.cc b/base/sequence_checker_impl.cc
index 6a9b5b2..bea3e77 100644
--- a/base/sequence_checker_impl.cc
+++ b/base/sequence_checker_impl.cc
@@ -19,7 +19,7 @@
         sequenced_worker_pool_token_(
             SequencedWorkerPool::GetSequenceTokenForCurrentThread()) {
     // SequencedWorkerPool doesn't use SequenceToken and code outside of
-    // SequenceWorkerPool doesn't set a SequencedWorkerPool token.
+    // SequencedWorkerPool doesn't set a SequencedWorkerPool token.
     DCHECK(!sequence_token_.IsValid() ||
            !sequenced_worker_pool_token_.IsValid());
   }
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index e0605290..57cb1b4 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -771,7 +771,8 @@
 }
 
 void LayerImpl::SetNeedsPushProperties() {
-  if (layer_tree_impl_ && !needs_push_properties_) {
+  // There's no need to push layer properties on the active tree.
+  if (!needs_push_properties_ && !layer_tree_impl()->IsActiveTree()) {
     needs_push_properties_ = true;
     layer_tree_impl()->AddLayerShouldPushProperties(this);
   }
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index 10474cf..35a155de 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -287,6 +287,9 @@
   ViewportLayerType viewport_layer_type() const {
     return static_cast<ViewportLayerType>(viewport_layer_type_);
   }
+  bool is_viewport_layer_type() const {
+    return viewport_layer_type() != NOT_VIEWPORT_LAYER;
+  }
 
   void SetCurrentScrollOffset(const gfx::ScrollOffset& scroll_offset);
   gfx::ScrollOffset CurrentScrollOffset() const;
diff --git a/cc/layers/layer_impl_unittest.cc b/cc/layers/layer_impl_unittest.cc
index 07259ad1..d2744ee 100644
--- a/cc/layers/layer_impl_unittest.cc
+++ b/cc/layers/layer_impl_unittest.cc
@@ -115,9 +115,9 @@
   return gfx::Vector2dF(delta.x(), delta.y());
 }
 
-TEST(LayerImplTest, VerifyLayerChangesAreTrackedProperly) {
+TEST(LayerImplTest, VerifyPendingLayerChangesAreTrackedProperly) {
   //
-  // This test checks that layerPropertyChanged() has the correct behavior.
+  // This test checks that LayerPropertyChanged() has the correct behavior.
   //
 
   // The constructor on this will fake that we are on the correct thread.
@@ -129,33 +129,28 @@
   FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
   host_impl.SetVisible(true);
   EXPECT_TRUE(host_impl.InitializeRenderer(compositor_frame_sink.get()));
+  host_impl.CreatePendingTree();
   std::unique_ptr<LayerImpl> root_clip_ptr =
-      LayerImpl::Create(host_impl.active_tree(), 1);
+      LayerImpl::Create(host_impl.pending_tree(), 1);
   LayerImpl* root_clip = root_clip_ptr.get();
   std::unique_ptr<LayerImpl> root_ptr =
-      LayerImpl::Create(host_impl.active_tree(), 2);
+      LayerImpl::Create(host_impl.pending_tree(), 2);
   LayerImpl* root = root_ptr.get();
   root_clip_ptr->test_properties()->AddChild(std::move(root_ptr));
-  host_impl.active_tree()->SetRootLayerForTesting(std::move(root_clip_ptr));
-
-  // Make root the inner viewport scroll layer. This ensures the later call to
-  // |SetViewportBoundsDelta| will be on a viewport layer.
-  LayerTreeImpl::ViewportLayerIds viewport_ids;
-  viewport_ids.inner_viewport_scroll = root->id();
-  host_impl.active_tree()->SetViewportLayersFromIds(viewport_ids);
+  host_impl.pending_tree()->SetRootLayerForTesting(std::move(root_clip_ptr));
 
   root->test_properties()->force_render_surface = true;
   root->SetMasksToBounds(true);
   root->layer_tree_impl()->ResetAllChangeTracking();
 
   root->test_properties()->AddChild(
-      LayerImpl::Create(host_impl.active_tree(), 7));
+      LayerImpl::Create(host_impl.pending_tree(), 7));
   LayerImpl* child = root->test_properties()->children[0];
   child->test_properties()->AddChild(
-      LayerImpl::Create(host_impl.active_tree(), 8));
+      LayerImpl::Create(host_impl.pending_tree(), 8));
   LayerImpl* grand_child = child->test_properties()->children[0];
   root->SetScrollClipLayer(root_clip->id());
-  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  host_impl.pending_tree()->BuildLayerListAndPropertyTreesForTesting();
 
   // Adding children is an internal operation and should not mark layers as
   // changed.
@@ -167,7 +162,7 @@
   float arbitrary_number = 0.352f;
   gfx::Size arbitrary_size = gfx::Size(111, 222);
   gfx::Point arbitrary_point = gfx::Point(333, 444);
-  gfx::Vector2d arbitrary_vector2d = gfx::Vector2d(111, 222);
+
   gfx::Rect arbitrary_rect = gfx::Rect(arbitrary_point, arbitrary_size);
   SkColor arbitrary_color = SkColorSetRGB(10, 20, 30);
   gfx::Transform arbitrary_transform;
@@ -180,49 +175,28 @@
   EXECUTE_AND_VERIFY_NEEDS_PUSH_PROPERTIES_AND_SUBTREE_DID_NOT_CHANGE(
       root->SetUpdateRect(arbitrary_rect));
   EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBounds(arbitrary_size));
-  host_impl.active_tree()->property_trees()->needs_rebuild = true;
-  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  host_impl.pending_tree()->property_trees()->needs_rebuild = true;
+  host_impl.pending_tree()->BuildLayerListAndPropertyTreesForTesting();
 
   // Changing these properties affects the entire subtree of layers.
   EXECUTE_AND_VERIFY_NO_NEED_TO_PUSH_PROPERTIES_AND_SUBTREE_CHANGED(
-      host_impl.active_tree()->SetFilterMutated(root->element_id(),
-                                                arbitrary_filters));
+      host_impl.pending_tree()->SetFilterMutated(root->element_id(),
+                                                 arbitrary_filters));
   EXECUTE_AND_VERIFY_NO_NEED_TO_PUSH_PROPERTIES_AND_SUBTREE_CHANGED(
-      host_impl.active_tree()->SetFilterMutated(root->element_id(),
-                                                FilterOperations()));
+      host_impl.pending_tree()->SetFilterMutated(root->element_id(),
+                                                 FilterOperations()));
   EXECUTE_AND_VERIFY_NO_NEED_TO_PUSH_PROPERTIES_AND_SUBTREE_CHANGED(
-      host_impl.active_tree()->SetOpacityMutated(root->element_id(),
-                                                 arbitrary_number));
+      host_impl.pending_tree()->SetOpacityMutated(root->element_id(),
+                                                  arbitrary_number));
   EXECUTE_AND_VERIFY_NO_NEED_TO_PUSH_PROPERTIES_AND_SUBTREE_CHANGED(
-      host_impl.active_tree()->SetTransformMutated(root->element_id(),
-                                                   arbitrary_transform));
-  EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->ScrollBy(arbitrary_vector2d);
-                                     root->SetNeedsPushProperties());
-  // SetViewportBoundsDelta changes subtree only when masks_to_bounds is true
-  // and it doesn't set needs_push_properties as it is always called on active
-  // tree.
-  root->SetMasksToBounds(true);
-  EXECUTE_AND_VERIFY_SUBTREE_CHANGED(
-      root->SetViewportBoundsDelta(arbitrary_vector2d);
-      root->SetNeedsPushProperties());
+      host_impl.pending_tree()->SetTransformMutated(root->element_id(),
+                                                    arbitrary_transform));
 
   // Changing these properties only affects the layer itself.
   EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetDrawsContent(true));
   EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(
       root->SetBackgroundColor(arbitrary_color));
 
-  // Special case: check that SetBounds changes behavior depending on
-  // masksToBounds.
-  gfx::Size bounds_size(135, 246);
-  root->SetMasksToBounds(false);
-  EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBounds(bounds_size));
-  host_impl.active_tree()->property_trees()->needs_rebuild = true;
-  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
-
-  root->SetMasksToBounds(true);
-  host_impl.active_tree()->property_trees()->needs_rebuild = true;
-  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
-
   // Changing these properties does not cause the layer to be marked as changed
   // but does cause the layer to need to push properties.
   EXECUTE_AND_VERIFY_NEEDS_PUSH_PROPERTIES_AND_SUBTREE_DID_NOT_CHANGE(
@@ -238,7 +212,82 @@
       root->SetPosition(arbitrary_point_f));
   EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetContentsOpaque(true));
   EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetDrawsContent(true));
-  EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetBounds(bounds_size));
+  EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetBounds(root->bounds()));
+}
+
+TEST(LayerImplTest, VerifyActiveLayerChangesAreTrackedProperly) {
+  FakeImplTaskRunnerProvider task_runner_provider;
+  TestTaskGraphRunner task_graph_runner;
+  std::unique_ptr<CompositorFrameSink> compositor_frame_sink =
+      FakeCompositorFrameSink::Create3d();
+  FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
+  host_impl.SetVisible(true);
+  EXPECT_TRUE(host_impl.InitializeRenderer(compositor_frame_sink.get()));
+  std::unique_ptr<LayerImpl> root_clip_ptr =
+      LayerImpl::Create(host_impl.active_tree(), 1);
+  LayerImpl* root_clip = root_clip_ptr.get();
+  std::unique_ptr<LayerImpl> root_ptr =
+      LayerImpl::Create(host_impl.active_tree(), 2);
+  LayerImpl* root = root_ptr.get();
+  root_clip_ptr->test_properties()->AddChild(std::move(root_ptr));
+  host_impl.active_tree()->SetRootLayerForTesting(std::move(root_clip_ptr));
+
+  root->test_properties()->AddChild(
+      LayerImpl::Create(host_impl.active_tree(), 7));
+  LayerImpl* child = root->test_properties()->children[0];
+  root->SetScrollClipLayer(root_clip->id());
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+
+  // Make root the inner viewport container layer. This ensures the later call
+  // to |SetViewportBoundsDelta| will be on a viewport layer.
+  LayerTreeImpl::ViewportLayerIds viewport_ids;
+  viewport_ids.inner_viewport_container = root->id();
+  host_impl.active_tree()->SetViewportLayersFromIds(viewport_ids);
+
+  root->SetMasksToBounds(true);
+  host_impl.active_tree()->property_trees()->needs_rebuild = true;
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  root->layer_tree_impl()->ResetAllChangeTracking();
+
+  // SetViewportBoundsDelta changes subtree only when masks_to_bounds is true.
+  root->SetViewportBoundsDelta(gfx::Vector2d(222, 333));
+  EXPECT_TRUE(root->LayerPropertyChanged());
+  EXPECT_TRUE(host_impl.active_tree()->property_trees()->full_tree_damaged);
+
+  root->SetMasksToBounds(false);
+  host_impl.active_tree()->property_trees()->needs_rebuild = true;
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  root->layer_tree_impl()->ResetAllChangeTracking();
+
+  // SetViewportBoundsDelta does not change the subtree without masks_to_bounds.
+  root->SetViewportBoundsDelta(gfx::Vector2d(333, 444));
+  EXPECT_TRUE(root->LayerPropertyChanged());
+  EXPECT_FALSE(host_impl.active_tree()->property_trees()->full_tree_damaged);
+
+  host_impl.active_tree()->property_trees()->needs_rebuild = true;
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  root->layer_tree_impl()->ResetAllChangeTracking();
+
+  // Ensure some node is affected by the inner viewport bounds delta. This
+  // ensures the later call to |SetViewportBoundsDelta| will require a
+  // transform tree update.
+  TransformTree& transform_tree =
+      host_impl.active_tree()->property_trees()->transform_tree;
+  transform_tree.AddNodeAffectedByInnerViewportBoundsDelta(
+      child->transform_tree_index());
+  EXPECT_FALSE(transform_tree.needs_update());
+  root->SetViewportBoundsDelta(gfx::Vector2d(111, 222));
+  EXPECT_TRUE(transform_tree.needs_update());
+
+  host_impl.active_tree()->property_trees()->needs_rebuild = true;
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  root->layer_tree_impl()->ResetAllChangeTracking();
+
+  // Ensure scrolling changes the transform tree but does not damage all trees.
+  root->ScrollBy(gfx::Vector2d(7, 9));
+  EXPECT_TRUE(transform_tree.needs_update());
+  EXPECT_TRUE(root->LayerPropertyChanged());
+  EXPECT_FALSE(host_impl.active_tree()->property_trees()->full_tree_damaged);
 }
 
 TEST(LayerImplTest, VerifyNeedsUpdateDrawProperties) {
diff --git a/cc/output/ca_layer_overlay.cc b/cc/output/ca_layer_overlay.cc
index 972c8c40..3e99264 100644
--- a/cc/output/ca_layer_overlay.cc
+++ b/cc/output/ca_layer_overlay.cc
@@ -168,6 +168,7 @@
   ca_layer_overlay->contents_rect = quad->tex_coord_rect;
   ca_layer_overlay->contents_rect.Scale(1.f / quad->texture_size.width(),
                                         1.f / quad->texture_size.height());
+  ca_layer_overlay->filter = quad->nearest_neighbor ? GL_NEAREST : GL_LINEAR;
   return CA_LAYER_SUCCESS;
 }
 
diff --git a/cc/output/dc_layer_overlay.cc b/cc/output/dc_layer_overlay.cc
index 7cf6872..e5a043d 100644
--- a/cc/output/dc_layer_overlay.cc
+++ b/cc/output/dc_layer_overlay.cc
@@ -205,6 +205,7 @@
     }
     overlay_damage_rect->Union(quad_rectangle);
 
+    RecordDCLayerResult(DC_LAYER_SUCCESS);
     ca_layer_overlays->push_back(ca_layer);
     // Only allow one overlay for now.
     break;
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 7c3232532..858aee6 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -178,11 +178,11 @@
   // If pending tree topology changed and we still want to notify the pending
   // tree about scroll offset in the active tree, we may not find the
   // corresponding pending layer.
-  if (LayerById(layer_id)) {
+  if (auto* layer = LayerById(layer_id)) {
     // TODO(sunxd): when we have a layer_id to property_tree index map in
     // property trees, use the transform_id parameter instead of looking for
     // indices from LayerImpls.
-    transform_id = LayerById(layer_id)->transform_tree_index();
+    transform_id = layer->transform_tree_index();
   } else {
     DCHECK(!IsActiveTree());
     return;
@@ -219,9 +219,6 @@
 
   int scroll_layer_id, clip_layer_id;
   if (IsViewportLayerId(layer_id)) {
-    if (!InnerViewportContainerLayer())
-      return;
-
     // For scrollbar purposes, a change to any of the four viewport layers
     // should affect the scrollbars tied to the outermost layers, which express
     // the sum of the entire viewport.
@@ -258,11 +255,15 @@
     return;
 
   gfx::ScrollOffset current_offset = scroll_layer->CurrentScrollOffset();
-  if (IsViewportLayerId(scroll_layer_id)) {
+  float viewport_vertical_adjust = 0;
+
+  bool is_viewport_scrollbar = scroll_layer->is_viewport_layer_type();
+  if (is_viewport_scrollbar) {
     current_offset += InnerViewportScrollLayer()->CurrentScrollOffset();
     if (OuterViewportContainerLayer())
       clip_size.SetToMin(OuterViewportContainerLayer()->BoundsForScrolling());
     clip_size.Scale(1 / current_page_scale_factor());
+    viewport_vertical_adjust = clip_layer->ViewportBoundsDelta().y();
   }
 
   bool y_offset_did_change = false;
@@ -276,10 +277,10 @@
       scrollbar->SetClipLayerLength(clip_size.height());
       scrollbar->SetScrollLayerLength(scroll_size.height());
     }
-    scrollbar->SetVerticalAdjust(clip_layer->ViewportBoundsDelta().y());
+    scrollbar->SetVerticalAdjust(viewport_vertical_adjust);
   }
 
-  if (y_offset_did_change && IsViewportLayerId(scroll_layer_id))
+  if (y_offset_did_change && is_viewport_scrollbar)
     TRACE_COUNTER_ID1("cc", "scroll_offset_y", scroll_layer->id(),
                       current_offset.y());
 }
@@ -1246,6 +1247,7 @@
 }
 
 void LayerTreeImpl::AddLayerShouldPushProperties(LayerImpl* layer) {
+  DCHECK(!IsActiveTree()) << "The active tree does not push layer properties";
   layers_that_should_push_properties_.insert(layer);
 }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index b5cad96..e89d9d4 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=61
 MINOR=0
-BUILD=3116
+BUILD=3117
 PATCH=0
diff --git a/chrome/android/java/res/layout/translate_menu_item_checked.xml b/chrome/android/java/res/layout/translate_menu_item_checked.xml
index 39705a4..64b527f6 100644
--- a/chrome/android/java/res/layout/translate_menu_item_checked.xml
+++ b/chrome/android/java/res/layout/translate_menu_item_checked.xml
@@ -14,6 +14,16 @@
         android:layout_width="match_parent"
         android:layout_height="?android:attr/listPreferredItemHeightSmall" >
 
+        <TextView
+            android:id="@+id/menu_item_text"
+            android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_gravity="start"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            android:paddingEnd="16dp" />
         <org.chromium.chrome.browser.widget.TintedImageView
             android:id="@+id/menu_item_icon"
             android:src="@drawable/ic_check_googblue_24dp"
@@ -22,15 +32,6 @@
             android:layout_gravity="end"
             android:gravity="center_vertical"
             chrome:chrometint="@color/dark_mode_tint" />
-        <TextView
-            android:id="@+id/menu_item_text"
-            android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="start"
-            android:gravity="center_vertical"
-            android:singleLine="true"
-            android:paddingStart="16dp" />
 
     </LinearLayout>
 
diff --git a/chrome/android/webapk/strings/android_webapk_strings.grd b/chrome/android/webapk/strings/android_webapk_strings.grd
index 8461366..40f53e5 100644
--- a/chrome/android/webapk/strings/android_webapk_strings.grd
+++ b/chrome/android/webapk/strings/android_webapk_strings.grd
@@ -95,7 +95,7 @@
     <messages fallback_to_english="true">
       <!-- Select host browser dialog -->
       <message name="IDS_CHOOSE_HOST_BROWSER_DIALOG_TITLE" desc="Title for the host browser picker dialog, which is used to ask users to pick a browser to launch the installed WebAPK.">
-        <ph name="WEBAPK_SITE">%1$s<ex>Housing.com</ex></ph> needs a Web browser to run
+        <ph name="WEBAPK_SITE">%1$s<ex>Housing.com</ex></ph> needs a web browser to run
       </message>
       <message name="IDS_CHOOSE_HOST_BROWSER" desc="Content for the host browser picker dialog, which is used to ask users to pick a browser to launch the installed WebAPK.">
         Please select the browser you'd like to use to run <ph name="WEBAPK_SITE">%1$s<ex>Housing.com</ex></ph>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 057e884..d4ed886 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2182,6 +2182,8 @@
       "android/offline_pages/request_coordinator_factory.h",
       "offline_pages/background_loader_offliner.cc",
       "offline_pages/background_loader_offliner.h",
+      "offline_pages/prefetch/prefetch_service_factory.cc",
+      "offline_pages/prefetch/prefetch_service_factory.h",
     ]
     if (is_android) {
       sources += [
@@ -2190,7 +2192,6 @@
       ]
     }
     deps += [
-      "//components/offline_pages/content",
       "//components/offline_pages/content/background_loader",
       "//components/offline_pages/core",
       "//components/offline_pages/core/background:background_offliner",
diff --git a/chrome/browser/android/offline_pages/prefetch/prefetch_background_task.cc b/chrome/browser/android/offline_pages/prefetch/prefetch_background_task.cc
index 17870fc..e2ad52e 100644
--- a/chrome/browser/android/offline_pages/prefetch/prefetch_background_task.cc
+++ b/chrome/browser/android/offline_pages/prefetch/prefetch_background_task.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/android/offline_pages/prefetch/prefetch_background_task.h"
 
+#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_android.h"
-#include "components/offline_pages/content/prefetch_service_factory.h"
 #include "components/offline_pages/core/prefetch/prefetch_service.h"
 #include "content/public/browser/browser_context.h"
 #include "jni/PrefetchBackgroundTask_jni.h"
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index f6417c0..89d8409 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -965,7 +965,6 @@
   // profile cannot have a browser.
   if (IsProfileSignedOut(lastProfile) || lastProfile->IsSystemProfile()) {
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
     return;
   }
@@ -1170,7 +1169,6 @@
   if (lastProfile->IsGuestSession() || IsProfileSignedOut(lastProfile) ||
       lastProfile->IsSystemProfile()) {
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   } else {
     CreateBrowser(lastProfile);
@@ -1342,7 +1340,6 @@
   } else {
     // No way to create a browser, default to the User Manager.
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_CHROME_SETTINGS);
   }
 }
@@ -1356,7 +1353,6 @@
   } else {
     // No way to create a browser, default to the User Manager.
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME);
   }
 }
diff --git a/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc b/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
index 03d55d7da..35c9f79a 100644
--- a/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
+++ b/chrome/browser/apps/app_shim/extension_app_shim_handler_mac.cc
@@ -240,7 +240,6 @@
 
 void ExtensionAppShimHandler::Delegate::LaunchUserManager() {
   UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_APP_LAUNCHER);
 }
 
diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc
index dd819bc..b2f6fdd9 100644
--- a/chrome/browser/background/background_mode_manager.cc
+++ b/chrome/browser/background/background_mode_manager.cc
@@ -644,7 +644,6 @@
         chrome::ShowAboutChrome(bmd->GetBrowserWindow());
       } else {
         UserManager::Show(base::FilePath(),
-                          profiles::USER_MANAGER_NO_TUTORIAL,
                           profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME);
       }
       break;
@@ -654,7 +653,6 @@
         chrome::OpenTaskManager(bmd->GetBrowserWindow());
       } else {
         UserManager::Show(base::FilePath(),
-                          profiles::USER_MANAGER_NO_TUTORIAL,
                           profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER);
       }
       break;
@@ -683,7 +681,6 @@
         bmd->ExecuteCommand(command_id, event_flags);
       } else {
         UserManager::Show(base::FilePath(),
-                          profiles::USER_MANAGER_NO_TUTORIAL,
                           profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
       }
       break;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index f22e7aa..10c3cc3 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -977,8 +977,6 @@
     "login/ui/user_adding_screen_input_methods_controller.h",
     "login/ui/web_contents_forced_title.cc",
     "login/ui/web_contents_forced_title.h",
-    "login/ui/web_contents_set_background_color.cc",
-    "login/ui/web_contents_set_background_color.h",
     "login/ui/webui_login_display.cc",
     "login/ui/webui_login_display.h",
     "login/ui/webui_login_view.cc",
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.cc b/chrome/browser/chromeos/login/ui/webui_login_view.cc
index 89d498d..78e038c8 100644
--- a/chrome/browser/chromeos/login/ui/webui_login_view.cc
+++ b/chrome/browser/chromeos/login/ui/webui_login_view.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/chromeos/login/ui/preloaded_web_view_factory.h"
 #include "chrome/browser/chromeos/login/ui/proxy_settings_dialog.h"
 #include "chrome/browser/chromeos/login/ui/web_contents_forced_title.h"
-#include "chrome/browser/chromeos/login/ui/web_contents_set_background_color.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_display.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
@@ -57,6 +56,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/keyboard/keyboard_controller.h"
+#include "ui/views/controls/webview/web_contents_set_background_color.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/widget/widget.h"
 
@@ -220,7 +220,7 @@
   if (!title.empty())
     WebContentsForcedTitle::CreateForWebContentsWithTitle(web_contents, title);
 
-  WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
+  views::WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
       web_contents, SK_ColorTRANSPARENT);
 
   // Ensure that the login UI has a tab ID, which will allow the GAIA auth
diff --git a/chrome/browser/chromeos/printing/printer_configurer.h b/chrome/browser/chromeos/printing/printer_configurer.h
index bd97c26..1bfcb039 100644
--- a/chrome/browser/chromeos/printing/printer_configurer.h
+++ b/chrome/browser/chromeos/printing/printer_configurer.h
@@ -14,6 +14,8 @@
 
 namespace chromeos {
 
+// These values are written to logs.  New enum values can be added, but existing
+// enums must never be renumbered or deleted and reused.
 enum PrinterSetupResult {
   kFatalError = 0,          // Setup failed in an unrecognized way
   kSuccess = 1,             // Printer set up successfully
diff --git a/chrome/browser/gcm/gcm_profile_service_factory.cc b/chrome/browser/gcm/gcm_profile_service_factory.cc
index 0d55417..8a35e4d 100644
--- a/chrome/browser/gcm/gcm_profile_service_factory.cc
+++ b/chrome/browser/gcm/gcm_profile_service_factory.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/gcm/gcm_profile_service_factory.h"
-
 #include <memory>
 
 #include "base/sequenced_task_runner.h"
@@ -15,6 +14,7 @@
 #include "chrome/browser/signin/signin_manager_factory.h"
 #include "components/gcm_driver/gcm_profile_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/offline_pages/features/features.h"
 #include "components/signin/core/browser/profile_identity_provider.h"
 #include "components/signin/core/browser/signin_manager.h"
 #include "content/public/browser/browser_thread.h"
@@ -26,6 +26,12 @@
 #include "components/gcm_driver/gcm_client_factory.h"
 #endif
 
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
+#include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
+#include "components/offline_pages/core/prefetch/prefetch_service.h"
+#endif
+
 namespace gcm {
 
 // static
@@ -53,6 +59,9 @@
 #if !defined(OS_ANDROID)
   DependsOn(LoginUIServiceFactory::GetInstance());
 #endif
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+  DependsOn(offline_pages::PrefetchServiceFactory::GetInstance());
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
 }
 
 GCMProfileServiceFactory::~GCMProfileServiceFactory() {
@@ -67,10 +76,12 @@
       base::CreateSequencedTaskRunnerWithTraits(
           {base::MayBlock(), base::TaskPriority::BACKGROUND,
            base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
+  std::unique_ptr<GCMProfileService> service = nullptr;
 #if defined(OS_ANDROID)
-  return new GCMProfileService(profile->GetPath(), blocking_task_runner);
+  service = base::WrapUnique(
+      new GCMProfileService(profile->GetPath(), blocking_task_runner));
 #else
-  return new GCMProfileService(
+  service = base::WrapUnique(new GCMProfileService(
       profile->GetPrefs(), profile->GetPath(), profile->GetRequestContext(),
       chrome::GetChannel(),
       gcm::GetProductCategoryForSubtypes(profile->GetPrefs()),
@@ -83,8 +94,20 @@
           content::BrowserThread::UI),
       content::BrowserThread::GetTaskRunnerForThread(
           content::BrowserThread::IO),
-      blocking_task_runner);
+      blocking_task_runner));
 #endif
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+  offline_pages::PrefetchService* prefetch_service =
+      offline_pages::PrefetchServiceFactory::GetForBrowserContext(context);
+  if (prefetch_service != nullptr) {
+    offline_pages::PrefetchGCMHandler* prefetch_gcm_handler =
+        prefetch_service->GetPrefetchGCMHandler();
+    service->driver()->AddAppHandler(prefetch_gcm_handler->GetAppId(),
+                                     prefetch_gcm_handler->AsGCMAppHandler());
+  }
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+  return service.release();
 }
 
 content::BrowserContext* GCMProfileServiceFactory::GetBrowserContextToUse(
diff --git a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
index 30c0dd4..f9a93716 100644
--- a/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
+++ b/chrome/browser/ntp_snippets/content_suggestions_service_factory.cc
@@ -47,6 +47,7 @@
 #include "components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h"
 #include "components/ntp_snippets/sessions/tab_delegate_sync_adapter.h"
 #include "components/ntp_snippets/user_classifier.h"
+#include "components/offline_pages/features/features.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_json/safe_json_parser.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
@@ -62,30 +63,25 @@
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/ntp/ntp_snippets_launcher.h"
-#include "chrome/browser/android/offline_pages/offline_page_model_factory.h"
-#include "chrome/browser/android/offline_pages/request_coordinator_factory.h"
 #include "chrome/browser/download/download_core_service.h"
 #include "chrome/browser/download/download_core_service_factory.h"
 #include "chrome/browser/download/download_history.h"
 #include "chrome/browser/ntp_snippets/download_suggestions_provider.h"
-#include "components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h"
 #include "components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h"
-#include "components/offline_pages/content/suggested_articles_observer.h"
+#include "components/physical_web/data_source/physical_web_data_source.h"
+#endif
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/browser/android/offline_pages/offline_page_model_factory.h"
+#include "chrome/browser/android/offline_pages/request_coordinator_factory.h"
+#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
+#include "components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h"
 #include "components/offline_pages/core/background/request_coordinator.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_model.h"
+#include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
 #include "components/offline_pages/core/recent_tabs/recent_tabs_ui_adapter_delegate.h"
-#include "components/physical_web/data_source/physical_web_data_source.h"
-
-using content::DownloadManager;
-using ntp_snippets::PhysicalWebPageSuggestionsProvider;
-using ntp_snippets::RecentTabSuggestionsProvider;
-using offline_pages::OfflinePageModel;
-using offline_pages::RequestCoordinator;
-using offline_pages::OfflinePageModelFactory;
-using offline_pages::RequestCoordinatorFactory;
-using physical_web::PhysicalWebDataSource;
-#endif  // OS_ANDROID
+#endif
 
 using bookmarks::BookmarkModel;
 using content::BrowserThread;
@@ -108,6 +104,20 @@
 using syncer::SyncService;
 using translate::LanguageModel;
 
+#if defined(OS_ANDROID)
+using content::DownloadManager;
+using ntp_snippets::PhysicalWebPageSuggestionsProvider;
+using physical_web::PhysicalWebDataSource;
+#endif  // OS_ANDROID
+
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+using ntp_snippets::RecentTabSuggestionsProvider;
+using offline_pages::OfflinePageModel;
+using offline_pages::RequestCoordinator;
+using offline_pages::OfflinePageModelFactory;
+using offline_pages::RequestCoordinatorFactory;
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
 // For now, ContentSuggestionsService must only be instantiated on Android.
 // See also crbug.com/688366.
 #if defined(OS_ANDROID)
@@ -130,7 +140,7 @@
 #endif
 }
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
 
 bool IsRecentTabProviderEnabled() {
   return base::FeatureList::IsEnabled(
@@ -151,6 +161,10 @@
   service->RegisterProvider(std::move(provider));
 }
 
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+#if defined(OS_ANDROID)
+
 void RegisterDownloadsProvider(OfflinePageModel* offline_page_model,
                                DownloadManager* download_manager,
                                DownloadHistory* download_history,
@@ -285,9 +299,10 @@
   DependsOn(BookmarkModelFactory::GetInstance());
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(LargeIconServiceFactory::GetInstance());
-#if defined(OS_ANDROID)
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
   DependsOn(OfflinePageModelFactory::GetInstance());
-#endif  // OS_ANDROID
+  DependsOn(offline_pages::PrefetchServiceFactory::GetInstance());
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
   DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
   DependsOn(ProfileSyncServiceFactory::GetInstance());
   DependsOn(SigninManagerFactory::GetInstance());
@@ -332,7 +347,7 @@
       pref_service, std::move(category_ranker), std::move(user_classifier),
       std::move(scheduler));
 
-#if defined(OS_ANDROID)
+#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
   OfflinePageModel* offline_page_model =
       OfflinePageModelFactory::GetForBrowserContext(profile);
   if (IsRecentTabProviderEnabled()) {
@@ -342,6 +357,12 @@
                               pref_service);
   }
 
+  offline_pages::PrefetchService* prefetch_service =
+      offline_pages::PrefetchServiceFactory::GetForBrowserContext(profile);
+  prefetch_service->ObserveContentSuggestionsService(service);
+#endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
+
+#if defined(OS_ANDROID)
   bool show_asset_downloads =
       !IsChromeHomeEnabled() &&
       base::FeatureList::IsEnabled(features::kAssetDownloadSuggestionsFeature);
@@ -361,9 +382,6 @@
         show_asset_downloads ? download_manager : nullptr, download_history,
         service, pref_service);
   }
-
-  offline_pages::SuggestedArticlesObserver::ObserveContentSuggestionsService(
-      profile, service);
 #endif  // OS_ANDROID
 
   // |bookmark_model| can be null in tests.
diff --git a/components/offline_pages/content/prefetch_service_factory.cc b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
similarity index 80%
rename from components/offline_pages/content/prefetch_service_factory.cc
rename to chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
index ffe46b26..76e0722 100644
--- a/components/offline_pages/content/prefetch_service_factory.cc
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/offline_pages/content/prefetch_service_factory.h"
+#include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h"
 
+#include "base/memory/ptr_util.h"
 #include "base/memory/singleton.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
 #include "components/offline_pages/core/prefetch/prefetch_service_impl.h"
 #include "content/public/browser/browser_context.h"
 
@@ -15,7 +17,6 @@
     : BrowserContextKeyedServiceFactory(
           "OfflinePagePrefetchService",
           BrowserContextDependencyManager::GetInstance()) {}
-
 // static
 PrefetchServiceFactory* PrefetchServiceFactory::GetInstance() {
   return base::Singleton<PrefetchServiceFactory>::get();
@@ -30,7 +31,7 @@
 
 KeyedService* PrefetchServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  return new PrefetchServiceImpl();
+  return new PrefetchServiceImpl(base::MakeUnique<PrefetchGCMAppHandler>());
 }
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/content/prefetch_service_factory.h b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
similarity index 83%
rename from components/offline_pages/content/prefetch_service_factory.h
rename to chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
index c7f4fb16..dac9fe12 100644
--- a/components/offline_pages/content/prefetch_service_factory.h
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OFFLINE_PAGES_CONTENT_PREFETCH_SERVICE_FACTORY_H_
-#define COMPONENTS_OFFLINE_PAGES_CONTENT_PREFETCH_SERVICE_FACTORY_H_
+#ifndef CHROME_BROWSER_OFFLINE_PAGES_PREFETCH_PREFETCH_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_OFFLINE_PAGES_PREFETCH_PREFETCH_SERVICE_FACTORY_H_
 
 #include "base/macros.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
@@ -40,4 +40,4 @@
 
 }  // namespace offline_pages
 
-#endif  // COMPONENTS_OFFLINE_PAGES_CONTENT_PREFETCH_SERVICE_FACTORY_H_
+#endif  // CHROME_BROWSER_OFFLINE_PAGES_PREFETCH_PREFETCH_SERVICE_FACTORY_H_
diff --git a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.cc
index 89b97bcb..07d663c 100644
--- a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.h"
 
+#include "chrome/browser/page_load_metrics/observers/from_gws_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "third_party/WebKit/public/platform/WebLoadingBehaviorFlag.h"
 
@@ -23,6 +24,12 @@
 const char kHistogramServiceWorkerParseStartToFirstContentfulPaint[] =
     "PageLoad.Clients.ServiceWorker.PaintTiming."
     "ParseStartToFirstContentfulPaint";
+const char kHistogramServiceWorkerFirstMeaningfulPaint[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "NavigationToFirstMeaningfulPaint";
+const char kHistogramServiceWorkerParseStartToFirstMeaningfulPaint[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "ParseStartToFirstMeaningfulPaint";
 const char kHistogramServiceWorkerDomContentLoaded[] =
     "PageLoad.Clients.ServiceWorker.DocumentTiming."
     "NavigationToDOMContentLoadedEventFired";
@@ -35,6 +42,12 @@
 const char kHistogramServiceWorkerParseStartToFirstContentfulPaintInbox[] =
     "PageLoad.Clients.ServiceWorker.PaintTiming."
     "ParseStartToFirstContentfulPaint.inbox";
+const char kHistogramServiceWorkerFirstMeaningfulPaintInbox[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "NavigationToFirstMeaningfulPaint.inbox";
+const char kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "ParseStartToFirstMeaningfulPaint.inbox";
 const char kHistogramServiceWorkerDomContentLoadedInbox[] =
     "PageLoad.Clients.ServiceWorker.DocumentTiming."
     "NavigationToDOMContentLoadedEventFired.inbox";
@@ -42,6 +55,25 @@
     "PageLoad.Clients.ServiceWorker.DocumentTiming.NavigationToLoadEventFired."
     "inbox";
 
+const char kHistogramServiceWorkerFirstContentfulPaintSearch[] =
+    "PageLoad.Clients.ServiceWorker.PaintTiming."
+    "NavigationToFirstContentfulPaint.search";
+const char kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch[] =
+    "PageLoad.Clients.ServiceWorker.PaintTiming."
+    "ParseStartToFirstContentfulPaint.search";
+const char kHistogramServiceWorkerFirstMeaningfulPaintSearch[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "NavigationToFirstMeaningfulPaint.search";
+const char kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch[] =
+    "PageLoad.Clients.ServiceWorker.Experimental.PaintTiming."
+    "ParseStartToFirstMeaningfulPaint.search";
+const char kHistogramServiceWorkerDomContentLoadedSearch[] =
+    "PageLoad.Clients.ServiceWorker.DocumentTiming."
+    "NavigationToDOMContentLoadedEventFired.search";
+const char kHistogramServiceWorkerLoadSearch[] =
+    "PageLoad.Clients.ServiceWorker.DocumentTiming.NavigationToLoadEventFired."
+    "search";
+
 }  // namespace internal
 
 namespace {
@@ -88,6 +120,50 @@
         internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintInbox,
         timing.paint_timing->first_contentful_paint.value() -
             timing.parse_timing->parse_start.value());
+  } else if (FromGWSPageLoadMetricsLogger::IsGoogleSearchResultUrl(info.url)) {
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerFirstContentfulPaintSearch,
+        timing.paint_timing->first_contentful_paint.value());
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch,
+        timing.paint_timing->first_contentful_paint.value() -
+            timing.parse_timing->parse_start.value());
+  }
+}
+
+void ServiceWorkerPageLoadMetricsObserver::
+    OnFirstMeaningfulPaintInMainFrameDocument(
+        const page_load_metrics::mojom::PageLoadTiming& timing,
+        const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!IsServiceWorkerControlled(info))
+    return;
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.paint_timing->first_meaningful_paint, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(internal::kHistogramServiceWorkerFirstMeaningfulPaint,
+                      timing.paint_timing->first_meaningful_paint.value());
+  PAGE_LOAD_HISTOGRAM(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint,
+      timing.paint_timing->first_meaningful_paint.value() -
+          timing.parse_timing->parse_start.value());
+
+  if (IsInboxSite(info.url)) {
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerFirstMeaningfulPaintInbox,
+        timing.paint_timing->first_meaningful_paint.value());
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox,
+        timing.paint_timing->first_meaningful_paint.value() -
+            timing.parse_timing->parse_start.value());
+  } else if (FromGWSPageLoadMetricsLogger::IsGoogleSearchResultUrl(info.url)) {
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerFirstMeaningfulPaintSearch,
+        timing.paint_timing->first_meaningful_paint.value());
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch,
+        timing.paint_timing->first_meaningful_paint.value() -
+            timing.parse_timing->parse_start.value());
   }
 }
 
@@ -107,6 +183,10 @@
     PAGE_LOAD_HISTOGRAM(
         internal::kHistogramServiceWorkerDomContentLoadedInbox,
         timing.document_timing->dom_content_loaded_event_start.value());
+  } else if (FromGWSPageLoadMetricsLogger::IsGoogleSearchResultUrl(info.url)) {
+    PAGE_LOAD_HISTOGRAM(
+        internal::kHistogramServiceWorkerDomContentLoadedSearch,
+        timing.document_timing->dom_content_loaded_event_start.value());
   }
 }
 
@@ -123,6 +203,9 @@
   if (IsInboxSite(info.url)) {
     PAGE_LOAD_HISTOGRAM(internal::kHistogramServiceWorkerLoadInbox,
                         timing.document_timing->load_event_start.value());
+  } else if (FromGWSPageLoadMetricsLogger::IsGoogleSearchResultUrl(info.url)) {
+    PAGE_LOAD_HISTOGRAM(internal::kHistogramServiceWorkerLoadSearch,
+                        timing.document_timing->load_event_start.value());
   }
 }
 
diff --git a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.h
index 8e1b9f2..b6b974d3 100644
--- a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer.h
@@ -16,14 +16,29 @@
 extern const char kHistogramServiceWorkerFirstContentfulPaint[];
 extern const char kBackgroundHistogramServiceWorkerFirstContentfulPaint[];
 extern const char kHistogramServiceWorkerParseStartToFirstContentfulPaint[];
+extern const char kHistogramServiceWorkerFirstMeaningfulPaint[];
+extern const char kHistogramServiceWorkerParseStartToFirstMeaningfulPaint[];
 extern const char kHistogramServiceWorkerDomContentLoaded[];
 extern const char kHistogramServiceWorkerLoad[];
+
 extern const char kHistogramServiceWorkerFirstContentfulPaintInbox[];
+extern const char kHistogramServiceWorkerFirstMeaningfulPaintInbox[];
+extern const char
+    kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox[];
 extern const char
     kHistogramServiceWorkerParseStartToFirstContentfulPaintInbox[];
 extern const char kHistogramServiceWorkerDomContentLoadedInbox[];
 extern const char kHistogramServiceWorkerLoadInbox[];
 
+extern const char kHistogramServiceWorkerFirstContentfulPaintSearch[];
+extern const char kHistogramServiceWorkerFirstMeaningfulPaintSearch[];
+extern const char
+    kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch[];
+extern const char
+    kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch[];
+extern const char kHistogramServiceWorkerDomContentLoadedSearch[];
+extern const char kHistogramServiceWorkerLoadSearch[];
+
 }  // namespace internal
 
 class ServiceWorkerPageLoadMetricsObserver
@@ -37,6 +52,9 @@
   void OnFirstContentfulPaintInPage(
       const page_load_metrics::mojom::PageLoadTiming& timing,
       const page_load_metrics::PageLoadExtraInfo& extra_info) override;
+  void OnFirstMeaningfulPaintInMainFrameDocument(
+      const page_load_metrics::mojom::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& info) override;
   void OnDomContentLoadedEventStart(
       const page_load_metrics::mojom::PageLoadTiming& timing,
       const page_load_metrics::PageLoadExtraInfo& extra_info) override;
diff --git a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer_unittest.cc
index 39a7f6e2..3074e0c3 100644
--- a/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/service_worker_page_load_metrics_observer_unittest.cc
@@ -11,6 +11,7 @@
 
 const char kDefaultTestUrl[] = "https://google.com";
 const char kInboxTestUrl[] = "https://inbox.google.com/test";
+const char kSearchTestUrl[] = "https://www.google.com/search?q=test";
 
 }  // namespace
 
@@ -37,6 +38,10 @@
     histogram_tester().ExpectTotalCount(
         internal::kHistogramServiceWorkerParseStartToFirstContentfulPaint, 0);
     histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerFirstMeaningfulPaint, 0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint, 0);
+    histogram_tester().ExpectTotalCount(
         internal::kHistogramServiceWorkerDomContentLoaded, 0);
     histogram_tester().ExpectTotalCount(internal::kHistogramServiceWorkerLoad,
                                         0);
@@ -53,11 +58,33 @@
         internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintInbox,
         0);
     histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerFirstMeaningfulPaintInbox, 0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox,
+        0);
+    histogram_tester().ExpectTotalCount(
         internal::kHistogramServiceWorkerDomContentLoadedInbox, 0);
     histogram_tester().ExpectTotalCount(
         internal::kHistogramServiceWorkerLoadInbox, 0);
   }
 
+  void AssertNoSearchHistogramsLogged() {
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerFirstContentfulPaintSearch, 0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch,
+        0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerFirstMeaningfulPaintSearch, 0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch,
+        0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerDomContentLoadedSearch, 0);
+    histogram_tester().ExpectTotalCount(
+        internal::kHistogramServiceWorkerLoadSearch, 0);
+  }
+
   void InitializeTestPageLoadTiming(
       page_load_metrics::mojom::PageLoadTiming* timing) {
     page_load_metrics::InitPageLoadTimingForTest(timing);
@@ -65,6 +92,8 @@
     timing->parse_timing->parse_start = base::TimeDelta::FromMilliseconds(100);
     timing->paint_timing->first_contentful_paint =
         base::TimeDelta::FromMilliseconds(300);
+    timing->paint_timing->first_meaningful_paint =
+        base::TimeDelta::FromMilliseconds(700);
     timing->document_timing->dom_content_loaded_event_start =
         base::TimeDelta::FromMilliseconds(600);
     timing->document_timing->load_event_start =
@@ -76,6 +105,7 @@
 TEST_F(ServiceWorkerPageLoadMetricsObserverTest, NoMetrics) {
   AssertNoServiceWorkerHistogramsLogged();
   AssertNoInboxHistogramsLogged();
+  AssertNoSearchHistogramsLogged();
 }
 
 TEST_F(ServiceWorkerPageLoadMetricsObserverTest, NoServiceWorker) {
@@ -87,6 +117,7 @@
 
   AssertNoServiceWorkerHistogramsLogged();
   AssertNoInboxHistogramsLogged();
+  AssertNoSearchHistogramsLogged();
 }
 
 TEST_F(ServiceWorkerPageLoadMetricsObserverTest, WithServiceWorker) {
@@ -134,6 +165,7 @@
       internal::kHistogramServiceWorkerParseStart, 1);
 
   AssertNoInboxHistogramsLogged();
+  AssertNoSearchHistogramsLogged();
 }
 
 TEST_F(ServiceWorkerPageLoadMetricsObserverTest, WithServiceWorkerBackground) {
@@ -165,6 +197,10 @@
   histogram_tester().ExpectTotalCount(
       internal::kHistogramServiceWorkerParseStartToFirstContentfulPaint, 0);
   histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaint, 0);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint, 0);
+  histogram_tester().ExpectTotalCount(
       internal::kHistogramServiceWorkerDomContentLoaded, 0);
   histogram_tester().ExpectTotalCount(internal::kHistogramServiceWorkerLoad, 0);
   // TODO(crbug.com/686590): The following expectation fails on Win7 Tests
@@ -173,6 +209,7 @@
   //     internal::kBackgroundHistogramServiceWorkerParseStart, 1);
 
   AssertNoInboxHistogramsLogged();
+  AssertNoSearchHistogramsLogged();
 }
 
 TEST_F(ServiceWorkerPageLoadMetricsObserverTest, InboxSite) {
@@ -218,6 +255,35 @@
       1);
 
   histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaint,
+      timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaintInbox, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaintInbox,
+      timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint,
+      (timing.paint_timing->first_meaningful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox,
+      1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintInbox,
+      (timing.paint_timing->first_meaningful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+
+  histogram_tester().ExpectTotalCount(
       internal::kHistogramServiceWorkerDomContentLoaded, 1);
   histogram_tester().ExpectBucketCount(
       internal::kHistogramServiceWorkerDomContentLoaded,
@@ -243,4 +309,107 @@
       timing.document_timing->load_event_start.value().InMilliseconds(), 1);
   histogram_tester().ExpectTotalCount(
       internal::kHistogramServiceWorkerParseStart, 1);
+
+  AssertNoSearchHistogramsLogged();
+}
+
+TEST_F(ServiceWorkerPageLoadMetricsObserverTest, SearchSite) {
+  page_load_metrics::mojom::PageLoadTiming timing;
+  InitializeTestPageLoadTiming(&timing);
+
+  NavigateAndCommit(GURL(kSearchTestUrl));
+  page_load_metrics::mojom::PageLoadMetadata metadata;
+  metadata.behavior_flags |=
+      blink::WebLoadingBehaviorFlag::kWebLoadingBehaviorServiceWorkerControlled;
+  SimulateTimingAndMetadataUpdate(timing, metadata);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstContentfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstContentfulPaint,
+      timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstContentfulPaintSearch, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstContentfulPaintSearch,
+      timing.paint_timing->first_contentful_paint.value().InMilliseconds(), 1);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kBackgroundHistogramServiceWorkerFirstContentfulPaint, 0);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstContentfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstContentfulPaint,
+      (timing.paint_timing->first_contentful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch,
+      1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstContentfulPaintSearch,
+      (timing.paint_timing->first_contentful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaint,
+      timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaintSearch, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerFirstMeaningfulPaintSearch,
+      timing.paint_timing->first_meaningful_paint.value().InMilliseconds(), 1);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaint,
+      (timing.paint_timing->first_meaningful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch,
+      1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerParseStartToFirstMeaningfulPaintSearch,
+      (timing.paint_timing->first_meaningful_paint.value() -
+       timing.parse_timing->parse_start.value())
+          .InMilliseconds(),
+      1);
+
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerDomContentLoaded, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerDomContentLoaded,
+      timing.document_timing->dom_content_loaded_event_start.value()
+          .InMilliseconds(),
+      1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerDomContentLoadedSearch, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerDomContentLoadedSearch,
+      timing.document_timing->dom_content_loaded_event_start.value()
+          .InMilliseconds(),
+      1);
+
+  histogram_tester().ExpectTotalCount(internal::kHistogramServiceWorkerLoad, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerLoad,
+      timing.document_timing->load_event_start.value().InMilliseconds(), 1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerLoadSearch, 1);
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramServiceWorkerLoadSearch,
+      timing.document_timing->load_event_start.value().InMilliseconds(), 1);
+  histogram_tester().ExpectTotalCount(
+      internal::kHistogramServiceWorkerParseStart, 1);
+
+  AssertNoInboxHistogramsLogged();
 }
diff --git a/chrome/browser/profiles/avatar_menu.cc b/chrome/browser/profiles/avatar_menu.cc
index 76a7cc3..43ed71ae 100644
--- a/chrome/browser/profiles/avatar_menu.cc
+++ b/chrome/browser/profiles/avatar_menu.cc
@@ -113,7 +113,6 @@
   // Don't open a browser window for signed-out profiles.
   if (item.signin_required) {
     UserManager::Show(item.profile_path,
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
     return;
   }
diff --git a/chrome/browser/profiles/profile_window.cc b/chrome/browser/profiles/profile_window.cc
index 594dbf7b..4e36df9 100644
--- a/chrome/browser/profiles/profile_window.cc
+++ b/chrome/browser/profiles/profile_window.cc
@@ -123,13 +123,11 @@
 };
 
 // Called after a |system_profile| is available to be used by the user manager.
-// Based on the value of |tutorial_mode| we determine a url to be displayed
-// by the webui and run the |callback|, if it exists. Depending on the value of
+// Runs |callback|, if it exists. Depending on the value of
 // |user_manager_action|, executes an action once the user manager displays or
 // after a profile is opened.
 void OnUserManagerSystemProfileCreated(
     const base::FilePath& profile_path_to_focus,
-    profiles::UserManagerTutorialMode tutorial_mode,
     profiles::UserManagerAction user_manager_action,
     const base::Callback<void(Profile*, const std::string&)>& callback,
     Profile* system_profile,
@@ -140,9 +138,7 @@
   // Tell the webui which user should be focused.
   std::string page = chrome::kChromeUIMdUserManagerUrl;
 
-  if (tutorial_mode == profiles::USER_MANAGER_TUTORIAL_OVERVIEW) {
-    page += profiles::kUserManagerDisplayTutorial;
-  } else if (!profile_path_to_focus.empty()) {
+  if (!profile_path_to_focus.empty()) {
     // The file path is processed in the same way as base::CreateFilePathValue
     // (i.e. convert to std::string with AsUTF8Unsafe()), and then URI encoded.
     page += "#";
@@ -186,7 +182,6 @@
 namespace profiles {
 
 // User Manager parameters are prefixed with hash.
-const char kUserManagerDisplayTutorial[] = "#tutorial";
 const char kUserManagerOpenCreateUserPage[] = "#create-user";
 const char kUserManagerSelectProfileTaskManager[] = "#task-manager";
 const char kUserManagerSelectProfileAboutChrome[] = "#about-chrome";
@@ -360,7 +355,6 @@
 
 void ProfileBrowserCloseSuccess(const base::FilePath& profile_path) {
   UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
 }
 
@@ -391,7 +385,6 @@
 
   chrome::HideTaskManager();
   UserManager::Show(profile_path,
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
 }
 
@@ -447,7 +440,6 @@
 
 void CreateSystemProfileForUserManager(
     const base::FilePath& profile_path_to_focus,
-    profiles::UserManagerTutorialMode tutorial_mode,
     profiles::UserManagerAction user_manager_action,
     const base::Callback<void(Profile*, const std::string&)>& callback) {
   // Create the system profile, if necessary, and open the User Manager
@@ -456,7 +448,6 @@
       ProfileManager::GetSystemProfilePath(),
       base::Bind(&OnUserManagerSystemProfileCreated,
                  profile_path_to_focus,
-                 tutorial_mode,
                  user_manager_action,
                  callback),
       base::string16(),
@@ -464,24 +455,8 @@
       std::string());
 }
 
-void ShowUserManagerMaybeWithTutorial(Profile* profile) {
-  // Guest users cannot appear in the User Manager, nor display a tutorial.
-  if (!profile || profile->IsGuestSession()) {
-    UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
-                      profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
-    return;
-  }
-  UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_TUTORIAL_OVERVIEW,
-                    profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
-}
-
-void BubbleViewModeFromAvatarBubbleMode(
-    BrowserWindow::AvatarBubbleMode mode,
-    BubbleViewMode* bubble_view_mode,
-    TutorialMode* tutorial_mode) {
-  *tutorial_mode = TUTORIAL_MODE_NONE;
+void BubbleViewModeFromAvatarBubbleMode(BrowserWindow::AvatarBubbleMode mode,
+                                        BubbleViewMode* bubble_view_mode) {
   switch (mode) {
     case BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT:
       *bubble_view_mode = BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT;
@@ -497,27 +472,13 @@
       return;
     case BrowserWindow::AVATAR_BUBBLE_MODE_CONFIRM_SIGNIN:
       *bubble_view_mode = BUBBLE_VIEW_MODE_PROFILE_CHOOSER;
-      *tutorial_mode = TUTORIAL_MODE_CONFIRM_SIGNIN;
       return;
     case BrowserWindow::AVATAR_BUBBLE_MODE_SHOW_ERROR:
       *bubble_view_mode = BUBBLE_VIEW_MODE_PROFILE_CHOOSER;
-      *tutorial_mode = TUTORIAL_MODE_SHOW_ERROR;
       return;
     default:
       *bubble_view_mode = profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER;
   }
 }
 
-bool ShouldShowWelcomeUpgradeTutorial(
-    Profile* profile, TutorialMode tutorial_mode) {
-  const int show_count = profile->GetPrefs()->GetInteger(
-      prefs::kProfileAvatarTutorialShown);
-  // Do not show the tutorial if user has dismissed it.
-  if (show_count > signin_ui_util::kUpgradeWelcomeTutorialShowMax)
-    return false;
-
-  return tutorial_mode == TUTORIAL_MODE_WELCOME_UPGRADE ||
-         show_count != signin_ui_util::kUpgradeWelcomeTutorialShowMax;
-}
-
 }  // namespace profiles
diff --git a/chrome/browser/profiles/profile_window.h b/chrome/browser/profiles/profile_window.h
index 6f9ddaa8..ce1bc2c7 100644
--- a/chrome/browser/profiles/profile_window.h
+++ b/chrome/browser/profiles/profile_window.h
@@ -18,13 +18,6 @@
 
 namespace profiles {
 
-// Different tutorials that can be displayed in the user manager.
-enum UserManagerTutorialMode {
-  USER_MANAGER_NO_TUTORIAL,        // Does not display a tutorial.
-  USER_MANAGER_TUTORIAL_OVERVIEW,  // Basic overview of new features.
-  USER_MANAGER_TUTORIAL_LOCK,      // TODO(noms): To be implemented.
-};
-
 // Different actions to perform after the user manager selects a profile as well
 // as actions to perform when user manager window opens. The former have a
 // USER_MANAGER_SELECT_PROFILE_ prefix and the later a USER_MANAGER_OPEN_
@@ -38,7 +31,6 @@
   USER_MANAGER_SELECT_PROFILE_APP_LAUNCHER,
 };
 
-extern const char kUserManagerDisplayTutorial[];
 extern const char kUserManagerOpenCreateUserPage[];
 extern const char kUserManagerSelectProfileTaskManager[];
 extern const char kUserManagerSelectProfileAboutChrome[];
@@ -117,9 +109,7 @@
 // Returns whether lock is available to this profile.
 bool IsLockAvailable(Profile* profile);
 
-// Creates or reuses the system profile needed by the user manager. Based on
-// the value of |tutorial_mode|, the user manager can show a specific
-// tutorial, or no tutorial at all. If a tutorial is not shown, then
+// Creates or reuses the system profile needed by the user manager.
 // |profile_path_to_focus| could be used to specify which user should be
 // focused. Depending on the value of |user_manager_action|, executes an action
 // once the user manager displays or after a profile is opened. |callback| is
@@ -127,26 +117,13 @@
 // profile.
 void CreateSystemProfileForUserManager(
     const base::FilePath& profile_path_to_focus,
-    profiles::UserManagerTutorialMode tutorial_mode,
     profiles::UserManagerAction user_manager_action,
     const base::Callback<void(Profile*, const std::string&)>& callback);
 
-// Based on the |profile| preferences, determines whether a user manager
-// tutorial needs to be shown, and displays the user manager with or without
-// the tutorial.
-void ShowUserManagerMaybeWithTutorial(Profile* profile);
-
 // Converts from modes in the avatar menu to modes understood by
 // ProfileChooserView.
-void BubbleViewModeFromAvatarBubbleMode(
-    BrowserWindow::AvatarBubbleMode mode,
-    BubbleViewMode* bubble_view_mode,
-    TutorialMode* tutorial_mode);
-
-// Returns true if the Welcome/Upgrade tutorial bubble should be shown to the
-// user, false otherwise.
-bool ShouldShowWelcomeUpgradeTutorial(
-    Profile* profile, TutorialMode tutorial_mode);
+void BubbleViewModeFromAvatarBubbleMode(BrowserWindow::AvatarBubbleMode mode,
+                                        BubbleViewMode* bubble_view_mode);
 
 }  // namespace profiles
 
diff --git a/chrome/browser/profiles/profile_window_browsertest.cc b/chrome/browser/profiles/profile_window_browsertest.cc
index 099624e1..31ecb86 100644
--- a/chrome/browser/profiles/profile_window_browsertest.cc
+++ b/chrome/browser/profiles/profile_window_browsertest.cc
@@ -257,7 +257,6 @@
   base::RunLoop run_loop;
   profiles::CreateSystemProfileForUserManager(
       browser()->profile()->GetPath(),
-      profiles::USER_MANAGER_NO_TUTORIAL,
       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION,
       base::Bind(&ProfileWindowWebUIBrowserTest::OnSystemProfileCreated,
                  base::Unretained(this),
@@ -281,7 +280,6 @@
   base::RunLoop run_loop;
   profiles::CreateSystemProfileForUserManager(
       expected_path,
-      profiles::USER_MANAGER_NO_TUTORIAL,
       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION,
       base::Bind(&ProfileWindowWebUIBrowserTest::OnSystemProfileCreated,
                  base::Unretained(this),
diff --git a/chrome/browser/resources/bluetooth_internals/expandable_list.js b/chrome/browser/resources/bluetooth_internals/expandable_list.js
index 5f543e68..8f172561e 100644
--- a/chrome/browser/resources/bluetooth_internals/expandable_list.js
+++ b/chrome/browser/resources/bluetooth_internals/expandable_list.js
@@ -137,5 +137,5 @@
   return {
     ExpandableListItem: ExpandableListItem,
     ExpandableList: ExpandableList,
-  }
+  };
 });
\ No newline at end of file
diff --git a/chrome/browser/resources/bluetooth_internals/value_control.js b/chrome/browser/resources/bluetooth_internals/value_control.js
index bd5673f..9146e59 100644
--- a/chrome/browser/resources/bluetooth_internals/value_control.js
+++ b/chrome/browser/resources/bluetooth_internals/value_control.js
@@ -400,7 +400,7 @@
             'Retry', this.writeValue_.bind(this));
       }.bind(this));
     },
-  }
+  };
 
   return {
     ValueControl: ValueControl,
diff --git a/chrome/browser/resources/bookmark_manager/js/main.js b/chrome/browser/resources/bookmark_manager/js/main.js
index ad2568bd..c2689e21 100644
--- a/chrome/browser/resources/bookmark_manager/js/main.js
+++ b/chrome/browser/resources/bookmark_manager/js/main.js
@@ -883,7 +883,7 @@
  */
 function getSelectedBookmarkIds(opt_target) {
   var selectedNodes = getSelectedBookmarkNodes(opt_target);
-  selectedNodes.sort(function(a, b) { return a.index - b.index });
+  selectedNodes.sort(function(a, b) { return a.index - b.index; });
   return selectedNodes.map(function(node) {
     return node.id;
   });
diff --git a/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js b/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js
index 8957d1c2..2098b86d 100644
--- a/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js
+++ b/chrome/browser/resources/chromeos/chromevox/braille/braille_table.js
@@ -76,7 +76,7 @@
  * @return {cvox.BrailleTable.Table} The found table, or null if not found.
  */
 cvox.BrailleTable.forId = function(tables, id) {
-  return tables.filter(function(table) { return table.id === id })[0] || null;
+  return tables.filter(function(table) { return table.id === id; })[0] || null;
 };
 
 
diff --git a/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js b/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js
index 0a8bdfb..563f8e139 100644
--- a/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js
+++ b/chrome/browser/resources/chromeos/chromevox/braille/expanding_braille_translator.js
@@ -152,7 +152,7 @@
 
   function finish() {
     var totalCells = chunks.reduce(
-        function(accum, chunk) { return accum + chunk.cells.byteLength}, 0);
+        function(accum, chunk) { return accum + chunk.cells.byteLength;}, 0);
     var cells = new Uint8Array(totalCells);
     var cellPos = 0;
     var textToBraille = [];
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_suspender.js b/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_suspender.js
index 2cd7865..73b2ba4 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_suspender.js
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/injected/event_suspender.js
@@ -27,14 +27,14 @@
  */
 cvox.ChromeVoxEventSuspender.enterSuspendEvents = function() {
   cvox.ChromeVoxEventSuspender.suspendLevel_ += 1;
-}
+};
 
 /**
  * Exits a (nested) suspended state.
  */
 cvox.ChromeVoxEventSuspender.exitSuspendEvents = function() {
   cvox.ChromeVoxEventSuspender.suspendLevel_ -= 1;
-}
+};
 
 /**
  * Returns true if events are currently suspended.
diff --git a/chrome/browser/resources/chromeos/chromevox/common/media_widget.js b/chrome/browser/resources/chromeos/chromevox/common/media_widget.js
index 7b6dd05..0c2444cb 100644
--- a/chrome/browser/resources/chromeos/chromevox/common/media_widget.js
+++ b/chrome/browser/resources/chromeos/chromevox/common/media_widget.js
@@ -25,10 +25,10 @@
 
   this.keyListener_ = function(evt) {
     self.eventHandler_(evt);
-  }
+  };
   this.blurListener_ = function(evt) {
     self.shutdown();
-  }
+  };
 
   this.mediaElem_.addEventListener('keydown', this.keyListener_, false);
   this.mediaElem_.addEventListener('keyup', this.keyListener_, false);
diff --git a/chrome/browser/resources/chromeos/chromevox/common/spannable.js b/chrome/browser/resources/chromeos/chromevox/common/spannable.js
index 64c9656..a275621 100644
--- a/chrome/browser/resources/chromeos/chromevox/common/spannable.js
+++ b/chrome/browser/resources/chromeos/chromevox/common/spannable.js
@@ -428,7 +428,7 @@
 function spanInstanceOf(constructor) {
   return function(span) {
     return span.value instanceof constructor;
-  }
+  };
 }
 
 /**
@@ -438,7 +438,7 @@
 function spanCoversPosition(position) {
   return function(span) {
     return span.start <= position && position < span.end;
-  }
+  };
 }
 
 /**
@@ -448,7 +448,7 @@
 function spanValueIs(value) {
   return function(span) {
     return span.value === value;
-  }
+  };
 }
 
 /**
diff --git a/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js b/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
index 6f14d2f..a671637 100644
--- a/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
+++ b/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
@@ -40,7 +40,7 @@
     return function() {
       savedArgs.arguments = Array.prototype.slice.call(arguments);
       runAll.invoke();
-    }
+    };
   }
 };
 
diff --git a/chrome/browser/resources/chromeos/login/login_shared.js b/chrome/browser/resources/chromeos/login/login_shared.js
index 9e21431..e073b61e 100644
--- a/chrome/browser/resources/chromeos/login/login_shared.js
+++ b/chrome/browser/resources/chromeos/login/login_shared.js
@@ -474,5 +474,5 @@
   // Install a global error handler so stack traces are included in logs.
   window.onerror = function(message, file, line, column, error) {
     console.error(error.stack);
-  }
+  };
 })();
diff --git a/chrome/browser/resources/chromeos/login/md_login_shared.js b/chrome/browser/resources/chromeos/login/md_login_shared.js
index b4b78f8..41afbaa 100644
--- a/chrome/browser/resources/chromeos/login/md_login_shared.js
+++ b/chrome/browser/resources/chromeos/login/md_login_shared.js
@@ -474,5 +474,5 @@
   // Install a global error handler so stack traces are included in logs.
   window.onerror = function(message, file, line, column, error) {
     console.error(error.stack);
-  }
+  };
 })();
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index d25ad7e68..14962b8 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -239,7 +239,7 @@
         return;
 
       // Simulate click on the checkbox.
-      e.target.click()
+      e.target.click();
     },
 
     /**
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
index 2ff4fa3..183abf4 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
@@ -51,7 +51,7 @@
      */
     isCancelDisabled_: null,
 
-    get isCancelDisabled() { return this.isCancelDisabled_ },
+    get isCancelDisabled() { return this.isCancelDisabled_; },
     set isCancelDisabled(disabled) {
       this.isCancelDisabled_ = disabled;
     },
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome.js b/chrome/browser/resources/chromeos/login/oobe_welcome.js
index aeaba1a..cd1ec9f 100644
--- a/chrome/browser/resources/chromeos/login/oobe_welcome.js
+++ b/chrome/browser/resources/chromeos/login/oobe_welcome.js
@@ -139,7 +139,7 @@
   hideAllScreens_: function() {
     this.$.welcomeScreen.hidden = true;
 
-    var screens = Polymer.dom(this.root).querySelectorAll('oobe-dialog')
+    var screens = Polymer.dom(this.root).querySelectorAll('oobe-dialog');
     for (var i = 0; i < screens.length; ++i) {
       screens[i].hidden = true;
     }
@@ -164,7 +164,7 @@
    * @private
    */
   getActiveScreen_: function() {
-    var screens = Polymer.dom(this.root).querySelectorAll('oobe-dialog')
+    var screens = Polymer.dom(this.root).querySelectorAll('oobe-dialog');
     for (var i = 0; i < screens.length; ++i) {
       if (!screens[i].hidden)
         return screens[i];
diff --git a/chrome/browser/resources/chromeos/power.js b/chrome/browser/resources/chromeos/power.js
index 98c38de..41a6e710 100644
--- a/chrome/browser/resources/chromeos/power.js
+++ b/chrome/browser/resources/chromeos/power.js
@@ -836,7 +836,7 @@
       $(sectionId).hidden = true;
       $(buttonId).textContent = loadTimeData.getString('showButton');
     }
-  }
+  };
 }
 
 var powerUI = {
diff --git a/chrome/browser/resources/chromeos/switch_access/automation_predicate.js b/chrome/browser/resources/chromeos/switch_access/automation_predicate.js
index 756839f..195ebafd 100644
--- a/chrome/browser/resources/chromeos/switch_access/automation_predicate.js
+++ b/chrome/browser/resources/chromeos/switch_access/automation_predicate.js
@@ -122,4 +122,4 @@
 
   // The general rule that applies to everything.
   return state[chrome.automation.StateType.FOCUSABLE] === true;
-}
+};
diff --git a/chrome/browser/resources/cleanup_tool/cleanup_browser_proxy.js b/chrome/browser/resources/cleanup_tool/cleanup_browser_proxy.js
index ef22211..e9c463d 100644
--- a/chrome/browser/resources/cleanup_tool/cleanup_browser_proxy.js
+++ b/chrome/browser/resources/cleanup_tool/cleanup_browser_proxy.js
@@ -74,5 +74,5 @@
   return {
     CleanupBrowserProxy: CleanupBrowserProxy,
     CleanupBrowserProxyImpl: CleanupBrowserProxyImpl,
-  }
+  };
 });
diff --git a/chrome/browser/resources/cryptotoken/gnubbies.js b/chrome/browser/resources/cryptotoken/gnubbies.js
index 8424b39..cbb92cb 100644
--- a/chrome/browser/resources/cryptotoken/gnubbies.js
+++ b/chrome/browser/resources/cryptotoken/gnubbies.js
@@ -213,7 +213,7 @@
   function makeEnumerateCb(namespace) {
     return function(devs) {
       enumerated(namespace, deviceIds, devs);
-    }
+    };
   }
 
   this.pendingEnumerate.push(cb);
diff --git a/chrome/browser/resources/inspect/inspect.js b/chrome/browser/resources/inspect/inspect.js
index c770ab9..512552b7 100644
--- a/chrome/browser/resources/inspect/inspect.js
+++ b/chrome/browser/resources/inspect/inspect.js
@@ -225,7 +225,7 @@
       section.remove();
   }
 
-  var newDeviceIds = devices.map(function(d) { return d.id });
+  var newDeviceIds = devices.map(function(d) { return d.id; });
   Array.prototype.forEach.call(
       deviceList.querySelectorAll('.device'),
       removeObsolete.bind(null, newDeviceIds));
@@ -282,7 +282,7 @@
 
     var browserList = deviceSection.querySelector('.browsers');
     var newBrowserIds =
-        device.browsers.map(function(b) { return b.id });
+        device.browsers.map(function(b) { return b.id; });
     Array.prototype.forEach.call(
         browserList.querySelectorAll('.browser'),
         removeObsolete.bind(null, newBrowserIds));
diff --git a/chrome/browser/resources/md_bookmarks/dnd_manager.js b/chrome/browser/resources/md_bookmarks/dnd_manager.js
index c5c3c7e..a4423b3 100644
--- a/chrome/browser/resources/md_bookmarks/dnd_manager.js
+++ b/chrome/browser/resources/md_bookmarks/dnd_manager.js
@@ -626,7 +626,7 @@
       if (getBookmarkNode(overElement).url)
         return false;
 
-      return !this.dragInfo_.isDraggingChildBookmark(overElement.itemId)
+      return !this.dragInfo_.isDraggingChildBookmark(overElement.itemId);
     },
 
     disableTimeoutsForTesting: function() {
diff --git a/chrome/browser/resources/net_internals/modules_view.js b/chrome/browser/resources/net_internals/modules_view.js
index dbb0b9c..f02a53d 100644
--- a/chrome/browser/resources/net_internals/modules_view.js
+++ b/chrome/browser/resources/net_internals/modules_view.js
@@ -107,7 +107,7 @@
   ModulesView.getLayeredServiceProviderProtocolType =
       function(serviceProvider) {
     return tryGetValueWithKey(PROTOCOL_TYPE, serviceProvider.socket_protocol);
-  }
+  };
 
   var NAMESPACE_PROVIDER_PTYPE = {
     '12': 'NS_DNS',
diff --git a/chrome/browser/resources/sync_file_system_internals/dump_database.js b/chrome/browser/resources/sync_file_system_internals/dump_database.js
index 58025bc..1f0b9f27 100644
--- a/chrome/browser/resources/sync_file_system_internals/dump_database.js
+++ b/chrome/browser/resources/sync_file_system_internals/dump_database.js
@@ -78,7 +78,7 @@
     div.appendChild(table);
     placeholder.appendChild(div);
   }
-}
+};
 
 function main() {
   getDatabaseDump();
diff --git a/chrome/browser/resources/sync_file_system_internals/extension_statuses.js b/chrome/browser/resources/sync_file_system_internals/extension_statuses.js
index a31de23..0b805ae 100644
--- a/chrome/browser/resources/sync_file_system_internals/extension_statuses.js
+++ b/chrome/browser/resources/sync_file_system_internals/extension_statuses.js
@@ -48,7 +48,7 @@
     tr.appendChild(createElementFromText('td', originEntry.status));
     itemContainer.appendChild(tr);
   }
-}
+};
 
 function main() {
   getExtensionStatuses();
diff --git a/chrome/browser/resources/sync_file_system_internals/sync_service.js b/chrome/browser/resources/sync_file_system_internals/sync_service.js
index b5a2578..299abc57 100644
--- a/chrome/browser/resources/sync_file_system_internals/sync_service.js
+++ b/chrome/browser/resources/sync_file_system_internals/sync_service.js
@@ -23,7 +23,7 @@
  */
 SyncService.onGetServiceStatus = function(statusString) {
   $('service-status').textContent = statusString;
-}
+};
 
 /**
  * Request Google Drive Notification Source. e.g. XMPP or polling.
@@ -38,7 +38,7 @@
  */
 SyncService.onGetNotificationSource = function(sourceString) {
   $('notification-source').textContent = sourceString;
-}
+};
 
 // Keeps track of the last log event seen so it's not reprinted.
 var lastLogEventId = -1;
@@ -76,7 +76,7 @@
 
     lastLogEventId = logEntry.id;
   }
-}
+};
 
 /**
  * Get initial sync service values and set listeners to get updated values.
diff --git a/chrome/browser/resources/sync_file_system_internals/task_log.js b/chrome/browser/resources/sync_file_system_internals/task_log.js
index 0cc24db3..5379754 100644
--- a/chrome/browser/resources/sync_file_system_internals/task_log.js
+++ b/chrome/browser/resources/sync_file_system_internals/task_log.js
@@ -44,7 +44,7 @@
   tr.appendChild(details);
 
   $('task-log-entries').appendChild(tr);
-}
+};
 
 /**
  * Get initial sync service values and set listeners to get updated values.
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index ccf25e0..77e10c2 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -474,7 +474,7 @@
 
 void ChromeSigninClient::ShowUserManager(const base::FilePath& profile_path) {
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
-  UserManager::Show(profile_path, profiles::USER_MANAGER_NO_TUTORIAL,
+  UserManager::Show(profile_path,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
 #endif
 }
diff --git a/chrome/browser/ui/app_list/app_list_service_disabled.cc b/chrome/browser/ui/app_list/app_list_service_disabled.cc
index a2ffa9a..9090317 100644
--- a/chrome/browser/ui/app_list/app_list_service_disabled.cc
+++ b/chrome/browser/ui/app_list/app_list_service_disabled.cc
@@ -97,7 +97,7 @@
   if (IsProfileSignedOut(app_list_profile) ||
       app_list_profile->IsSystemProfile() ||
       app_list_profile->IsGuestSession()) {
-    UserManager::Show(base::FilePath(), profiles::USER_MANAGER_NO_TUTORIAL,
+    UserManager::Show(base::FilePath(),
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
     return;
   }
diff --git a/chrome/browser/ui/app_list/profile_loader.cc b/chrome/browser/ui/app_list/profile_loader.cc
index be09089c..21d6bf408 100644
--- a/chrome/browser/ui/app_list/profile_loader.cc
+++ b/chrome/browser/ui/app_list/profile_loader.cc
@@ -36,7 +36,6 @@
 
   if (profile_store_->IsProfileLocked(profile_file_path)) {
       UserManager::Show(base::FilePath(),
-                        profiles::USER_MANAGER_NO_TUTORIAL,
                         profiles::USER_MANAGER_SELECT_PROFILE_APP_LAUNCHER);
       return;
   }
diff --git a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
index d9ba2e56..a0f9487b 100644
--- a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
+++ b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
@@ -21,6 +21,7 @@
 #include "ui/app_list/app_list_features.h"
 #include "ui/app_list/app_list_model.h"
 #include "ui/app_list/search_box_model.h"
+#include "ui/views/controls/webview/web_contents_set_background_color.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/widget/widget.h"
 
@@ -105,6 +106,11 @@
     web_view_->SetFocusBehavior(views::View::FocusBehavior::NEVER);
 
   model->AddObserver(this);
+
+  // Make the webview transparent since it's going to be shown on top of a
+  // highlightable button.
+  views::WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
+      web_contents_.get(), SK_ColorTRANSPARENT);
 }
 
 SearchAnswerWebContentsDelegate::~SearchAnswerWebContentsDelegate() {
@@ -168,8 +174,7 @@
       IsCardSizeOk(pref_size) || features::IsAnswerCardDarkRunEnabled();
   model_->SetSearchAnswerAvailable(is_card_size_ok_ && received_answer_ &&
                                    !web_contents_->IsLoading());
-  if (!features::IsAnswerCardDarkRunEnabled())
-    web_view_->SetPreferredSize(pref_size);
+  web_view_->SetPreferredSize(pref_size);
   if (!answer_loaded_time_.is_null()) {
     UMA_HISTOGRAM_TIMES("SearchAnswer.ResizeAfterLoadTime",
                         base::TimeTicks::Now() - answer_loaded_time_);
diff --git a/chrome/browser/ui/ash/shelf_browsertest.cc b/chrome/browser/ui/ash/shelf_browsertest.cc
index 6a72221c..022c90b 100644
--- a/chrome/browser/ui/ash/shelf_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf_browsertest.cc
@@ -4,7 +4,6 @@
 
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
-#include "ash/wm_window.h"
 #include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/browser.h"
diff --git a/chrome/browser/ui/cocoa/browser_window_cocoa.mm b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
index cb61d3a..0416c62d 100644
--- a/chrome/browser/ui/cocoa/browser_window_cocoa.mm
+++ b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
@@ -821,9 +821,7 @@
     signin_metrics::AccessPoint access_point,
     bool is_source_keyboard) {
   profiles::BubbleViewMode bubble_view_mode;
-  profiles::TutorialMode tutorial_mode;
-  profiles::BubbleViewModeFromAvatarBubbleMode(mode, &bubble_view_mode,
-                                               &tutorial_mode);
+  profiles::BubbleViewModeFromAvatarBubbleMode(mode, &bubble_view_mode);
 
   if (SigninViewController::ShouldShowModalSigninForMode(bubble_view_mode)) {
     browser_->signin_view_controller()->ShowModalSignin(bubble_view_mode,
diff --git a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_browsertest_mac.mm b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_browsertest_mac.mm
index 94457df..b8a3d02 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_browsertest_mac.mm
+++ b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_browsertest_mac.mm
@@ -105,17 +105,6 @@
   DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleBrowserTestMac);
 };
 
-class ExtensionMessageBubbleBrowserTestLegacyMac
-    : public ExtensionMessageBubbleBrowserTestMac {
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    ExtensionMessageBubbleBrowserTestMac::SetUpCommandLine(command_line);
-    override_redesign_.reset();
-    override_redesign_.reset(new extensions::FeatureSwitch::ScopedOverride(
-        extensions::FeatureSwitch::extension_action_redesign(), false));
-  }
-};
-
 void ExtensionMessageBubbleBrowserTestMac::SetUpCommandLine(
     base::CommandLine* command_line) {
   ExtensionMessageBubbleBrowserTest::SetUpCommandLine(command_line);
@@ -180,12 +169,12 @@
   TestBubbleAnchoredToExtensionAction();
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleBrowserTestLegacyMac,
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleBrowserTestMac,
                        ExtensionBubbleAnchoredToAppMenu) {
   TestBubbleAnchoredToAppMenu();
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleBrowserTestLegacyMac,
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleBrowserTestMac,
                        ExtensionBubbleAnchoredToAppMenuWithOtherAction) {
   TestBubbleAnchoredToAppMenuWithOtherAction();
 }
diff --git a/chrome/browser/ui/cocoa/profiles/avatar_base_controller.mm b/chrome/browser/ui/cocoa/profiles/avatar_base_controller.mm
index a3e0d5a1..190db7a 100644
--- a/chrome/browser/ui/cocoa/profiles/avatar_base_controller.mm
+++ b/chrome/browser/ui/cocoa/profiles/avatar_base_controller.mm
@@ -133,14 +133,11 @@
                    fromAccessPoint:(signin_metrics::AccessPoint)accessPoint {
   if (menuController_) {
     profiles::BubbleViewMode viewMode;
-    profiles::TutorialMode tutorialMode;
-    profiles::BubbleViewModeFromAvatarBubbleMode(
-        mode, &viewMode, &tutorialMode);
-    if (tutorialMode != profiles::TUTORIAL_MODE_NONE) {
+    profiles::BubbleViewModeFromAvatarBubbleMode(mode, &viewMode);
+    if (viewMode == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) {
       ProfileChooserController* profileChooserController =
           base::mac::ObjCCastStrict<ProfileChooserController>(
               menuController_);
-      [profileChooserController setTutorialMode:tutorialMode];
       [profileChooserController initMenuContentsWithView:viewMode];
     }
     return;
@@ -168,15 +165,12 @@
 
   // |menuController_| will automatically release itself on close.
   profiles::BubbleViewMode viewMode;
-  profiles::TutorialMode tutorialMode;
-  profiles::BubbleViewModeFromAvatarBubbleMode(
-      mode, &viewMode, &tutorialMode);
+  profiles::BubbleViewModeFromAvatarBubbleMode(mode, &viewMode);
 
   menuController_ =
       [[ProfileChooserController alloc] initWithBrowser:browser_
                                              anchoredAt:point
                                                viewMode:viewMode
-                                           tutorialMode:tutorialMode
                                             serviceType:serviceType
                                             accessPoint:accessPoint];
 
diff --git a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h
index c706514..623fdce 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h
+++ b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.h
@@ -53,9 +53,6 @@
   // Active view mode.
   profiles::BubbleViewMode viewMode_;
 
-  // The current tutorial mode.
-  profiles::TutorialMode tutorialMode_;
-
   // List of the full, un-elided accounts for the active profile. The keys are
   // generated used to tag the UI buttons, and the values are the original
   // emails displayed by the buttons.
@@ -78,7 +75,6 @@
 - (id)initWithBrowser:(Browser*)browser
            anchoredAt:(NSPoint)point
              viewMode:(profiles::BubbleViewMode)viewMode
-         tutorialMode:(profiles::TutorialMode)tutorialMode
           serviceType:(signin::GAIAServiceType)GAIAServiceType
           accessPoint:(signin_metrics::AccessPoint)accessPoint;
 
@@ -88,9 +84,6 @@
 // Returns the view currently displayed by the bubble.
 - (profiles::BubbleViewMode)viewMode;
 
-// Sets the tutorial mode of the bubble.
-- (void)setTutorialMode:(profiles::TutorialMode)tutorialMode;
-
 // Switches to a given profile. |sender| is an ProfileChooserItemController.
 - (IBAction)switchToProfile:(id)sender;
 
@@ -139,9 +132,7 @@
 - (id)initWithBrowser:(Browser*)browser
            anchoredAt:(NSPoint)point
              viewMode:(profiles::BubbleViewMode)viewMode
-         tutorialMode:(profiles::TutorialMode)tutorialMode
           serviceType:(signin::GAIAServiceType)GAIAServiceType;
-- (IBAction)dismissTutorial:(id)sender;
 @end
 
 #endif  // CHROME_BROWSER_UI_COCOA_PROFILES_PROFILE_CHOOSER_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
index 4c54c885..462bca4 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
+++ b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm
@@ -676,7 +676,6 @@
 
 // Builds the regular profile chooser view.
 - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView
-                                  tutorialView:(NSView*)tutorialView
                                  syncErrorView:(NSView*)syncErrorView
                                  otherProfiles:(NSArray*)otherProfiles
                                      atYOffset:(CGFloat)yOffset
@@ -686,38 +685,9 @@
 // Builds the profile chooser view.
 - (NSView*)buildProfileChooserView;
 
-// Builds a tutorial card with a title label using |titleMessage|, a content
-// label using |contentMessage|, a link using |linkMessage|, and a button using
-// |buttonMessage|. If |stackButton| is YES, places the button above the link.
-// Otherwise places both on the same row with the link left aligned and button
-// right aligned. On click, the link would execute |linkAction|, and the button
-// would execute |buttonAction|. It sets |tutorialMode_| to the given |mode|.
-- (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
-                   titleMessage:(NSString*)titleMessage
-                 contentMessage:(NSString*)contentMessage
-                    linkMessage:(NSString*)linkMessage
-                  buttonMessage:(NSString*)buttonMessage
-                    stackButton:(BOOL)stackButton
-                 hasCloseButton:(BOOL)hasCloseButton
-                     linkAction:(SEL)linkAction
-                   buttonAction:(SEL)buttonAction;
-
 // Builds a header for signin and sync error surfacing on the user menu.
 - (NSView*)buildSyncErrorViewIfNeeded;
 
-// Builds a tutorial card to introduce an upgrade user to the new avatar menu if
-// needed. |tutorial_shown| indicates if the tutorial has already been shown in
-// the previous active view. |avatar_item| refers to the current profile.
-- (NSView*)buildWelcomeUpgradeTutorialView:(const AvatarMenu::Item&)item;
-
-// Builds a tutorial card to have the user confirm the last Chrome signin,
-// Chrome sync will be delayed until the user either dismisses the tutorial, or
-// configures sync through the "Settings" link.
-- (NSView*)buildSigninConfirmationView;
-
-// Builds a tutorial card to show the last signin error.
-- (NSView*)buildSigninErrorView;
-
 // Creates the main profile card for the profile |item| at the top of
 // the bubble.
 - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item;
@@ -793,10 +763,6 @@
   return viewMode_;
 }
 
-- (void)setTutorialMode:(profiles::TutorialMode)tutorialMode {
-  tutorialMode_ = tutorialMode;
-}
-
 - (IBAction)editProfile:(id)sender {
   avatarMenu_->EditProfile(avatarMenu_->GetActiveProfileIndex());
   [self postActionPerformed:ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE];
@@ -821,7 +787,6 @@
 
 - (IBAction)showUserManager:(id)sender {
   UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   [self postActionPerformed:
       ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER];
@@ -830,7 +795,6 @@
 - (IBAction)exitGuest:(id)sender {
   DCHECK(browser_->profile()->IsGuestSession());
   UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   profiles::CloseGuestProfileWindows();
 }
@@ -941,14 +905,6 @@
   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT];
 }
 
-- (IBAction)seeWhatsNew:(id)sender {
-  UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_TUTORIAL_OVERVIEW,
-                    profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
-  ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
-      ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
-}
-
 - (IBAction)showSwitchUserView:(id)sender {
   [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_SWITCH_USER];
   ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
@@ -960,7 +916,6 @@
 }
 
 - (IBAction)configureSyncSettings:(id)sender {
-  tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
   LoginUIServiceFactory::GetForProfile(browser_->profile())->
       SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
   ProfileMetrics::LogProfileNewAvatarMenuSignin(
@@ -968,7 +923,6 @@
 }
 
 - (IBAction)syncSettingsConfirmed:(id)sender {
-  tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
   LoginUIServiceFactory::GetForProfile(browser_->profile())->
       SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
   ProfileMetrics::LogProfileNewAvatarMenuSignin(
@@ -988,24 +942,7 @@
       ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_BACK);
 }
 
-- (IBAction)dismissTutorial:(id)sender {
-  // Never shows the upgrade tutorial again if manually closed.
-  if (tutorialMode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
-    browser_->profile()->GetPrefs()->SetInteger(
-        prefs::kProfileAvatarTutorialShown,
-        signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
-  }
-
-  tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
-  [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER];
-}
-
 - (void)windowWillClose:(NSNotification*)notification {
-  if (tutorialMode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
-    LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
-  }
-
   [super windowWillClose:notification];
 }
 
@@ -1025,7 +962,6 @@
 - (id)initWithBrowser:(Browser*)browser
            anchoredAt:(NSPoint)point
              viewMode:(profiles::BubbleViewMode)viewMode
-         tutorialMode:(profiles::TutorialMode)tutorialMode
           serviceType:(signin::GAIAServiceType)serviceType
           accessPoint:(signin_metrics::AccessPoint)accessPoint {
   base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
@@ -1039,7 +975,6 @@
                          anchoredAt:point])) {
     browser_ = browser;
     viewMode_ = viewMode;
-    tutorialMode_ = tutorialMode;
     observer_.reset(new ActiveProfileObserverBridge(self, browser_));
     serviceType_ = serviceType;
     accessPoint_ = accessPoint;
@@ -1114,10 +1049,6 @@
       break;
   }
 
-  // Clears tutorial mode for all non-profile-chooser views.
-  if (viewMode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
-    tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
-
   // Add a dummy, empty element so that we don't initially display any
   // focus rings.
   NSButton* dummyFocusButton =
@@ -1158,7 +1089,6 @@
 }
 
 - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView
-                                  tutorialView:(NSView*)tutorialView
                                  syncErrorView:(NSView*)syncErrorView
                                  otherProfiles:(NSArray*)otherProfiles
                                      atYOffset:(CGFloat)yOffset
@@ -1229,15 +1159,6 @@
     yOffset = NSMaxY([syncErrorView frame]);
   }
 
-  if (tutorialView) {
-    [tutorialView setFrameOrigin:NSMakePoint(0, yOffset)];
-    [container addSubview:tutorialView];
-    yOffset = NSMaxY([tutorialView frame]);
-    //TODO(mlerman): update UMA stats for the new tutorials.
-  } else {
-    tutorialMode_ = profiles::TUTORIAL_MODE_NONE;
-  }
-
   [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
 }
 
@@ -1245,7 +1166,6 @@
   base::scoped_nsobject<NSView> container(
       [[NSView alloc] initWithFrame:NSZeroRect]);
 
-  NSView* tutorialView = nil;
   NSView* syncErrorView = nil;
   NSView* currentProfileView = nil;
   base::scoped_nsobject<NSMutableArray> otherProfiles(
@@ -1276,7 +1196,6 @@
   CGFloat yOffset = 1;
 
   [self buildProfileChooserViewWithProfileView:currentProfileView
-                                  tutorialView:tutorialView
                                  syncErrorView:syncErrorView
                                  otherProfiles:otherProfiles.get()
                                      atYOffset:yOffset
@@ -1285,216 +1204,6 @@
   return container.autorelease();
 }
 
-- (NSView*)buildSigninConfirmationView {
-  ProfileMetrics::LogProfileNewAvatarMenuSignin(
-      ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
-
-  NSString* titleMessage = l10n_util::GetNSString(
-      IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE);
-  NSString* contentMessage = l10n_util::GetNSString(
-      IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT);
-  NSString* linkMessage = l10n_util::GetNSString(
-      IDS_PROFILES_SYNC_SETTINGS_LINK);
-  NSString* buttonMessage = l10n_util::GetNSString(
-      IDS_PROFILES_TUTORIAL_OK_BUTTON);
-  return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
-                       titleMessage:titleMessage
-                     contentMessage:contentMessage
-                        linkMessage:linkMessage
-                      buttonMessage:buttonMessage
-                        stackButton:NO
-                     hasCloseButton:NO
-                         linkAction:@selector(configureSyncSettings:)
-                       buttonAction:@selector(syncSettingsConfirmed:)];
-}
-
-- (NSView*)buildSigninErrorView {
-  NSString* titleMessage = l10n_util::GetNSString(
-      IDS_PROFILES_ERROR_TUTORIAL_TITLE);
-  LoginUIService* loginUiService =
-      LoginUIServiceFactory::GetForProfile(browser_->profile());
-  NSString* contentMessage =
-      base::SysUTF16ToNSString(loginUiService->GetLastLoginResult());
-  NSString* linkMessage = l10n_util::GetNSString(
-      IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE);
-  return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_CONFIRM_SIGNIN
-                       titleMessage:titleMessage
-                     contentMessage:contentMessage
-                        linkMessage:linkMessage
-                      buttonMessage:nil
-                        stackButton:NO
-                     hasCloseButton:YES
-                         linkAction:@selector(showLearnMorePage:)
-                       buttonAction:nil];
-}
-
-- (NSView*)buildWelcomeUpgradeTutorialView:(const AvatarMenu::Item&)item {
-  ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
-      ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
-
-  NSString* titleMessage = l10n_util::GetNSString(
-      IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE);
-  NSString* contentMessage = l10n_util::GetNSString(
-      IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT);
-  // For local profiles, the "Not you" link doesn't make sense.
-  NSString* linkMessage =
-      item.signed_in ? ElideMessage(l10n_util::GetStringFUTF16(
-                                        IDS_PROFILES_NOT_YOU, item.name),
-                                    kFixedMenuWidth - 2 * kHorizontalSpacing)
-                     : nil;
-  NSString* buttonMessage = l10n_util::GetNSString(
-      IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON);
-  return [self tutorialViewWithMode:profiles::TUTORIAL_MODE_WELCOME_UPGRADE
-                       titleMessage:titleMessage
-                     contentMessage:contentMessage
-                        linkMessage:linkMessage
-                      buttonMessage:buttonMessage
-                        stackButton:YES
-                     hasCloseButton:YES
-                         linkAction:@selector(showSwitchUserView:)
-                       buttonAction:@selector(seeWhatsNew:)];
-}
-
-- (NSView*)tutorialViewWithMode:(profiles::TutorialMode)mode
-                   titleMessage:(NSString*)titleMessage
-                 contentMessage:(NSString*)contentMessage
-                    linkMessage:(NSString*)linkMessage
-                  buttonMessage:(NSString*)buttonMessage
-                    stackButton:(BOOL)stackButton
-                 hasCloseButton:(BOOL)hasCloseButton
-                     linkAction:(SEL)linkAction
-                   buttonAction:(SEL)buttonAction {
-  tutorialMode_ = mode;
-
-  NSColor* tutorialBackgroundColor =
-      skia::SkColorToSRGBNSColor(profiles::kAvatarTutorialBackgroundColor);
-  base::scoped_nsobject<NSView> container([[BackgroundColorView alloc]
-      initWithFrame:NSMakeRect(0, 0, kFixedMenuWidth, 0)
-          withColor:tutorialBackgroundColor]);
-  CGFloat availableWidth = kFixedMenuWidth - 2 * kHorizontalSpacing;
-  CGFloat yOffset = kVerticalSpacing;
-
-  // Adds links and buttons at the bottom.
-  base::scoped_nsobject<NSButton> tutorialOkButton;
-  if (buttonMessage) {
-    tutorialOkButton.reset([[HoverButton alloc] initWithFrame:NSZeroRect]);
-    [tutorialOkButton setTitle:buttonMessage];
-    [tutorialOkButton setBezelStyle:NSRoundedBezelStyle];
-    [tutorialOkButton setTarget:self];
-    [tutorialOkButton setAction:buttonAction];
-    [tutorialOkButton setAlignment:NSCenterTextAlignment];
-    [tutorialOkButton sizeToFit];
-  }
-
-  NSButton* learnMoreLink = nil;
-  if (linkMessage) {
-    learnMoreLink = [self linkButtonWithTitle:linkMessage
-                                  frameOrigin:NSZeroPoint
-                                       action:linkAction];
-    [[learnMoreLink cell] setTextColor:[NSColor whiteColor]];
-  }
-
-  if (stackButton) {
-    [learnMoreLink setFrameOrigin:NSMakePoint((kFixedMenuWidth -
-                                               NSWidth([learnMoreLink frame])) /
-                                                  2,
-                                              yOffset)];
-    [tutorialOkButton setFrameSize:NSMakeSize(
-        availableWidth, NSHeight([tutorialOkButton frame]))];
-    [tutorialOkButton setFrameOrigin:NSMakePoint(
-        kHorizontalSpacing,
-        yOffset + (learnMoreLink ? NSHeight([learnMoreLink frame]) : 0))];
-  } else {
-    if (buttonMessage) {
-      NSSize buttonSize = [tutorialOkButton frame].size;
-      const CGFloat kTopBottomTextPadding = 6;
-      const CGFloat kLeftRightTextPadding = 15;
-      buttonSize.width += 2 * kLeftRightTextPadding;
-      buttonSize.height += 2 * kTopBottomTextPadding;
-      [tutorialOkButton setFrameSize:buttonSize];
-      CGFloat buttonXOffset = kFixedMenuWidth -
-                              NSWidth([tutorialOkButton frame]) -
-                              kHorizontalSpacing;
-      [tutorialOkButton setFrameOrigin:NSMakePoint(buttonXOffset, yOffset)];
-    }
-
-    if (linkMessage) {
-      CGFloat linkYOffset = yOffset;
-      if (buttonMessage) {
-        linkYOffset += (NSHeight([tutorialOkButton frame]) -
-                        NSHeight([learnMoreLink frame])) / 2;
-      }
-      [learnMoreLink setFrameOrigin:NSMakePoint(
-          kHorizontalSpacing, linkYOffset)];
-    }
-  }
-
-  if (buttonMessage) {
-    [container addSubview:tutorialOkButton];
-    yOffset = NSMaxY([tutorialOkButton frame]);
-  }
-
-  if (linkMessage) {
-    [container addSubview:learnMoreLink];
-    yOffset = std::max(NSMaxY([learnMoreLink frame]), yOffset);
-  }
-
-  yOffset += kVerticalSpacing;
-
-  // Adds body content.
-  NSTextField* contentLabel = BuildLabel(
-      contentMessage,
-      NSMakePoint(kHorizontalSpacing, yOffset),
-      skia::SkColorToSRGBNSColor(profiles::kAvatarTutorialContentTextColor));
-  [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)];
-  [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel];
-  [container addSubview:contentLabel];
-  yOffset = NSMaxY([contentLabel frame]) + kSmallVerticalSpacing;
-
-  // Adds title.
-  NSTextField* titleLabel =
-      BuildLabel(titleMessage,
-                 NSMakePoint(kHorizontalSpacing, yOffset),
-                 [NSColor whiteColor] /* text_color */);
-  [titleLabel setFont:[NSFont labelFontOfSize:kTitleFontSize]];
-
-  if (hasCloseButton) {
-    base::scoped_nsobject<HoverImageButton> closeButton(
-        [[HoverImageButton alloc] initWithFrame:NSZeroRect]);
-    [closeButton setBordered:NO];
-
-    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
-    NSImage* closeImage = rb->GetNativeImageNamed(IDR_CLOSE_1).ToNSImage();
-    CGFloat closeImageWidth = [closeImage size].width;
-    [closeButton setDefaultImage:closeImage];
-    [closeButton setHoverImage:
-        rb->GetNativeImageNamed(IDR_CLOSE_1_H).ToNSImage()];
-    [closeButton setPressedImage:
-        rb->GetNativeImageNamed(IDR_CLOSE_1_P).ToNSImage()];
-    [closeButton setTarget:self];
-    [closeButton setAction:@selector(dismissTutorial:)];
-    [closeButton setFrameSize:[closeImage size]];
-    [closeButton
-        setFrameOrigin:NSMakePoint(kFixedMenuWidth - kHorizontalSpacing -
-                                       closeImageWidth,
-                                   yOffset)];
-    [container addSubview:closeButton];
-
-    [titleLabel setFrameSize:NSMakeSize(
-        availableWidth - closeImageWidth - kHorizontalSpacing, 0)];
-  } else {
-    [titleLabel setFrameSize:NSMakeSize(availableWidth, 0)];
-  }
-
-  [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:titleLabel];
-  [container addSubview:titleLabel];
-  yOffset = NSMaxY([titleLabel frame]) + kVerticalSpacing;
-
-  [container setFrameSize:NSMakeSize(kFixedMenuWidth, yOffset)];
-  [container setFrameOrigin:NSZeroPoint];
-  return container.autorelease();
-}
-
 - (NSView*)buildSyncErrorViewIfNeeded {
   int contentStringId, buttonStringId;
   SEL buttonAction;
diff --git a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller_unittest.mm b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller_unittest.mm
index 5ec616b..0f74dcee 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller_unittest.mm
@@ -89,18 +89,12 @@
   }
 
   void StartProfileChooserController() {
-    StartProfileChooserControllerWithTutorialMode(profiles::TUTORIAL_MODE_NONE);
-  }
-
-  void StartProfileChooserControllerWithTutorialMode(
-      profiles::TutorialMode mode) {
     NSRect frame = [test_window() frame];
     NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
     controller_.reset([[ProfileChooserController alloc]
         initWithBrowser:browser()
              anchoredAt:point
                viewMode:profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER
-           tutorialMode:mode
             serviceType:signin::GAIA_SERVICE_TYPE_NONE
             accessPoint:signin_metrics::AccessPoint::
                             ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN]);
@@ -194,29 +188,6 @@
   [controller() close];
 }
 
-TEST_F(ProfileChooserControllerTest, RightClickTutorialShownAfterWelcome) {
-  // The welcome upgrade tutorial takes precedence so show it then dismiss it.
-  // The right click tutorial should be shown right away.
-  StartProfileChooserControllerWithTutorialMode(
-      profiles::TUTORIAL_MODE_WELCOME_UPGRADE);
-
-  [controller() dismissTutorial:nil];
-}
-
-TEST_F(ProfileChooserControllerTest, RightClickTutorialShownAfterReopen) {
-  // The welcome upgrade tutorial takes precedence so show it then close the
-  // menu. Reopening the menu should show the tutorial.
-  StartProfileChooserController();
-
-  [controller() close];
-  StartProfileChooserController();
-
-  // The tutorial must be manually dismissed so it should still be shown after
-  // closing and reopening the menu,
-  [controller() close];
-  StartProfileChooserController();
-}
-
 TEST_F(ProfileChooserControllerTest,
     LocalProfileActiveCardLinksWithNewMenu) {
   StartProfileChooserController();
diff --git a/chrome/browser/ui/cocoa/profiles/user_manager_mac.mm b/chrome/browser/ui/cocoa/profiles/user_manager_mac.mm
index b329ac1..0116bd6 100644
--- a/chrome/browser/ui/cocoa/profiles/user_manager_mac.mm
+++ b/chrome/browser/ui/cocoa/profiles/user_manager_mac.mm
@@ -413,7 +413,6 @@
 // static
 void UserManager::Show(
     const base::FilePath& profile_path_to_focus,
-    profiles::UserManagerTutorialMode tutorial_mode,
     profiles::UserManagerAction user_manager_action) {
   DCHECK(profile_path_to_focus != ProfileManager::GetGuestProfilePath());
 
@@ -437,7 +436,6 @@
   // from the guest profile.
   profiles::CreateSystemProfileForUserManager(
       profile_path_to_focus,
-      tutorial_mode,
       user_manager_action,
       base::Bind(&UserManagerMac::OnSystemProfileCreated, base::Time::Now()));
 }
diff --git a/chrome/browser/ui/cocoa/profiles/user_manager_mac_unittest.mm b/chrome/browser/ui/cocoa/profiles/user_manager_mac_unittest.mm
index f2dbbf4a..4cc3abee 100644
--- a/chrome/browser/ui/cocoa/profiles/user_manager_mac_unittest.mm
+++ b/chrome/browser/ui/cocoa/profiles/user_manager_mac_unittest.mm
@@ -57,7 +57,6 @@
 
   EXPECT_FALSE(UserManager::IsShowing());
   UserManager::Show(base::FilePath(),
-                    profiles::USER_MANAGER_NO_TUTORIAL,
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   EXPECT_TRUE(UserManager::IsShowing());
 
diff --git a/chrome/browser/ui/extensions/extension_enable_flow.cc b/chrome/browser/ui/extensions/extension_enable_flow.cc
index 9af3285..19b9dff 100644
--- a/chrome/browser/ui/extensions/extension_enable_flow.cc
+++ b/chrome/browser/ui/extensions/extension_enable_flow.cc
@@ -99,7 +99,6 @@
 
   if (profiles::IsProfileLocked(profile_->GetPath())) {
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_APP_LAUNCHER);
     return;
   }
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_bridge.cc b/chrome/browser/ui/extensions/extension_message_bubble_bridge.cc
index 32e335a..a60d365 100644
--- a/chrome/browser/ui/extensions/extension_message_bubble_bridge.cc
+++ b/chrome/browser/ui/extensions/extension_message_bubble_bridge.cc
@@ -52,10 +52,13 @@
 base::string16 ExtensionMessageBubbleBridge::GetActionButtonText() {
   const extensions::ExtensionIdList& list = controller_->GetExtensionIdList();
   DCHECK(!list.empty());
+  // Normally, the extension is enabled, but this might not be the case (such as
+  // for the SuspiciousExtensionBubbleDelegate, which warns the user about
+  // disabled extensions).
   const extensions::Extension* extension =
       extensions::ExtensionRegistry::Get(controller_->profile())
-          ->enabled_extensions()
-          .GetByID(list[0]);
+          ->GetExtensionById(list[0],
+                             extensions::ExtensionRegistry::EVERYTHING);
 
   DCHECK(extension);
   // An empty string is returned so that we don't display the button prompting
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc b/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
index 99d3ae1..f2557f4 100644
--- a/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
+++ b/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
@@ -122,8 +122,14 @@
       extensions::extension_action_test_util::CreateActionExtension(
           "no_action_extension",
           extensions::extension_action_test_util::NO_ACTION,
-          extensions::Manifest::UNPACKED);
+          extensions::Manifest::INTERNAL);
   extension_service()->AddExtension(no_action_extension.get());
+  // The 'suspicious extension' bubble warns the user about extensions that are
+  // disabled for not being from the webstore. This is one of the few bubbles
+  // that lets us test anchoring to the app menu, since we usually anchor to the
+  // extension action now that every extension is given a permanent UI presence.
+  extension_service()->DisableExtension(
+      no_action_extension->id(), extensions::Extension::DISABLE_NOT_VERIFIED);
   Browser* second_browser = new Browser(Browser::CreateParams(profile(), true));
   ASSERT_TRUE(second_browser);
   second_browser->window()->Show();
@@ -138,7 +144,7 @@
       extensions::extension_action_test_util::CreateActionExtension(
           "no_action_extension",
           extensions::extension_action_test_util::NO_ACTION,
-          extensions::Manifest::UNPACKED);
+          extensions::Manifest::INTERNAL);
   extension_service()->AddExtension(no_action_extension.get());
 
   scoped_refptr<const extensions::Extension> action_extension =
@@ -148,6 +154,13 @@
           extensions::Manifest::INTERNAL);
   extension_service()->AddExtension(action_extension.get());
 
+  // The 'suspicious extension' bubble warns the user about extensions that are
+  // disabled for not being from the webstore. This is one of the few bubbles
+  // that lets us test anchoring to the app menu, since we usually anchor to the
+  // extension action now that every extension is given a permanent UI presence.
+  extension_service()->DisableExtension(
+      no_action_extension->id(), extensions::Extension::DISABLE_NOT_VERIFIED);
+
   Browser* second_browser = new Browser(Browser::CreateParams(profile(), true));
   ASSERT_TRUE(second_browser);
   second_browser->window()->Show();
diff --git a/chrome/browser/ui/profile_chooser_constants.h b/chrome/browser/ui/profile_chooser_constants.h
index d18f4e3..52adfbd 100644
--- a/chrome/browser/ui/profile_chooser_constants.h
+++ b/chrome/browser/ui/profile_chooser_constants.h
@@ -29,18 +29,6 @@
   BUBBLE_VIEW_MODE_SWITCH_USER,
 };
 
-// Tutorial modes that can be displayed in the profile chooser bubble.
-enum TutorialMode {
-  // No tutorial card shown.
-  TUTORIAL_MODE_NONE,
-  // A tutorial card shown to confirm Chrome signin.
-  TUTORIAL_MODE_CONFIRM_SIGNIN,
-  // A tutorial card shown to introduce an upgrade user to the new avatar menu.
-  TUTORIAL_MODE_WELCOME_UPGRADE,
-  // A tutorial card shown to display the signin errors.
-  TUTORIAL_MODE_SHOW_ERROR,
-};
-
 };  // namespace profiles
 
 #endif  // CHROME_BROWSER_UI_PROFILE_CHOOSER_CONSTANTS_H_
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 2777068c..994ee56 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -297,8 +297,7 @@
       command_line.HasSwitch(switches::kShowAppList) ?
           profiles::USER_MANAGER_SELECT_PROFILE_APP_LAUNCHER :
           profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION;
-  UserManager::Show(
-      base::FilePath(), profiles::USER_MANAGER_NO_TUTORIAL, action);
+  UserManager::Show(base::FilePath(), action);
   return true;
 }
 
diff --git a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
index 992e89f..e474aea 100644
--- a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
+++ b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc
@@ -71,10 +71,6 @@
     base::CommandLine* command_line) {
   ExtensionBrowserTest::SetUpCommandLine(command_line);
   ToolbarActionsBar::disable_animations_for_testing_ = true;
-  // These tests are deliberately testing behavior without the redesign.
-  // Forcefully disable it.
-  override_redesign_.reset(new extensions::FeatureSwitch::ScopedOverride(
-      extensions::FeatureSwitch::extension_action_redesign(), true));
 }
 
 void BrowserActionsBarBrowserTest::SetUpOnMainThread() {
@@ -112,40 +108,24 @@
   }
 }
 
-// BrowserActionsBarLegacyBrowserTest:
-
-BrowserActionsBarLegacyBrowserTest::BrowserActionsBarLegacyBrowserTest() {
-}
-
-BrowserActionsBarLegacyBrowserTest::~BrowserActionsBarLegacyBrowserTest() {
-}
-
-void BrowserActionsBarLegacyBrowserTest::SetUpCommandLine(
-    base::CommandLine* command_line) {
-  BrowserActionsBarBrowserTest::SetUpCommandLine(command_line);
-  // Override to force the redesign. Completely clear the previous override
-  // first, since doing so resets the value of the switch.
-  override_redesign_.reset();
-  override_redesign_.reset(new extensions::FeatureSwitch::ScopedOverride(
-      extensions::FeatureSwitch::extension_action_redesign(), false));
-}
-
 // Test the basic functionality.
-IN_PROC_BROWSER_TEST_F(BrowserActionsBarLegacyBrowserTest, Basic) {
+IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest, Basic) {
   // Load an extension with no browser action.
   extension_service()->AddExtension(CreateExtension("alpha", false).get());
-  // This extension should not be in the model (has no browser action).
-  EXPECT_EQ(0, browser_actions_bar()->NumberOfBrowserActions());
-
-  // Load an extension with a browser action.
-  extension_service()->AddExtension(CreateExtension("beta", true).get());
+  // This extension should be present in the model (it will receive a
+  // synthesized action).
   EXPECT_EQ(1, browser_actions_bar()->NumberOfBrowserActions());
   EXPECT_TRUE(browser_actions_bar()->HasIcon(0));
 
-  // Unload the extension.
-  std::string id = browser_actions_bar()->GetExtensionId(0);
+  // Load an extension with a browser action; it will also be in the toolbar.
+  extension_service()->AddExtension(CreateExtension("beta", true).get());
+  EXPECT_EQ(2, browser_actions_bar()->NumberOfBrowserActions());
+  EXPECT_TRUE(browser_actions_bar()->HasIcon(1));
+
+  // Unload the extension; the icon should be removed.
+  std::string id = browser_actions_bar()->GetExtensionId(1);
   UnloadExtension(id);
-  EXPECT_EQ(0, browser_actions_bar()->NumberOfBrowserActions());
+  EXPECT_EQ(1, browser_actions_bar()->NumberOfBrowserActions());
 }
 
 // Test moving various browser actions. This is not to check the logic of the
@@ -180,21 +160,6 @@
   EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(2));
 }
 
-// Test that explicitly hiding an extension action results in it disappearing
-// from the browser actions bar.
-IN_PROC_BROWSER_TEST_F(BrowserActionsBarLegacyBrowserTest, ForceHide) {
-  LoadExtensions();
-
-  EXPECT_EQ(3, browser_actions_bar()->VisibleBrowserActions());
-  EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(0));
-  // Force hide one of the extensions' browser action.
-  extensions::ExtensionActionAPI::Get(browser()->profile())->
-      SetBrowserActionVisibility(extension_a()->id(), false);
-  // The browser action for Extension A should be removed.
-  EXPECT_EQ(2, browser_actions_bar()->VisibleBrowserActions());
-  EXPECT_EQ(extension_b()->id(), browser_actions_bar()->GetExtensionId(0));
-}
-
 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest, Visibility) {
   LoadExtensions();
 
diff --git a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
index dcb7476..08fe6fb 100644
--- a/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
+++ b/chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h
@@ -10,7 +10,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
-#include "extensions/common/feature_switch.h"
 
 namespace extensions {
 class Extension;
@@ -49,12 +48,6 @@
     return extension_c_.get();
   }
 
- protected:
-  // Enable or disable the feature redesign switch.
-  std::unique_ptr<extensions::FeatureSwitch::ScopedOverride> override_redesign_;
-  std::unique_ptr<extensions::FeatureSwitch::ScopedOverride>
-      override_media_router_;
-
  private:
   std::unique_ptr<BrowserActionTestUtil> browser_actions_bar_;
 
@@ -69,17 +62,4 @@
   DISALLOW_COPY_AND_ASSIGN(BrowserActionsBarBrowserTest);
 };
 
-// A test with the extension-action-redesign switch disabled.
-class BrowserActionsBarLegacyBrowserTest
-    : public BrowserActionsBarBrowserTest {
- protected:
-  BrowserActionsBarLegacyBrowserTest();
-  ~BrowserActionsBarLegacyBrowserTest() override;
-
-  void SetUpCommandLine(base::CommandLine* command_line) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BrowserActionsBarLegacyBrowserTest);
-};
-
 #endif  // CHROME_BROWSER_UI_TOOLBAR_BROWSER_ACTIONS_BAR_BROWSERTEST_H_
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
index d013ae08..1f76ec09 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
@@ -35,7 +35,6 @@
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/runtime_data.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/feature_switch.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image_skia.h"
 
@@ -90,10 +89,7 @@
 
 ToolbarActionsBar::PlatformSettings::PlatformSettings()
     : item_spacing(GetLayoutConstant(TOOLBAR_STANDARD_SPACING)),
-      icons_per_overflow_menu_row(1),
-      chevron_enabled(!extensions::FeatureSwitch::extension_action_redesign()->
-                          IsEnabled()) {
-}
+      icons_per_overflow_menu_row(1) {}
 
 ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
                                      Browser* browser,
@@ -267,8 +263,7 @@
   // popped out action (because the action will pop back into overflow when the
   // menu opens).
   return GetEndIndexInBounds() != toolbar_actions_.size() ||
-         (is_drag_in_progress_ && !platform_settings_.chevron_enabled) ||
-         (popped_out_action_ && !is_popped_out_sticky_);
+         is_drag_in_progress_ || (popped_out_action_ && !is_popped_out_sticky_);
 }
 
 gfx::Rect ToolbarActionsBar::GetFrameForIndex(
@@ -514,7 +509,7 @@
     delegate_->Redraw(true);
   }
 
-  ResizeDelegate(gfx::Tween::LINEAR, false);
+  ResizeDelegate(gfx::Tween::LINEAR);
   if (!delegate_->IsAnimating()) {
     // Don't call the closure re-entrantly.
     base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, closure);
@@ -532,7 +527,7 @@
   popped_out_closure_.Reset();
   if (!IsActionVisibleOnMainBar(controller))
     delegate_->Redraw(true);
-  ResizeDelegate(gfx::Tween::LINEAR, false);
+  ResizeDelegate(gfx::Tween::LINEAR);
 }
 
 void ToolbarActionsBar::SetPopupOwner(
@@ -633,15 +628,12 @@
                           model_->CreateActionForItem(browser_, this, item));
   delegate_->AddViewForAction(toolbar_actions_[index].get(), index);
 
-  // We may need to resize (e.g. to show the new icon, or the chevron). We don't
-  // need to check if an extension is upgrading here, because ResizeDelegate()
-  // checks to see if the container is already the proper size, and because
-  // if the action is newly incognito enabled, even though it's a reload, it's
-  // a new extension to this toolbar.
-  // We suppress the chevron during animation because, if we're expanding to
-  // show a new icon, we don't want to have the chevron visible only for the
-  // duration of the animation.
-  ResizeDelegate(gfx::Tween::LINEAR, true);
+  // We may need to resize (e.g. to show the new icon). We don't need to check
+  // if an extension is upgrading here, because ResizeDelegate() checks to see
+  // if the container is already the proper size, and because if the action is
+  // newly incognito enabled, even though it's a reload, it's a new extension to
+  // this toolbar.
+  ResizeDelegate(gfx::Tween::LINEAR);
 }
 
 void ToolbarActionsBar::OnToolbarActionRemoved(const std::string& action_id) {
@@ -681,7 +673,7 @@
     } else {
       // Either we went from overflow to no-overflow, or we shrunk the no-
       // overflow container by 1.  Either way the size changed, so animate.
-      ResizeDelegate(gfx::Tween::EASE_OUT, false);
+      ResizeDelegate(gfx::Tween::EASE_OUT);
     }
   }
 }
@@ -704,11 +696,10 @@
 }
 
 void ToolbarActionsBar::OnToolbarVisibleCountChanged() {
-  ResizeDelegate(gfx::Tween::EASE_OUT, false);
+  ResizeDelegate(gfx::Tween::EASE_OUT);
 }
 
-void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type,
-                                       bool suppress_chevron) {
+void ToolbarActionsBar::ResizeDelegate(gfx::Tween::Type tween_type) {
   int desired_width = GetFullSize().width();
   if (desired_width !=
       delegate_->GetWidth(ToolbarActionsBarDelegate::GET_WIDTH_CURRENT)) {
@@ -782,7 +773,7 @@
   tracked_objects::ScopedTracker tracking_profile(
       FROM_HERE_WITH_EXPLICIT_FUNCTION(
           "ToolbarActionsBar::OnToolbarModelInitialized"));
-  ResizeDelegate(gfx::Tween::EASE_OUT, false);
+  ResizeDelegate(gfx::Tween::EASE_OUT);
 }
 
 void ToolbarActionsBar::TabInsertedAt(TabStripModel* tab_strip_model,
@@ -807,7 +798,7 @@
   // Our visible browser actions may have changed - re-Layout() and check the
   // size (if we aren't suppressing the layout).
   if (!suppress_layout_) {
-    ResizeDelegate(gfx::Tween::EASE_OUT, false);
+    ResizeDelegate(gfx::Tween::EASE_OUT);
     delegate_->Redraw(true);
   }
 }
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index 3675218..6a7f37f 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -60,9 +60,6 @@
     int item_spacing;
     // The number of icons per row in the overflow menu.
     int icons_per_overflow_menu_row;
-    // Whether or not the overflow menu is displayed as a chevron (this is being
-    // phased out).
-    bool chevron_enabled;
   };
 
   // The type of drag that occurred in a drag-and-drop operation.
@@ -274,8 +271,8 @@
                      bool foreground) override;
 
   // Resizes the delegate (if necessary) to the preferred size using the given
-  // |tween_type| and optionally suppressing the chevron.
-  void ResizeDelegate(gfx::Tween::Type tween_type, bool suppress_chevron);
+  // |tween_type|.
+  void ResizeDelegate(gfx::Tween::Type tween_type);
 
   // Returns the action for the given |id|, if one exists.
   ToolbarActionViewController* GetActionForId(const std::string& action_id);
@@ -304,7 +301,7 @@
   // is the main bar.
   ToolbarActionsBar* main_bar_;
 
-  // Platform-specific settings for dimensions and the overflow chevron.
+  // Platform-specific settings for dimensions.
   PlatformSettings platform_settings_;
 
   // The toolbar actions.
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
index 84b8a6f..de8ac79 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
@@ -25,7 +25,6 @@
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
-#include "extensions/common/feature_switch.h"
 #include "ui/base/test/material_design_controller_test_api.h"
 
 namespace {
@@ -115,10 +114,8 @@
   ToolbarActionsBar::disable_animations_for_testing_ = true;
   browser_action_test_util_.reset(new BrowserActionTestUtil(browser(), false));
 
-  if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
-    overflow_browser_action_test_util_ =
-        browser_action_test_util_->CreateOverflowBar();
-  }
+  overflow_browser_action_test_util_ =
+      browser_action_test_util_->CreateOverflowBar();
 }
 
 void ToolbarActionsBarUnitTest::TearDown() {
@@ -169,15 +166,9 @@
                                total_size,
                                visible_count);
   std::string overflow_bar_error;
-  if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
-    overflow_bar_error =
-        VerifyToolbarOrderForBar(overflow_bar(),
-                                 overflow_browser_action_test_util(),
-                                 expected_names,
-                                 total_size,
-                                 total_size - visible_count);
-
-  }
+  overflow_bar_error = VerifyToolbarOrderForBar(
+      overflow_bar(), overflow_browser_action_test_util(), expected_names,
+      total_size, total_size - visible_count);
 
   return main_bar_error.empty() && overflow_bar_error.empty() ?
       testing::AssertionSuccess() :
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.cc b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
index 4350ad2..abbb2cf0 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
@@ -36,7 +36,6 @@
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/common/extension_set.h"
-#include "extensions/common/feature_switch.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/one_shot_event.h"
 
@@ -53,8 +52,6 @@
       component_actions_factory_(
           base::MakeUnique<ComponentToolbarActionsFactory>(profile_)),
       actions_initialized_(false),
-      use_redesign_(
-          extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()),
       highlight_type_(HIGHLIGHT_NONE),
       has_active_bubble_(false),
       extension_action_observer_(this),
@@ -191,7 +188,6 @@
       break;
     }
     case COMPONENT_ACTION: {
-      DCHECK(use_redesign_);
       result = component_actions_factory_->GetComponentToolbarActionForId(
           item.id, browser, bar);
       break;
@@ -203,18 +199,6 @@
   return result;
 }
 
-void ToolbarActionsModel::OnExtensionActionVisibilityChanged(
-    const std::string& extension_id,
-    bool is_now_visible) {
-  if (use_redesign_)
-    return;
-  const extensions::Extension* extension = GetExtensionById(extension_id);
-  if (is_now_visible)
-    AddExtension(extension);
-  else
-    RemoveExtension(extension);
-}
-
 void ToolbarActionsModel::OnExtensionLoaded(
     content::BrowserContext* browser_context,
     const extensions::Extension* extension) {
@@ -271,11 +255,9 @@
   for (Observer& observer : observers_)
     observer.OnToolbarModelInitialized();
 
-  if (use_redesign_) {
-    component_actions_factory_->UnloadMigratedExtensions(
-        extensions::ExtensionSystem::Get(profile_)->extension_service(),
-        extension_registry_);
-  }
+  component_actions_factory_->UnloadMigratedExtensions(
+      extensions::ExtensionSystem::Get(profile_)->extension_service(),
+      extension_registry_);
 }
 
 size_t ToolbarActionsModel::FindNewPositionFromLastKnownGood(
@@ -308,14 +290,9 @@
       !extensions::util::IsIncognitoEnabled(extension->id(), profile_))
     return false;
 
-  if (use_redesign_) {
-    // In this case, we don't care about the browser action visibility, because
-    // we want to show each extension regardless.
-    return extension_action_manager_->GetExtensionAction(*extension) != nullptr;
-  }
-
-  return extension_action_manager_->GetBrowserAction(*extension) &&
-         extension_action_api_->GetBrowserActionVisibility(extension->id());
+  // In this case, we don't care about the browser action visibility, because
+  // we want to show each extension regardless.
+  return extension_action_manager_->GetExtensionAction(*extension) != nullptr;
 }
 
 void ToolbarActionsModel::AddExtension(const extensions::Extension* extension) {
@@ -608,14 +585,10 @@
             ? base::HistogramBase::kSampleType_MAX
             : visible_icon_count_ - component_actions_count);
 
-    if (use_redesign_) {
-      // The only time this will useful and possibly vary from
-      // BrowserActionsVisible is when the redesign has been enabled.
-      UMA_HISTOGRAM_COUNTS_100("Toolbar.ActionsModel.ToolbarActionsVisible",
-                               visible_icon_count_ == -1
-                                   ? base::HistogramBase::kSampleType_MAX
-                                   : visible_icon_count_);
-    }
+    UMA_HISTOGRAM_COUNTS_100("Toolbar.ActionsModel.ToolbarActionsVisible",
+                             visible_icon_count_ == -1
+                                 ? base::HistogramBase::kSampleType_MAX
+                                 : visible_icon_count_);
   }
 }
 
@@ -625,12 +598,10 @@
 
 bool ToolbarActionsModel::HasComponentAction(
     const std::string& action_id) const {
-  DCHECK(use_redesign_);
   return HasItem(ToolbarItem(action_id, COMPONENT_ACTION));
 }
 
 void ToolbarActionsModel::AddComponentAction(const std::string& action_id) {
-  DCHECK(use_redesign_);
   if (!actions_initialized_) {
     component_actions_factory_->OnAddComponentActionBeforeInit(action_id);
     return;
@@ -642,7 +613,6 @@
 }
 
 void ToolbarActionsModel::RemoveComponentAction(const std::string& action_id) {
-  DCHECK(use_redesign_);
   if (!actions_initialized_) {
     component_actions_factory_->OnRemoveComponentActionBeforeInit(action_id);
     return;
@@ -715,35 +685,26 @@
 void ToolbarActionsModel::SetActionVisibility(const std::string& action_id,
                                               bool is_now_visible) {
   // Hiding works differently with the new and old toolbars.
-  if (use_redesign_) {
-    DCHECK(HasItem(ToolbarItem(action_id, EXTENSION_ACTION)));
+  DCHECK(HasItem(ToolbarItem(action_id, EXTENSION_ACTION)));
 
-    int new_size = 0;
-    int new_index = 0;
-    if (is_now_visible) {
-      // If this action used to be hidden, we can't possibly be showing all.
-      DCHECK_LT(visible_icon_count(), toolbar_items_.size());
-      // Grow the bar by one and move the action to the end of the visibles.
-      new_size = visible_icon_count() + 1;
-      new_index = new_size - 1;
-    } else {
-      // If we're hiding one, we must be showing at least one.
-      DCHECK_GE(visible_icon_count(), 0u);
-      // Shrink the bar by one and move the action to the beginning of the
-      // overflow menu.
-      new_size = visible_icon_count() - 1;
-      new_index = new_size;
-    }
-    SetVisibleIconCount(new_size);
-    MoveActionIcon(action_id, new_index);
-  } else {  // Legacy toolbar; hiding removes it from the toolbar.
-    if (!profile_->IsOffTheRecord()) {
-      extension_action_api_->SetBrowserActionVisibility(action_id,
-                                                        is_now_visible);
-    } else {
-      OnExtensionActionVisibilityChanged(action_id, is_now_visible);
-    }
+  int new_size = 0;
+  int new_index = 0;
+  if (is_now_visible) {
+    // If this action used to be hidden, we can't possibly be showing all.
+    DCHECK_LT(visible_icon_count(), toolbar_items_.size());
+    // Grow the bar by one and move the action to the end of the visibles.
+    new_size = visible_icon_count() + 1;
+    new_index = new_size - 1;
+  } else {
+    // If we're hiding one, we must be showing at least one.
+    DCHECK_GE(visible_icon_count(), 0u);
+    // Shrink the bar by one and move the action to the beginning of the
+    // overflow menu.
+    new_size = visible_icon_count() - 1;
+    new_index = new_size;
   }
+  SetVisibleIconCount(new_size);
+  MoveActionIcon(action_id, new_index);
 }
 
 void ToolbarActionsModel::OnActionToolbarPrefChange() {
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.h b/chrome/browser/ui/toolbar/toolbar_actions_model.h
index 8f01c23..662844d 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.h
@@ -225,8 +225,6 @@
       ExtensionAction* extension_action,
       content::WebContents* web_contents,
       content::BrowserContext* browser_context) override;
-  void OnExtensionActionVisibilityChanged(const std::string& extension_id,
-                                          bool is_now_visible) override;
 
   // To be called after the extension service is ready; gets loaded extensions
   // from the ExtensionRegistry, their saved order from the pref service, and
@@ -299,9 +297,6 @@
   // True if we've handled the initial EXTENSIONS_READY notification.
   bool actions_initialized_;
 
-  // If true, we include all actions in the toolbar model.
-  bool use_redesign_;
-
   // Ordered list of browser actions.
   std::vector<ToolbarItem> toolbar_items_;
 
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc b/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
index 158a2eb..6fd4609 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model_unittest.cc
@@ -43,7 +43,6 @@
 #include "extensions/browser/uninstall_reason.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
-#include "extensions/common/feature_switch.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/value_builder.h"
 
@@ -836,24 +835,10 @@
   EXPECT_EQ(num_toolbar_items(), toolbar_model()->visible_icon_count());
 }
 
-// Test that, in the absence of the extension-action-redesign switch, the
-// model only contains extensions with browser actions and component actions.
-TEST_F(ToolbarActionsModelUnitTest, TestToolbarExtensionTypesDisabledSwitch) {
-  extensions::FeatureSwitch::ScopedOverride enable_redesign(
-      extensions::FeatureSwitch::extension_action_redesign(), false);
-  Init();
-  ASSERT_TRUE(AddActionExtensions());
-
-  EXPECT_EQ(1u, num_toolbar_items());
-  EXPECT_EQ(browser_action()->id(), GetActionIdAtIndex(0u));
-}
-
 // Test that, with the extension-action-redesign switch, the model contains
 // all types of extensions, except those which should not be displayed on the
 // toolbar (like component extensions).
 TEST_F(ToolbarActionsModelUnitTest, TestToolbarExtensionTypesEnabledSwitch) {
-  extensions::FeatureSwitch::ScopedOverride enable_redesign(
-      extensions::FeatureSwitch::extension_action_redesign(), true);
   Init();
 
   ASSERT_TRUE(AddActionExtensions());
@@ -904,48 +889,6 @@
   EXPECT_TRUE(ModelHasActionForId(internal_extension_no_action->id()));
 }
 
-// Test that hiding actions on the toolbar results in their removal from the
-// model when the redesign switch is not enabled.
-TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarActionsVisibilityNoSwitch) {
-  extensions::FeatureSwitch::ScopedOverride enable_redesign(
-      extensions::FeatureSwitch::extension_action_redesign(), false);
-  Init();
-
-  extensions::ExtensionActionAPI* action_api =
-      extensions::ExtensionActionAPI::Get(profile());
-
-  ASSERT_TRUE(AddBrowserActionExtensions());
-  // Sanity check: Order should start as A , B, C.
-  EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u));
-  EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u));
-  EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u));
-
-  // By default, all actions should be visible.
-  EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_a()->id()));
-  EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_b()->id()));
-  EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_c()->id()));
-
-  // Hiding an action should result in its removal from the toolbar.
-  action_api->SetBrowserActionVisibility(browser_action_b()->id(), false);
-  EXPECT_FALSE(
-      action_api->GetBrowserActionVisibility(browser_action_b()->id()));
-  // Thus, there should now only be two items on the toolbar - A and C.
-  EXPECT_EQ(2u, num_toolbar_items());
-  EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u));
-  EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u));
-
-  // Resetting the visibility to 'true' should result in the extension being
-  // added back at its original position.
-  action_api->SetBrowserActionVisibility(browser_action_b()->id(), true);
-  EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_b()->id()));
-  // So the toolbar order should be A, B, C.
-  EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u));
-  EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u));
-  EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u));
-}
-
 TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarIncognitoModeTest) {
   Init();
   ASSERT_TRUE(AddBrowserActionExtensions());
@@ -1120,8 +1063,6 @@
 // overflow menu when the redesign switch is enabled.
 TEST_F(ToolbarActionsModelUnitTest,
        ActionsToolbarActionsVisibilityWithSwitchAndComponentActions) {
-  extensions::FeatureSwitch::ScopedOverride enable_redesign(
-      extensions::FeatureSwitch::extension_action_redesign(), true);
   Init();
 
   // We choose to use all types of extensions here, since the misnamed
@@ -1249,8 +1190,6 @@
 // toolbar with component actions.
 TEST_F(ToolbarActionsModelUnitTest,
        ActionsToolbarReorderAndReinsertWithSwitchAndComponentActions) {
-  extensions::FeatureSwitch::ScopedOverride enable_redesign(
-      extensions::FeatureSwitch::extension_action_redesign(), true);
   InitWithMockActionsFactory();
 
   // One component action was added when the model was initialized.
diff --git a/chrome/browser/ui/user_manager.h b/chrome/browser/ui/user_manager.h
index 508ac9ac..161150a 100644
--- a/chrome/browser/ui/user_manager.h
+++ b/chrome/browser/ui/user_manager.h
@@ -25,12 +25,10 @@
 
   // Shows the User Manager or re-activates an existing one, focusing the
   // profile given by |profile_path_to_focus|; passing an empty base::FilePath
-  // focuses no user pod. Based on the value of |tutorial_mode|, a tutorial
-  // could be shown, in which case |profile_path_to_focus| is ignored. Depending
-  // on the value of |user_manager_action|, executes an action once the user
-  // manager displays or after a profile is opened.
+  // focuses no user pod. Depending on the value of |user_manager_action|,
+  // executes an action once the user manager displays or after a profile is
+  // opened.
   static void Show(const base::FilePath& profile_path_to_focus,
-                   profiles::UserManagerTutorialMode tutorial_mode,
                    profiles::UserManagerAction user_manager_action);
 
   // Hides the User Manager.
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc b/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc
index 94f853ec..760f3659 100644
--- a/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc
@@ -70,17 +70,6 @@
   DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleViewBrowserTest);
 };
 
-class ExtensionMessageBubbleViewBrowserTestLegacy
-    : public ExtensionMessageBubbleViewBrowserTest {
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    ExtensionMessageBubbleViewBrowserTest::SetUpCommandLine(command_line);
-    override_redesign_.reset();
-    override_redesign_.reset(new extensions::FeatureSwitch::ScopedOverride(
-        extensions::FeatureSwitch::extension_action_redesign(), false));
-  }
-};
-
 void ExtensionMessageBubbleViewBrowserTest::SetUpCommandLine(
     base::CommandLine* command_line) {
   ExtensionMessageBubbleBrowserTest::SetUpCommandLine(command_line);
@@ -163,12 +152,12 @@
   TestBubbleAnchoredToExtensionAction();
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTestLegacy,
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
                        ExtensionBubbleAnchoredToAppMenu) {
   TestBubbleAnchoredToAppMenu();
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTestLegacy,
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
                        ExtensionBubbleAnchoredToAppMenuWithOtherAction) {
   TestBubbleAnchoredToAppMenuWithOtherAction();
 }
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 6120c4b..1a2486d 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2526,15 +2526,13 @@
     return;
 
   profiles::BubbleViewMode bubble_view_mode;
-  profiles::TutorialMode tutorial_mode;
-  profiles::BubbleViewModeFromAvatarBubbleMode(mode, &bubble_view_mode,
-                                               &tutorial_mode);
+  profiles::BubbleViewModeFromAvatarBubbleMode(mode, &bubble_view_mode);
   if (SigninViewController::ShouldShowModalSigninForMode(bubble_view_mode)) {
     browser_->signin_view_controller()->ShowModalSignin(
         bubble_view_mode, browser_.get(), access_point);
   } else {
-    ProfileChooserView::ShowBubble(bubble_view_mode, tutorial_mode,
-                                   manage_accounts_params, access_point,
+    ProfileChooserView::ShowBubble(bubble_view_mode, manage_accounts_params,
+                                   access_point,
                                    frame_->GetNewAvatarMenuButton(), browser(),
                                    focus_first_profile_button);
     ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::ICON_AVATAR_BUBBLE);
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
index 1ee72aa9..b88a330 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
@@ -58,12 +58,9 @@
   return color_utils::IsDark(blend_color) ? SK_ColorWHITE : SK_ColorBLACK;
 }
 
-void Windows10CaptionButton::OnPaint(gfx::Canvas* canvas) {
-  PaintBackground(canvas);
-  PaintSymbol(canvas);
-}
-
-void Windows10CaptionButton::PaintBackground(gfx::Canvas* canvas) {
+void Windows10CaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
+  // Paint the background of the button (the semi-transparent rectangle that
+  // appears when you hover or press the button).
   const ui::ThemeProvider* theme_provider = GetThemeProvider();
   const SkColor bg_color =
       theme_provider->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND);
@@ -109,6 +106,10 @@
   canvas->FillRect(GetContentsBounds(), SkColorSetA(base_color, alpha));
 }
 
+void Windows10CaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
+  PaintSymbol(canvas);
+}
+
 namespace {
 
 // Canvas::DrawRect's stroke can bleed out of |rect|'s bounds, so this draws a
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.h b/chrome/browser/ui/views/frame/windows_10_caption_button.h
index 44ea3656..60bdeaa 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.h
+++ b/chrome/browser/ui/views/frame/windows_10_caption_button.h
@@ -17,17 +17,14 @@
 
   // views::CustomButton:
   gfx::Size CalculatePreferredSize() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void OnPaintBackground(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:
   // The base color to use for the button symbols and background blending. Uses
   // the more readable of black and white.
   SkColor GetBaseColor() const;
 
-  // Paints the background of the button (the semi-transparent rectangle that
-  // appears when you hover or press the button).
-  void PaintBackground(gfx::Canvas* canvas);
-
   // Paints the minimize/maximize/restore/close icon for the button.
   void PaintSymbol(gfx::Canvas* canvas);
 
diff --git a/chrome/browser/ui/views/passwords/credentials_item_view.cc b/chrome/browser/ui/views/passwords/credentials_item_view.cc
index 5976ca7..905d4a2a47 100644
--- a/chrome/browser/ui/views/passwords/credentials_item_view.cc
+++ b/chrome/browser/ui/views/passwords/credentials_item_view.cc
@@ -195,9 +195,7 @@
   }
 }
 
-void CredentialsItemView::OnPaint(gfx::Canvas* canvas) {
+void CredentialsItemView::OnPaintBackground(gfx::Canvas* canvas) {
   if (state() == STATE_PRESSED || state() == STATE_HOVERED)
     canvas->DrawColor(hover_color_);
-
-  CustomButton::OnPaint(canvas);
 }
diff --git a/chrome/browser/ui/views/passwords/credentials_item_view.h b/chrome/browser/ui/views/passwords/credentials_item_view.h
index adffb5b..054c222b 100644
--- a/chrome/browser/ui/views/passwords/credentials_item_view.h
+++ b/chrome/browser/ui/views/passwords/credentials_item_view.h
@@ -55,7 +55,7 @@
   gfx::Size CalculatePreferredSize() const override;
   int GetHeightForWidth(int w) const override;
   void Layout() override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void OnPaintBackground(gfx::Canvas* canvas) override;
 
   const autofill::PasswordForm* form_;
 
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index 186376d9..e7535c42 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -508,22 +508,16 @@
 // static
 void ProfileChooserView::ShowBubble(
     profiles::BubbleViewMode view_mode,
-    profiles::TutorialMode tutorial_mode,
     const signin::ManageAccountsParams& manage_accounts_params,
     signin_metrics::AccessPoint access_point,
     views::View* anchor_view,
     Browser* browser,
     bool is_source_keyboard) {
-  if (IsShowing()) {
-    if (tutorial_mode != profiles::TUTORIAL_MODE_NONE) {
-      profile_bubble_->tutorial_mode_ = tutorial_mode;
-      profile_bubble_->ShowViewFromMode(view_mode);
-    }
+  if (IsShowing())
     return;
-  }
 
   profile_bubble_ =
-      new ProfileChooserView(anchor_view, browser, view_mode, tutorial_mode,
+      new ProfileChooserView(anchor_view, browser, view_mode,
                              manage_accounts_params.service_type, access_point);
   views::Widget* widget =
       views::BubbleDialogDelegateView::CreateBubble(profile_bubble_);
@@ -553,13 +547,11 @@
 ProfileChooserView::ProfileChooserView(views::View* anchor_view,
                                        Browser* browser,
                                        profiles::BubbleViewMode view_mode,
-                                       profiles::TutorialMode tutorial_mode,
                                        signin::GAIAServiceType service_type,
                                        signin_metrics::AccessPoint access_point)
     : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
       browser_(browser),
       view_mode_(view_mode),
-      tutorial_mode_(tutorial_mode),
       gaia_service_type_(service_type),
       access_point_(access_point) {
   // The sign in webview will be clipped on the bottom corners without these
@@ -580,12 +572,6 @@
   open_other_profile_indexes_map_.clear();
   delete_account_button_map_.clear();
   reauth_account_button_map_.clear();
-  tutorial_sync_settings_ok_button_ = nullptr;
-  tutorial_close_button_ = nullptr;
-  tutorial_sync_settings_link_ = nullptr;
-  tutorial_see_whats_new_button_ = nullptr;
-  tutorial_not_you_link_ = nullptr;
-  tutorial_learn_more_link_ = nullptr;
   sync_error_signin_button_ = nullptr;
   sync_error_passphrase_button_ = nullptr;
   sync_error_upgrade_button_ = nullptr;
@@ -739,9 +725,6 @@
       sub_view = CreateProfileChooserView(avatar_menu);
       break;
   }
-  // Clears tutorial mode for all non-profile-chooser views.
-  if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER)
-    tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
 
   layout->StartRow(1, 0);
   layout->AddView(sub_view);
@@ -773,11 +756,6 @@
 void ProfileChooserView::WindowClosing() {
   DCHECK_EQ(profile_bubble_, this);
   profile_bubble_ = NULL;
-
-  if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) {
-    LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
-  }
 }
 
 bool ProfileChooserView::AcceleratorPressed(
@@ -819,7 +797,6 @@
       profiles::CloseGuestProfileWindows();
     } else {
       UserManager::Show(base::FilePath(),
-                        profiles::USER_MANAGER_NO_TUTORIAL,
                         profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
     }
     PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_OPEN_USER_MANAGER);
@@ -849,22 +826,6 @@
     ShowViewFromMode(profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN);
   } else if (sender == sync_error_signout_button_) {
     chrome::ShowSettingsSubPage(browser_, chrome::kSignOutSubPage);
-  } else if (sender == tutorial_sync_settings_ok_button_) {
-    LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
-    DismissTutorial();
-    ProfileMetrics::LogProfileNewAvatarMenuSignin(
-        ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_OK);
-  } else if (sender == tutorial_close_button_) {
-    DCHECK(tutorial_mode_ != profiles::TUTORIAL_MODE_NONE &&
-           tutorial_mode_ != profiles::TUTORIAL_MODE_CONFIRM_SIGNIN);
-    DismissTutorial();
-  } else if (sender == tutorial_see_whats_new_button_) {
-    ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
-        ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_WHATS_NEW);
-    UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_TUTORIAL_OVERVIEW,
-                      profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   } else if (sender == remove_account_button_) {
     RemoveAccount();
   } else if (sender == account_removal_cancel_button_) {
@@ -896,7 +857,6 @@
     ProfileMetrics::LogProfileNewAvatarMenuNotYou(
         ProfileMetrics::PROFILE_AVATAR_MENU_NOT_YOU_ADD_PERSON);
     UserManager::Show(base::FilePath(),
-                      profiles::USER_MANAGER_NO_TUTORIAL,
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   } else if (sender == disconnect_button_) {
     ProfileMetrics::LogProfileNewAvatarMenuNotYou(
@@ -957,19 +917,6 @@
   } else if (sender == add_account_link_) {
     ShowViewFromMode(profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT);
     PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT);
-  } else if (sender == tutorial_sync_settings_link_) {
-    LoginUIServiceFactory::GetForProfile(browser_->profile())->
-        SyncConfirmationUIClosed(LoginUIService::CONFIGURE_SYNC_FIRST);
-    tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
-    ProfileMetrics::LogProfileNewAvatarMenuSignin(
-        ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_SETTINGS);
-  } else if (sender == tutorial_not_you_link_) {
-    ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
-        ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_NOT_YOU);
-    ShowViewFromMode(profiles::BUBBLE_VIEW_MODE_SWITCH_USER);
-  } else {
-    DCHECK(sender == tutorial_learn_more_link_);
-    signin_ui_util::ShowSigninErrorLearnMorePage(browser_->profile());
   }
 }
 
@@ -985,7 +932,6 @@
   views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
   // Separate items into active and alternatives.
   Indexes other_profiles;
-  views::View* tutorial_view = NULL;
   views::View* sync_error_view = NULL;
   views::View* current_profile_view = NULL;
   views::View* current_profile_accounts = NULL;
@@ -1005,14 +951,6 @@
     }
   }
 
-  if (tutorial_view) {
-    // TODO(mlerman): update UMA stats for the new tutorial.
-    layout->StartRow(1, 0);
-    layout->AddView(tutorial_view);
-  } else {
-    tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
-  }
-
   if (sync_error_view) {
     layout->StartRow(1, 0);
     layout->AddView(sync_error_view);
@@ -1052,171 +990,6 @@
   return view;
 }
 
-void ProfileChooserView::DismissTutorial() {
-  // Never shows the upgrade tutorial again if manually closed.
-  if (tutorial_mode_ == profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
-    browser_->profile()->GetPrefs()->SetInteger(
-        prefs::kProfileAvatarTutorialShown,
-        signin_ui_util::kUpgradeWelcomeTutorialShowMax + 1);
-  }
-
-  tutorial_mode_ = profiles::TUTORIAL_MODE_NONE;
-  ShowViewFromMode(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER);
-}
-
-views::View* ProfileChooserView::CreateTutorialViewIfNeeded(
-    const AvatarMenu::Item& item) {
-  if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN)
-    return CreateSigninConfirmationView();
-
-  if (tutorial_mode_ == profiles::TUTORIAL_MODE_SHOW_ERROR)
-    return CreateSigninErrorView();
-
-  if (profiles::ShouldShowWelcomeUpgradeTutorial(
-      browser_->profile(), tutorial_mode_)) {
-    if (tutorial_mode_ != profiles::TUTORIAL_MODE_WELCOME_UPGRADE) {
-      Profile* profile = browser_->profile();
-      const int show_count = profile->GetPrefs()->GetInteger(
-          prefs::kProfileAvatarTutorialShown);
-      profile->GetPrefs()->SetInteger(
-          prefs::kProfileAvatarTutorialShown, show_count + 1);
-    }
-
-    return CreateWelcomeUpgradeTutorialView(item);
-  }
-
-  return nullptr;
-}
-
-views::View* ProfileChooserView::CreateTutorialView(
-    profiles::TutorialMode tutorial_mode,
-    const base::string16& title_text,
-    const base::string16& content_text,
-    const base::string16& link_text,
-    const base::string16& button_text,
-    bool stack_button,
-    views::Link** link,
-    views::LabelButton** button,
-    views::ImageButton** close_button) {
-  tutorial_mode_ = tutorial_mode;
-
-  // TODO(ananta)
-  // Use the dialog framework to create a dialog here instead of manually
-  // creating one.
-  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
-
-  views::View* view = new views::View();
-  view->set_background(views::Background::CreateSolidBackground(
-      profiles::kAvatarTutorialBackgroundColor));
-
-  gfx::Insets dialog_insets = provider->GetInsetsMetric(
-      views::INSETS_DIALOG_CONTENTS);
-
-  view->SetBorder(views::CreateEmptyBorder(dialog_insets));
-
-  // TODO(ananta)
-  // This seems to add a double margin at the side. Investigate and remove if
-  // so.
-  views::GridLayout* layout = CreateSingleColumnLayout(
-      view, kFixedMenuWidth - dialog_insets.width());
-
-  // Creates a second column set for buttons and links.
-  views::ColumnSet* button_columns = layout->AddColumnSet(1);
-  button_columns->AddColumn(views::GridLayout::LEADING,
-      views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
-  button_columns->AddPaddingColumn(1,
-      provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_HORIZONTAL));
-  button_columns->AddColumn(views::GridLayout::TRAILING,
-      views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
-
-  // Adds title and close button if needed.
-  const SkColor kTitleAndButtonTextColor = SK_ColorWHITE;
-  views::Label* title_label = new views::Label(title_text);
-  title_label->SetMultiLine(true);
-  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  title_label->SetAutoColorReadabilityEnabled(false);
-  title_label->SetEnabledColor(kTitleAndButtonTextColor);
-  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
-      ui::ResourceBundle::MediumFont));
-
-  if (close_button) {
-    layout->StartRow(1, 1);
-    layout->AddView(title_label);
-    *close_button = new views::ImageButton(this);
-    (*close_button)->SetImageAlignment(views::ImageButton::ALIGN_RIGHT,
-                                       views::ImageButton::ALIGN_MIDDLE);
-    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
-    (*close_button)->SetImage(views::ImageButton::STATE_NORMAL,
-                              rb->GetImageSkiaNamed(IDR_CLOSE_1));
-    (*close_button)->SetImage(views::ImageButton::STATE_HOVERED,
-                              rb->GetImageSkiaNamed(IDR_CLOSE_1_H));
-    (*close_button)->SetImage(views::ImageButton::STATE_PRESSED,
-                              rb->GetImageSkiaNamed(IDR_CLOSE_1_P));
-    layout->AddView(*close_button);
-  } else {
-    layout->StartRow(1, 0);
-    layout->AddView(title_label);
-  }
-
-  // Adds body content.
-  views::Label* content_label = new views::Label(content_text);
-  content_label->SetMultiLine(true);
-  content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  content_label->SetAutoColorReadabilityEnabled(false);
-  content_label->SetEnabledColor(profiles::kAvatarTutorialContentTextColor);
-
-  const int related_control_vertical =
-      provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_VERTICAL);
-
-  layout->StartRowWithPadding(1, 0, 0, related_control_vertical);
-  layout->AddView(content_label);
-
-  // Adds links and buttons.
-  bool has_button = !button_text.empty();
-  if (has_button) {
-    *button = views::MdTextButton::CreateSecondaryUiButton(this, button_text);
-    if (ui::MaterialDesignController::IsSecondaryUiMaterial())
-      (*button)->SetEnabledTextColors(kTitleAndButtonTextColor);
-    else
-      (*button)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  }
-
-  bool has_link = !link_text.empty();
-  if (has_link) {
-    *link = CreateLink(link_text, this);
-    (*link)->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    (*link)->SetAutoColorReadabilityEnabled(false);
-    (*link)->SetEnabledColor(kTitleAndButtonTextColor);
-  }
-
-  const int unrelated_control_vertical =
-      provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_VERTICAL);
-
-  if (stack_button) {
-    DCHECK(has_button);
-    layout->StartRowWithPadding(1, 0, 0, unrelated_control_vertical);
-    layout->AddView(*button);
-    if (has_link) {
-      layout->StartRowWithPadding(1, 0, 0, related_control_vertical);
-      (*link)->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-      layout->AddView(*link);
-    }
-  } else {
-    DCHECK(has_link || has_button);
-    layout->StartRowWithPadding(1, 1, 0, unrelated_control_vertical);
-    if (has_link)
-      layout->AddView(*link);
-    else
-      layout->SkipColumns(1);
-    if (has_button)
-      layout->AddView(*button);
-    else
-      layout->SkipColumns(1);
-  }
-
-  return view;
-}
-
 views::View* ProfileChooserView::CreateSyncErrorViewIfNeeded() {
   int content_string_id, button_string_id;
   views::LabelButton** button_out = nullptr;
@@ -1787,63 +1560,6 @@
       kFixedAccountRemovalViewWidth);
 }
 
-views::View* ProfileChooserView::CreateWelcomeUpgradeTutorialView(
-    const AvatarMenu::Item& avatar_item) {
-  ProfileMetrics::LogProfileNewAvatarMenuUpgrade(
-      ProfileMetrics::PROFILE_AVATAR_MENU_UPGRADE_VIEW);
-
-  // For local profiles, the "Not you" link doesn't make sense.
-  base::string16 link_message = avatar_item.signed_in ?
-      l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name) :
-      base::string16();
-
-  return CreateTutorialView(
-      profiles::TUTORIAL_MODE_WELCOME_UPGRADE,
-      l10n_util::GetStringUTF16(
-          IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE),
-      l10n_util::GetStringUTF16(
-          IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT),
-      link_message,
-      l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON),
-      true /* stack_button */,
-      &tutorial_not_you_link_,
-      &tutorial_see_whats_new_button_,
-      &tutorial_close_button_);
-}
-
-views::View* ProfileChooserView::CreateSigninConfirmationView() {
-  ProfileMetrics::LogProfileNewAvatarMenuSignin(
-      ProfileMetrics::PROFILE_AVATAR_MENU_SIGNIN_VIEW);
-
-  return CreateTutorialView(
-      profiles::TUTORIAL_MODE_CONFIRM_SIGNIN,
-      l10n_util::GetStringUTF16(IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE),
-      l10n_util::GetStringUTF16(
-          IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT),
-      l10n_util::GetStringUTF16(IDS_PROFILES_SYNC_SETTINGS_LINK),
-      l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_OK_BUTTON),
-      false /* stack_button */,
-      &tutorial_sync_settings_link_,
-      &tutorial_sync_settings_ok_button_,
-      NULL /* close_button*/);
-}
-
-views::View* ProfileChooserView::CreateSigninErrorView() {
-  LoginUIService* login_ui_service =
-      LoginUIServiceFactory::GetForProfile(browser_->profile());
-  base::string16 last_login_result(login_ui_service->GetLastLoginResult());
-  return CreateTutorialView(
-      profiles::TUTORIAL_MODE_SHOW_ERROR,
-      l10n_util::GetStringUTF16(IDS_PROFILES_ERROR_TUTORIAL_TITLE),
-      last_login_result,
-      l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_TUTORIAL_LEARN_MORE),
-      base::string16(),
-      false /* stack_button */,
-      &tutorial_learn_more_link_,
-      NULL,
-      &tutorial_close_button_);
-}
-
 views::View* ProfileChooserView::CreateSwitchUserView() {
   ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
   views::View* view = new views::View();
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.h b/chrome/browser/ui/views/profiles/profile_chooser_view.h
index 1bb9cc9..72fcf29c 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.h
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.h
@@ -50,7 +50,6 @@
   // the existing bubble will auto-close due to focus loss.
   static void ShowBubble(
       profiles::BubbleViewMode view_mode,
-      profiles::TutorialMode tutorial_mode,
       const signin::ManageAccountsParams& manage_accounts_params,
       signin_metrics::AccessPoint access_point,
       views::View* anchor_view,
@@ -72,7 +71,6 @@
   ProfileChooserView(views::View* anchor_view,
                      Browser* browser,
                      profiles::BubbleViewMode view_mode,
-                     profiles::TutorialMode tutorial_mode,
                      signin::GAIAServiceType service_type,
                      signin_metrics::AccessPoint access_point);
   ~ProfileChooserView() override;
@@ -152,40 +150,6 @@
   // Removes the currently selected account and attempts to restart Chrome.
   void RemoveAccount();
 
-  // Close the tutorial card.
-  void DismissTutorial();
-
-  // Creates a tutorial card to introduce an upgrade user to the new avatar
-  // menu. |avatar_item| refers to the current profile.
-  views::View* CreateWelcomeUpgradeTutorialView(
-      const AvatarMenu::Item& avatar_item);
-
-  // Creates a tutorial card to have the user confirm the last Chrome signin,
-  // Chrome sync will be delayed until the user either dismisses the tutorial,
-  // or configures sync through the "Settings" link.
-  views::View* CreateSigninConfirmationView();
-
-  // Creates a tutorial card to show the errors in the last Chrome signin.
-  views::View* CreateSigninErrorView();
-
-  views::View* CreateTutorialViewIfNeeded(const AvatarMenu::Item& item);
-
-  // Creates a tutorial card. If |stack_button| is true, places the button above
-  // the link otherwise places both on the same row with the link left aligned
-  // and button right aligned. The method sets |link| to point to the newly
-  // create link, |button| to the newly created button, and |tutorial_mode_| to
-  // the given |tutorial_mode|.
-  views::View*  CreateTutorialView(
-      profiles::TutorialMode tutorial_mode,
-      const base::string16& title_text,
-      const base::string16& content_text,
-      const base::string16& link_text,
-      const base::string16& button_text,
-      bool stack_button,
-      views::Link** link,
-      views::LabelButton** button,
-      views::ImageButton** close_button);
-
   // Creates a header for signin and sync error surfacing for the user menu.
   views::View* CreateSyncErrorViewIfNeeded();
 
@@ -208,14 +172,6 @@
   AccountButtonIndexes delete_account_button_map_;
   AccountButtonIndexes reauth_account_button_map_;
 
-  // Links and buttons displayed in the tutorial card.
-  views::LabelButton* tutorial_sync_settings_ok_button_;
-  views::Link* tutorial_sync_settings_link_;
-  views::LabelButton* tutorial_see_whats_new_button_;
-  views::Link* tutorial_not_you_link_;
-  views::Link* tutorial_learn_more_link_;
-  views::ImageButton* tutorial_close_button_;
-
   // Buttons in the signin/sync error header on top of the desktop user menu.
   views::LabelButton* sync_error_signin_button_;
   views::LabelButton* sync_error_passphrase_button_;
@@ -260,9 +216,6 @@
   // Active view mode.
   profiles::BubbleViewMode view_mode_;
 
-  // The current tutorial mode.
-  profiles::TutorialMode tutorial_mode_;
-
   // The GAIA service type provided in the response header.
   signin::GAIAServiceType gaia_service_type_;
 
diff --git a/chrome/browser/ui/views/profiles/user_manager_view.cc b/chrome/browser/ui/views/profiles/user_manager_view.cc
index 2b069658..1b55dfb8 100644
--- a/chrome/browser/ui/views/profiles/user_manager_view.cc
+++ b/chrome/browser/ui/views/profiles/user_manager_view.cc
@@ -135,7 +135,6 @@
 // static
 void UserManager::Show(
     const base::FilePath& profile_path_to_focus,
-    profiles::UserManagerTutorialMode tutorial_mode,
     profiles::UserManagerAction user_manager_action) {
   DCHECK(profile_path_to_focus != ProfileManager::GetGuestProfilePath());
 
@@ -165,7 +164,7 @@
   UserManagerView* user_manager = new UserManagerView();
   user_manager->set_user_manager_started_showing(base::Time::Now());
   profiles::CreateSystemProfileForUserManager(
-      profile_path_to_focus, tutorial_mode, user_manager_action,
+      profile_path_to_focus, user_manager_action,
       base::Bind(&UserManagerView::OnSystemProfileCreated,
                  base::Passed(base::WrapUnique(user_manager)),
                  base::Owned(new base::AutoReset<bool>(
diff --git a/chrome/browser/ui/views/tab_icon_view.cc b/chrome/browser/ui/views/tab_icon_view.cc
index 3b870b8..d9975ef4 100644
--- a/chrome/browser/ui/views/tab_icon_view.cc
+++ b/chrome/browser/ui/views/tab_icon_view.cc
@@ -122,7 +122,7 @@
   return "TabIconView";
 }
 
-void TabIconView::OnPaint(gfx::Canvas* canvas) {
+void TabIconView::PaintButtonContents(gfx::Canvas* canvas) {
   bool rendered = false;
 
   if (model_->ShouldTabIconViewAnimate()) {
diff --git a/chrome/browser/ui/views/tab_icon_view.h b/chrome/browser/ui/views/tab_icon_view.h
index 7ef95df..cc22a74 100644
--- a/chrome/browser/ui/views/tab_icon_view.h
+++ b/chrome/browser/ui/views/tab_icon_view.h
@@ -34,7 +34,7 @@
   // views::MenuButton:
   gfx::Size CalculatePreferredSize() const override;
   const char* GetClassName() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   void PaintThrobber(gfx::Canvas* canvas);
   void PaintFavicon(gfx::Canvas* canvas, const gfx::ImageSkia& image);
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button.cc b/chrome/browser/ui/views/tabs/alert_indicator_button.cc
index 0a7f1e65..9f1fd4c 100644
--- a/chrome/browser/ui/views/tabs/alert_indicator_button.cc
+++ b/chrome/browser/ui/views/tabs/alert_indicator_button.cc
@@ -216,22 +216,6 @@
   UpdateEnabledForMuteToggle();
 }
 
-void AlertIndicatorButton::OnPaint(gfx::Canvas* canvas) {
-  double opaqueness = 1.0;
-  if (fade_animation_) {
-    opaqueness = fade_animation_->GetCurrentValue();
-    if (alert_state_ == TabAlertState::NONE)
-      opaqueness = 1.0 - opaqueness;  // Fading out, not in.
-  } else if (is_dormant()) {
-    opaqueness = 0.5;
-  }
-  if (opaqueness < 1.0)
-    canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
-  ImageButton::OnPaint(canvas);
-  if (opaqueness < 1.0)
-    canvas->Restore();
-}
-
 bool AlertIndicatorButton::DoesIntersectRect(const views::View* target,
                                              const gfx::Rect& rect) const {
   // If this button is not enabled, Tab (the parent View) handles all mouse
@@ -282,6 +266,22 @@
   return views::ImageButton::IsTriggerableEvent(event);
 }
 
+void AlertIndicatorButton::PaintButtonContents(gfx::Canvas* canvas) {
+  double opaqueness = 1.0;
+  if (fade_animation_) {
+    opaqueness = fade_animation_->GetCurrentValue();
+    if (alert_state_ == TabAlertState::NONE)
+      opaqueness = 1.0 - opaqueness;  // Fading out, not in.
+  } else if (is_dormant()) {
+    opaqueness = 0.5;
+  }
+  if (opaqueness < 1.0)
+    canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
+  ImageButton::PaintButtonContents(canvas);
+  if (opaqueness < 1.0)
+    canvas->Restore();
+}
+
 gfx::ImageSkia AlertIndicatorButton::GetImageToPaint() {
   if (is_dormant())
     return views::ImageButton::images_[views::CustomButton::STATE_NORMAL];
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button.h b/chrome/browser/ui/views/tabs/alert_indicator_button.h
index ba0effe..1dbed5a8 100644
--- a/chrome/browser/ui/views/tabs/alert_indicator_button.h
+++ b/chrome/browser/ui/views/tabs/alert_indicator_button.h
@@ -68,7 +68,6 @@
   void OnMouseExited(const ui::MouseEvent& event) override;
   void OnMouseMoved(const ui::MouseEvent& event) override;
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-  void OnPaint(gfx::Canvas* canvas) override;
 
   // views::ViewTargeterDelegate
   bool DoesIntersectRect(const View* target,
@@ -79,6 +78,7 @@
 
   // views::CustomButton:
   bool IsTriggerableEvent(const ui::Event& event) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // views::ImageButton:
   gfx::ImageSkia GetImageToPaint() override;
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 6c5a019..fcac8658 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -263,8 +263,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 // NewTabButton
 //
-//  A subclass of button that hit-tests to the shape of the new tab button and
-//  does custom drawing.
+//  A subclass of ImageButton that hit-tests to the shape of the new tab button
+//  and does custom drawing.
 
 class NewTabButton : public views::ImageButton,
                      public views::MaskedTargeterDelegate {
@@ -284,7 +284,7 @@
   void OnMouseReleased(const ui::MouseEvent& event) override;
 #endif
   void OnGestureEvent(ui::GestureEvent* event) override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // views::MaskedTargeterDelegate:
   bool GetHitTestMask(gfx::Path* mask) const override;
@@ -361,7 +361,7 @@
   event->SetHandled();
 }
 
-void NewTabButton::OnPaint(gfx::Canvas* canvas) {
+void NewTabButton::PaintButtonContents(gfx::Canvas* canvas) {
   gfx::ScopedCanvas scoped_canvas(canvas);
   const int visible_height = GetLayoutSize(NEW_TAB_BUTTON).height();
   canvas->Translate(gfx::Vector2d(0, height() - visible_height));
diff --git a/chrome/browser/ui/views/toolbar/app_menu_button.cc b/chrome/browser/ui/views/toolbar/app_menu_button.cc
index f2cd131..461e0ac 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu_button.cc
@@ -143,9 +143,9 @@
   views::MenuButton::Layout();
 }
 
-void AppMenuButton::OnPaint(gfx::Canvas* canvas) {
+void AppMenuButton::PaintButtonContents(gfx::Canvas* canvas) {
   if (!animation_) {
-    views::MenuButton::OnPaint(canvas);
+    views::MenuButton::PaintButtonContents(canvas);
     return;
   }
 
diff --git a/chrome/browser/ui/views/toolbar/app_menu_button.h b/chrome/browser/ui/views/toolbar/app_menu_button.h
index 037081b3..804c5f6 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_button.h
+++ b/chrome/browser/ui/views/toolbar/app_menu_button.h
@@ -58,7 +58,7 @@
   // views::MenuButton:
   gfx::Size CalculatePreferredSize() const override;
   void Layout() override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // TabStripObserver:
   void TabInsertedAt(TabStripModel* tab_strip_model,
diff --git a/chrome/browser/ui/webui/options/certificate_manager_browsertest.js b/chrome/browser/ui/webui/options/certificate_manager_browsertest.js
index cb9e3d81..0db9129 100644
--- a/chrome/browser/ui/webui/options/certificate_manager_browsertest.js
+++ b/chrome/browser/ui/webui/options/certificate_manager_browsertest.js
@@ -248,7 +248,7 @@
                                'policy': false }],
                }],
            ]
-          ].forEach(CertificateManager.onPopulateTree)}));
+          ].forEach(CertificateManager.onPopulateTree);}));
   },
 };
 
diff --git a/chrome/browser/ui/webui/profile_helper.cc b/chrome/browser/ui/webui/profile_helper.cc
index 528292e..968b967 100644
--- a/chrome/browser/ui/webui/profile_helper.cc
+++ b/chrome/browser/ui/webui/profile_helper.cc
@@ -27,7 +27,7 @@
 
 void ShowUserManager(const ProfileManager::CreateCallback& callback) {
   if (!UserManager::IsShowing()) {
-    UserManager::Show(base::FilePath(), profiles::USER_MANAGER_NO_TUTORIAL,
+    UserManager::Show(base::FilePath(),
                       profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
   }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index 2d1181a..cd129cb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -45,6 +45,14 @@
 
 namespace {
 
+// These values are written to logs.  New enum values can be added, but existing
+// enums must never be renumbered or deleted and reused.
+enum PpdSourceForHistogram { kUser = 0, kScs = 1, kPpdSourceMax };
+
+void RecordPpdSource(const PpdSourceForHistogram& source) {
+  UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PpdSource", source, kPpdSourceMax);
+}
+
 void OnRemovedPrinter(bool success) {}
 
 std::unique_ptr<base::DictionaryValue> GetPrinterInfo(const Printer& printer) {
@@ -226,6 +234,7 @@
 
   // Verify a valid ppd path is present.
   if (!printer_ppd_path.empty()) {
+    RecordPpdSource(kUser);
     GURL tmp = net::FilePathToFileURL(base::FilePath(printer_ppd_path));
     if (!tmp.is_valid()) {
       LOG(ERROR) << "Invalid ppd path: " << printer_ppd_path;
@@ -234,6 +243,7 @@
     }
     printer->mutable_ppd_reference()->user_supplied_ppd_url = tmp.spec();
   } else if (!printer_manufacturer.empty() && !printer_model.empty()) {
+    RecordPpdSource(kScs);
     // Using the manufacturer and model, get a ppd reference.
     if (!ppd_provider_->GetPpdReference(printer_manufacturer, printer_model,
                                         printer->mutable_ppd_reference())) {
@@ -256,6 +266,8 @@
     std::unique_ptr<Printer> printer,
     chromeos::PrinterSetupResult result_code) {
   std::string printer_name = printer->display_name();
+  UMA_HISTOGRAM_ENUMERATION("Printing.CUPS.PrinterSetupResult", result_code,
+                            chromeos::PrinterSetupResult::kMaxValue);
   switch (result_code) {
     case chromeos::PrinterSetupResult::kSuccess: {
       auto* manager = PrintersManagerFactory::GetForBrowserContext(profile_);
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 4c37c95..edd8e44 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -557,7 +557,7 @@
 }
 
 void PeopleHandler::HandleManageOtherPeople(const base::ListValue* /* args */) {
-  UserManager::Show(base::FilePath(), profiles::USER_MANAGER_NO_TUTORIAL,
+  UserManager::Show(base::FilePath(),
                     profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 56b69361..cd80d9b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5026,6 +5026,9 @@
         "$root_out_dir/resources.pak",
       ]
     }
+    if (is_mac) {
+      deps += [ "//chrome:chrome_app" ]
+    }
 
     # This target should not require the Chrome executable to run.
     assert_no_deps = [ "//chrome" ]
diff --git a/chrome/test/perf/mach_ports_performancetest.cc b/chrome/test/perf/mach_ports_performancetest.cc
index 7887298a..e7b1d78 100644
--- a/chrome/test/perf/mach_ports_performancetest.cc
+++ b/chrome/test/perf/mach_ports_performancetest.cc
@@ -25,13 +25,13 @@
 class MachPortsTest : public InProcessBrowserTest {
  public:
   MachPortsTest() {
-    server_.ServeFilesFromSourceDirectory("data/mach_ports/moz");
+    embedded_test_server()->ServeFilesFromSourceDirectory(
+        "data/mach_ports/moz");
   }
 
-  void SetUp() override {
-    InProcessBrowserTest::SetUp();
-
-    ASSERT_TRUE(server_.Start());
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    ASSERT_TRUE(embedded_test_server()->Start());
   }
 
   void TearDown() override {
@@ -72,12 +72,12 @@
 
   // Adds a tab from the page cycler data at the specified domain.
   void AddTab(const std::string& domain) {
-    GURL url = server_.GetURL("/" + domain + "/").Resolve("?skip");
-    AddTabAtIndex(0, url, ui::PAGE_TRANSITION_TYPED);
+    AddTabAtIndex(
+        0, embedded_test_server()->GetURL("/" + domain + "/").Resolve("?skip"),
+        ui::PAGE_TRANSITION_TYPED);
   }
 
  private:
-  net::EmbeddedTestServer server_;
   std::vector<int> port_counts_;
 };
 
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index f4700d5..e8824c4 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -37,7 +37,7 @@
 #include "chromecast/browser/url_request_context_factory.h"
 #include "chromecast/common/global_descriptors.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
-#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory_impl.h"
 #include "chromecast/media/cma/backend/media_pipeline_backend_manager.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 #include "components/crash/content/app/breakpad_linux.h"
@@ -177,7 +177,7 @@
   DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
   if (!media_pipeline_backend_factory_) {
     media_pipeline_backend_factory_.reset(
-        new media::MediaPipelineBackendFactory(
+        new media::MediaPipelineBackendFactoryImpl(
             media_pipeline_backend_manager()));
   }
   return media_pipeline_backend_factory_.get();
@@ -197,9 +197,15 @@
 std::unique_ptr<::media::AudioManager>
 CastContentBrowserClient::CreateAudioManager(
     ::media::AudioLogFactory* audio_log_factory) {
+  // TODO(alokp): Consider switching off the mixer on audio platforms
+  // because we already have a mixer in the audio pipeline downstream of
+  // CastAudioManager.
+  bool use_mixer = true;
   return base::MakeUnique<media::CastAudioManager>(
       base::MakeUnique<::media::AudioThreadImpl>(), audio_log_factory,
-      media_pipeline_backend_manager());
+      base::MakeUnique<media::MediaPipelineBackendFactoryImpl>(
+          media_pipeline_backend_manager()),
+      GetMediaTaskRunner(), use_mixer);
 }
 
 std::unique_ptr<::media::CdmFactory>
diff --git a/chromecast/media/BUILD.gn b/chromecast/media/BUILD.gn
index 698f77f5..896700bdd 100644
--- a/chromecast/media/BUILD.gn
+++ b/chromecast/media/BUILD.gn
@@ -16,6 +16,7 @@
 
 test("cast_media_unittests") {
   sources = [
+    "audio/cast_audio_manager_unittest.cc",
     "audio/cast_audio_mixer_unittest.cc",
     "audio/cast_audio_output_stream_unittest.cc",
     "base/media_resource_tracker_unittest.cc",
@@ -29,6 +30,7 @@
     "//base/test:test_support",
     "//chromecast/base",
     "//chromecast/base/metrics:test_support",
+    "//chromecast/media/cma:test_support",
     "//chromecast/public",
     "//media",
     "//media/audio:test_support",
diff --git a/chromecast/media/audio/cast_audio_manager.cc b/chromecast/media/audio/cast_audio_manager.cc
index 2176cddc..3cd80d6f 100644
--- a/chromecast/media/audio/cast_audio_manager.cc
+++ b/chromecast/media/audio/cast_audio_manager.cc
@@ -8,9 +8,11 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/memory/ptr_util.h"
 #include "chromecast/media/audio/cast_audio_mixer.h"
 #include "chromecast/media/audio/cast_audio_output_stream.h"
-#include "chromecast/media/cma/backend/media_pipeline_backend_manager.h"
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
+#include "chromecast/public/media/media_pipeline_backend.h"
 
 namespace {
 // TODO(alokp): Query the preferred value from media backend.
@@ -30,22 +32,15 @@
 CastAudioManager::CastAudioManager(
     std::unique_ptr<::media::AudioThread> audio_thread,
     ::media::AudioLogFactory* audio_log_factory,
-    MediaPipelineBackendManager* backend_manager)
-    : CastAudioManager(std::move(audio_thread),
-                       audio_log_factory,
-                       backend_manager,
-                       new CastAudioMixer(
-                           base::Bind(&CastAudioManager::MakeMixerOutputStream,
-                                      base::Unretained(this)))) {}
-
-CastAudioManager::CastAudioManager(
-    std::unique_ptr<::media::AudioThread> audio_thread,
-    ::media::AudioLogFactory* audio_log_factory,
-    MediaPipelineBackendManager* backend_manager,
-    CastAudioMixer* audio_mixer)
+    std::unique_ptr<MediaPipelineBackendFactory> backend_factory,
+    scoped_refptr<base::SingleThreadTaskRunner> backend_task_runner,
+    bool use_mixer)
     : AudioManagerBase(std::move(audio_thread), audio_log_factory),
-      backend_manager_(backend_manager),
-      mixer_(audio_mixer) {}
+      backend_factory_(std::move(backend_factory)),
+      backend_task_runner_(std::move(backend_task_runner)) {
+  if (use_mixer)
+    mixer_ = base::MakeUnique<CastAudioMixer>(this);
+}
 
 CastAudioManager::~CastAudioManager() = default;
 
@@ -80,12 +75,6 @@
   return "Cast";
 }
 
-std::unique_ptr<MediaPipelineBackend>
-CastAudioManager::CreateMediaPipelineBackend(
-    const MediaPipelineDeviceParams& params) {
-  return backend_manager_->CreateMediaPipelineBackend(params);
-}
-
 void CastAudioManager::ReleaseOutputStream(::media::AudioOutputStream* stream) {
   // If |stream| is |mixer_output_stream_|, we should not use
   // AudioManagerBase::ReleaseOutputStream as we do not want the release
@@ -108,7 +97,7 @@
 
   // If |mixer_| exists, return a mixing stream.
   if (mixer_)
-    return mixer_->MakeStream(params, this);
+    return mixer_->MakeStream(params);
   else
     return new CastAudioOutputStream(params, this);
 }
@@ -121,7 +110,7 @@
 
   // If |mixer_| exists, return a mixing stream.
   if (mixer_)
-    return mixer_->MakeStream(params, this);
+    return mixer_->MakeStream(params);
   else
     return new CastAudioOutputStream(params, this);
 }
diff --git a/chromecast/media/audio/cast_audio_manager.h b/chromecast/media/audio/cast_audio_manager.h
index f12264a..7c95557 100644
--- a/chromecast/media/audio/cast_audio_manager.h
+++ b/chromecast/media/audio/cast_audio_manager.h
@@ -13,22 +13,19 @@
 namespace media {
 
 class CastAudioMixer;
-class MediaPipelineBackend;
-class MediaPipelineBackendManager;
-struct MediaPipelineDeviceParams;
+class MediaPipelineBackendFactory;
 
 class CastAudioManager : public ::media::AudioManagerBase {
  public:
-  CastAudioManager(std::unique_ptr<::media::AudioThread> audio_thread,
-                   ::media::AudioLogFactory* audio_log_factory,
-                   MediaPipelineBackendManager* backend_manager);
-  CastAudioManager(std::unique_ptr<::media::AudioThread> audio_thread,
-                   ::media::AudioLogFactory* audio_log_factory,
-                   MediaPipelineBackendManager* backend_manager,
-                   CastAudioMixer* audio_mixer);
+  CastAudioManager(
+      std::unique_ptr<::media::AudioThread> audio_thread,
+      ::media::AudioLogFactory* audio_log_factory,
+      std::unique_ptr<MediaPipelineBackendFactory> backend_factory,
+      scoped_refptr<base::SingleThreadTaskRunner> backend_task_runner,
+      bool use_mixer);
   ~CastAudioManager() override;
 
-  // AudioManager implementation.
+  // AudioManagerBase implementation.
   bool HasAudioOutputDevices() override;
   bool HasAudioInputDevices() override;
   void ShowAudioInputSettings() override;
@@ -37,15 +34,16 @@
   ::media::AudioParameters GetInputStreamParameters(
       const std::string& device_id) override;
   const char* GetName() override;
-
-  // AudioManagerBase implementation
   void ReleaseOutputStream(::media::AudioOutputStream* stream) override;
 
-  // This must be called on audio thread.
-  virtual std::unique_ptr<MediaPipelineBackend> CreateMediaPipelineBackend(
-      const MediaPipelineDeviceParams& params);
+  MediaPipelineBackendFactory* backend_factory() {
+    return backend_factory_.get();
+  }
+  base::SingleThreadTaskRunner* backend_task_runner() {
+    return backend_task_runner_.get();
+  }
 
- private:
+ protected:
   // AudioManagerBase implementation.
   ::media::AudioOutputStream* MakeLinearOutputStream(
       const ::media::AudioParameters& params,
@@ -67,13 +65,16 @@
       const ::media::AudioParameters& input_params) override;
 
   // Generates a CastAudioOutputStream for |mixer_|.
-  ::media::AudioOutputStream* MakeMixerOutputStream(
+  virtual ::media::AudioOutputStream* MakeMixerOutputStream(
       const ::media::AudioParameters& params);
 
-  MediaPipelineBackendManager* const backend_manager_;
+ private:
+  friend class CastAudioMixer;
+
+  std::unique_ptr<MediaPipelineBackendFactory> backend_factory_;
+  scoped_refptr<base::SingleThreadTaskRunner> backend_task_runner_;
   std::unique_ptr<::media::AudioOutputStream> mixer_output_stream_;
   std::unique_ptr<CastAudioMixer> mixer_;
-
   DISALLOW_COPY_AND_ASSIGN(CastAudioManager);
 };
 
diff --git a/chromecast/media/audio/cast_audio_manager_unittest.cc b/chromecast/media/audio/cast_audio_manager_unittest.cc
new file mode 100644
index 0000000..ef8ea959
--- /dev/null
+++ b/chromecast/media/audio/cast_audio_manager_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/audio/cast_audio_manager.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/test/test_message_loop.h"
+#include "chromecast/media/cma/test/mock_media_pipeline_backend.h"
+#include "chromecast/media/cma/test/mock_media_pipeline_backend_factory.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace chromecast {
+namespace media {
+namespace {
+
+const ::media::AudioParameters kDefaultAudioParams(
+    ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+    ::media::CHANNEL_LAYOUT_STEREO,
+    ::media::AudioParameters::kAudioCDSampleRate,
+    16,
+    256);
+
+class CastAudioManagerTest : public testing::Test {
+ public:
+  CastAudioManagerTest() : media_thread_("CastMediaThread") {
+    CHECK(media_thread_.Start());
+
+    backend_factory_ = new MockMediaPipelineBackendFactory();
+    audio_manager_ = base::MakeUnique<CastAudioManager>(
+        base::MakeUnique<::media::TestAudioThread>(), &audio_log_factory_,
+        base::WrapUnique(backend_factory_), media_thread_.task_runner(), false);
+  }
+
+  ~CastAudioManagerTest() override { audio_manager_->Shutdown(); }
+
+ protected:
+  base::TestMessageLoop message_loop_;
+  base::Thread media_thread_;
+  ::media::FakeAudioLogFactory audio_log_factory_;
+  std::unique_ptr<CastAudioManager> audio_manager_;
+
+  // Owned by |audio_manager_|
+  MockMediaPipelineBackendFactory* backend_factory_;
+};
+
+TEST_F(CastAudioManagerTest, MakeAudioOutputStreamProxy) {
+  StrictMock<MockAudioDecoder> audio_decoder;
+  EXPECT_CALL(audio_decoder, SetDelegate(_)).Times(1);
+  EXPECT_CALL(audio_decoder, SetConfig(_)).WillOnce(Return(true));
+
+  auto backend = base::MakeUnique<StrictMock<MockMediaPipelineBackend>>();
+  EXPECT_CALL(*backend, CreateAudioDecoder()).WillOnce(Return(&audio_decoder));
+  EXPECT_CALL(*backend, Initialize()).WillOnce(Return(true));
+
+  EXPECT_CALL(*backend_factory_, CreateBackend(_))
+      .WillOnce(Invoke([&backend](const MediaPipelineDeviceParams&) {
+        return std::move(backend);
+      }));
+
+  ::media::AudioOutputStream* stream =
+      audio_manager_->MakeAudioOutputStreamProxy(kDefaultAudioParams,
+                                                 std::string());
+  EXPECT_TRUE(stream->Open());
+  stream->Close();
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/audio/cast_audio_mixer.cc b/chromecast/media/audio/cast_audio_mixer.cc
index 4d236a1..a7ed6f5 100644
--- a/chromecast/media/audio/cast_audio_mixer.cc
+++ b/chromecast/media/audio/cast_audio_mixer.cc
@@ -4,7 +4,9 @@
 
 #include "chromecast/media/audio/cast_audio_mixer.h"
 
+#include "base/bind.h"
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
 #include "chromecast/media/audio/cast_audio_output_stream.h"
 #include "media/base/audio_timestamp_helper.h"
@@ -21,30 +23,34 @@
 
 class CastAudioMixer::MixerProxyStream
     : public ::media::AudioOutputStream,
-      private ::media::AudioConverter::InputCallback {
+      public ::media::AudioConverter::InputCallback {
  public:
   MixerProxyStream(const ::media::AudioParameters& input_params,
                    const ::media::AudioParameters& output_params,
-                   CastAudioManager* audio_manager,
                    CastAudioMixer* audio_mixer)
-      : audio_manager_(audio_manager),
-        audio_mixer_(audio_mixer),
-        source_callback_(nullptr),
+      : audio_mixer_(audio_mixer),
         input_params_(input_params),
         output_params_(output_params),
         opened_(false),
-        volume_(1.0) {}
+        volume_(1.0),
+        source_callback_(nullptr) {
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  }
 
-  ~MixerProxyStream() override {}
+  ~MixerProxyStream() override {
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  }
 
   void OnError() {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
-
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
     if (source_callback_)
       source_callback_->OnError();
   }
 
  private:
+  // ResamplerProxy is an intermediate filter between MixerProxyStream and
+  // CastAudioMixer::output_stream_ whose only responsibility is to resample
+  // audio to the sample rate expected by CastAudioMixer::output_stream_.
   class ResamplerProxy : public ::media::AudioConverter::InputCallback {
    public:
     ResamplerProxy(::media::AudioConverter::InputCallback* input_callback,
@@ -53,6 +59,7 @@
       resampler_.reset(
           new ::media::AudioConverter(input_params, output_params, false));
       resampler_->AddInput(input_callback);
+      DETACH_FROM_THREAD(backend_thread_checker_);
     }
 
     ~ResamplerProxy() override {}
@@ -61,20 +68,21 @@
     // ::media::AudioConverter::InputCallback implementation
     double ProvideInput(::media::AudioBus* audio_bus,
                         uint32_t frames_delayed) override {
+      DCHECK_CALLED_ON_VALID_THREAD(backend_thread_checker_);
       resampler_->ConvertWithDelay(frames_delayed, audio_bus);
-
       // Volume multiplier has already been applied by |resampler_|.
       return 1.0;
     }
 
     std::unique_ptr<::media::AudioConverter> resampler_;
 
+    THREAD_CHECKER(backend_thread_checker_);
     DISALLOW_COPY_AND_ASSIGN(ResamplerProxy);
   };
 
   // ::media::AudioOutputStream implementation
   bool Open() override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
     ::media::AudioParameters::Format format = input_params_.format();
     DCHECK((format == ::media::AudioParameters::AUDIO_PCM_LINEAR) ||
@@ -93,46 +101,52 @@
   }
 
   void Close() override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
     if (proxy_)
       Stop();
     if (opened_)
       audio_mixer_->Unregister(this);
+
     // Signal to the manager that we're closed and can be removed.
     // This should be the last call in the function as it deletes "this".
-    audio_manager_->ReleaseOutputStream(this);
+    audio_mixer_->audio_manager_->ReleaseOutputStream(this);
   }
 
   void Start(AudioSourceCallback* source_callback) override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
     DCHECK(source_callback);
 
     if (!opened_ || proxy_)
       return;
     source_callback_ = source_callback;
-    proxy_.reset(new ResamplerProxy(this, input_params_, output_params_));
+    proxy_ =
+        base::MakeUnique<ResamplerProxy>(this, input_params_, output_params_);
     audio_mixer_->AddInput(proxy_.get());
   }
 
   void Stop() override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
     if (!proxy_)
       return;
     audio_mixer_->RemoveInput(proxy_.get());
+    // Once the above function returns it is guaranteed that proxy_ or
+    // source_callback_ would not be used on the backend thread, so it is safe
+    // to reset them.
     proxy_.reset();
     source_callback_ = nullptr;
   }
 
   void SetVolume(double volume) override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
+    base::AutoLock auto_lock(volume_lock_);
     volume_ = volume;
   }
 
   void GetVolume(double* volume) override {
-    DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+    DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
     *volume = volume_;
   }
@@ -140,56 +154,57 @@
   // ::media::AudioConverter::InputCallback implementation
   double ProvideInput(::media::AudioBus* audio_bus,
                       uint32_t frames_delayed) override {
+    // Called on backend thread. Member variables accessed from both backend
+    // and audio thread must be thread-safe.
     DCHECK(source_callback_);
 
     const base::TimeDelta delay = ::media::AudioTimestampHelper::FramesToTime(
         frames_delayed, input_params_.sample_rate());
     source_callback_->OnMoreData(delay, base::TimeTicks::Now(), 0, audio_bus);
+
+    base::AutoLock auto_lock(volume_lock_);
     return volume_;
   }
 
-  CastAudioManager* const audio_manager_;
   CastAudioMixer* const audio_mixer_;
-
-  std::unique_ptr<ResamplerProxy> proxy_;
-  AudioSourceCallback* source_callback_;
   const ::media::AudioParameters input_params_;
   const ::media::AudioParameters output_params_;
+
   bool opened_;
   double volume_;
+  base::Lock volume_lock_;
+  AudioSourceCallback* source_callback_;
+  std::unique_ptr<ResamplerProxy> proxy_;
 
+  THREAD_CHECKER(audio_thread_checker_);
   DISALLOW_COPY_AND_ASSIGN(MixerProxyStream);
 };
 
-CastAudioMixer::CastAudioMixer(const RealStreamFactory& real_stream_factory)
-    : error_(false),
-      real_stream_factory_(real_stream_factory),
-      output_stream_(nullptr) {
+CastAudioMixer::CastAudioMixer(CastAudioManager* audio_manager)
+    : audio_manager_(audio_manager), error_(false), output_stream_(nullptr) {
   output_params_ = ::media::AudioParameters(
       ::media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
       ::media::CHANNEL_LAYOUT_STEREO, kSampleRate, kBitsPerSample,
       kFramesPerBuffer);
   mixer_.reset(
       new ::media::AudioConverter(output_params_, output_params_, false));
-  thread_checker_.DetachFromThread();
+  DETACH_FROM_THREAD(audio_thread_checker_);
 }
 
 CastAudioMixer::~CastAudioMixer() {}
 
 ::media::AudioOutputStream* CastAudioMixer::MakeStream(
-    const ::media::AudioParameters& params,
-    CastAudioManager* audio_manager) {
-  return new MixerProxyStream(params, output_params_, audio_manager, this);
+    const ::media::AudioParameters& params) {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  return new MixerProxyStream(params, output_params_, this);
 }
 
 bool CastAudioMixer::Register(MixerProxyStream* proxy_stream) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
   DCHECK(std::find(proxy_streams_.begin(), proxy_streams_.end(),
                    proxy_stream) == proxy_streams_.end());
 
-  // If the mixer is in an error state, do not allow any new
-  // registrations until every active stream has been
-  // unregistered.
+  // Do not allow opening new streams while in error state.
   if (error_)
     return false;
 
@@ -198,7 +213,7 @@
   // is not opened properly.
   if (proxy_streams_.empty()) {
     DCHECK(!output_stream_);
-    output_stream_ = real_stream_factory_.Run(output_params_);
+    output_stream_ = audio_manager_->MakeMixerOutputStream(output_params_);
     if (!output_stream_->Open()) {
       output_stream_->Close();
       output_stream_ = nullptr;
@@ -206,17 +221,16 @@
     }
   }
 
-  proxy_streams_.push_back(proxy_stream);
+  proxy_streams_.insert(proxy_stream);
   return true;
 }
 
 void CastAudioMixer::Unregister(MixerProxyStream* proxy_stream) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  auto it =
-      std::find(proxy_streams_.begin(), proxy_streams_.end(), proxy_stream);
-  DCHECK(it != proxy_streams_.end());
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  DCHECK(std::find(proxy_streams_.begin(), proxy_streams_.end(),
+                   proxy_stream) != proxy_streams_.end());
 
-  proxy_streams_.erase(it);
+  proxy_streams_.erase(proxy_stream);
 
   // Reset the state once all streams have been unregistered.
   if (proxy_streams_.empty()) {
@@ -230,19 +244,25 @@
 
 void CastAudioMixer::AddInput(
     ::media::AudioConverter::InputCallback* input_callback) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
   // Start the backend if there are no current inputs.
   if (mixer_->empty() && output_stream_)
     output_stream_->Start(this);
+
+  base::AutoLock auto_lock(mixer_lock_);
   mixer_->AddInput(input_callback);
 }
 
 void CastAudioMixer::RemoveInput(
     ::media::AudioConverter::InputCallback* input_callback) {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
-  mixer_->RemoveInput(input_callback);
+  {
+    base::AutoLock auto_lock(mixer_lock_);
+    mixer_->RemoveInput(input_callback);
+  }
+
   // Stop |output_stream_| if there are no inputs and the stream is running.
   if (mixer_->empty() && output_stream_)
     output_stream_->Stop();
@@ -252,36 +272,28 @@
                                base::TimeTicks /* delay_timestamp */,
                                int /* prior_frames_skipped */,
                                ::media::AudioBus* dest) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
+  // Called on backend thread.
   uint32_t frames_delayed = ::media::AudioTimestampHelper::TimeToFrames(
       delay, output_params_.sample_rate());
+
+  base::AutoLock auto_lock(mixer_lock_);
   mixer_->ConvertWithDelay(frames_delayed, dest);
   return dest->frames();
 }
 
 void CastAudioMixer::OnError() {
-  DCHECK(thread_checker_.CalledOnValidThread());
+  // Called on backend thread.
+  audio_manager_->GetTaskRunner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CastAudioMixer::HandleError, base::Unretained(this)));
+}
 
-  // TODO(ameyak): Add rate limiting. If errors are seen to occur
-  //               above some arbitrary value in a specified amount
-  //               of time, signal errors to all currently active
-  //               streams and close.
+void CastAudioMixer::HandleError() {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
 
-  output_stream_->Stop();
-  output_stream_->Close();
-  output_stream_ = real_stream_factory_.Run(output_params_);
-
-  if (output_stream_->Open()) {
-    output_stream_->Start(this);
-  } else {
-    // Assume error state
-    output_stream_->Close();
-    output_stream_ = nullptr;
-    error_ = true;
-    for (auto it = proxy_streams_.begin(); it != proxy_streams_.end(); it++)
-      (*it)->OnError();
-  }
+  error_ = true;
+  for (auto it = proxy_streams_.begin(); it != proxy_streams_.end(); ++it)
+    (*it)->OnError();
 }
 
 }  // namespace media
diff --git a/chromecast/media/audio/cast_audio_mixer.h b/chromecast/media/audio/cast_audio_mixer.h
index 7927f4259..0a67090 100644
--- a/chromecast/media/audio/cast_audio_mixer.h
+++ b/chromecast/media/audio/cast_audio_mixer.h
@@ -6,7 +6,7 @@
 #define CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_MIXER_H_
 
 #include <memory>
-#include <vector>
+#include <set>
 
 #include "base/callback.h"
 #include "base/macros.h"
@@ -25,15 +25,11 @@
 // stream down to a single AudioOutputStream to be rendered by the CMA backend.
 class CastAudioMixer : public ::media::AudioOutputStream::AudioSourceCallback {
  public:
-  using RealStreamFactory = base::Callback<::media::AudioOutputStream*(
-      const ::media::AudioParameters&)>;
-
-  explicit CastAudioMixer(const RealStreamFactory& real_stream_factory);
+  explicit CastAudioMixer(CastAudioManager* audio_manager);
   ~CastAudioMixer() override;
 
   virtual ::media::AudioOutputStream* MakeStream(
-      const ::media::AudioParameters& params,
-      CastAudioManager* audio_manager);
+      const ::media::AudioParameters& params);
 
  private:
   class MixerProxyStream;
@@ -43,7 +39,6 @@
                  base::TimeTicks delay_timestamp,
                  int prior_frames_skipped,
                  ::media::AudioBus* dest) override;
-
   void OnError() override;
 
   // MixedAudioOutputStreams call Register on opening and AddInput on starting.
@@ -51,17 +46,17 @@
   void Unregister(MixerProxyStream* proxy_stream);
   void AddInput(::media::AudioConverter::InputCallback* input_callback);
   void RemoveInput(::media::AudioConverter::InputCallback* input_callback);
+  void HandleError();
 
-  base::ThreadChecker thread_checker_;
-  std::unique_ptr<::media::AudioConverter> mixer_;
+  CastAudioManager* const audio_manager_;
   bool error_;
-
-  const RealStreamFactory real_stream_factory_;
-  ::media::AudioOutputStream* output_stream_;
+  std::set<MixerProxyStream*> proxy_streams_;
+  std::unique_ptr<::media::AudioConverter> mixer_;
+  base::Lock mixer_lock_;
   ::media::AudioParameters output_params_;
+  ::media::AudioOutputStream* output_stream_;
 
-  std::vector<MixerProxyStream*> proxy_streams_;
-
+  THREAD_CHECKER(audio_thread_checker_);
   DISALLOW_COPY_AND_ASSIGN(CastAudioMixer);
 };
 
diff --git a/chromecast/media/audio/cast_audio_mixer_unittest.cc b/chromecast/media/audio/cast_audio_mixer_unittest.cc
index 9477967..131c087 100644
--- a/chromecast/media/audio/cast_audio_mixer_unittest.cc
+++ b/chromecast/media/audio/cast_audio_mixer_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
 #include "chromecast/media/audio/cast_audio_output_stream.h"
+#include "chromecast/media/cma/test/mock_media_pipeline_backend_factory.h"
 #include "media/audio/test_audio_thread.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -25,10 +26,12 @@
 namespace media {
 namespace {
 
-using ::testing::_;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::StrictMock;
+using testing::_;
+using testing::Assign;
+using testing::Invoke;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
 
 // Utility functions
 ::media::AudioParameters GetAudioParams() {
@@ -87,11 +90,12 @@
 
 class MockCastAudioManager : public CastAudioManager {
  public:
-  MockCastAudioManager(CastAudioMixer* audio_mixer)
+  MockCastAudioManager()
       : CastAudioManager(base::MakeUnique<::media::TestAudioThread>(),
                          nullptr,
                          nullptr,
-                         audio_mixer) {
+                         nullptr,
+                         true /* use_mixer */) {
     ON_CALL(*this, ReleaseOutputStream(_))
         .WillByDefault(
             Invoke(this, &MockCastAudioManager::ReleaseOutputStreamConcrete));
@@ -108,69 +112,42 @@
   }
 };
 
-class MockCastAudioMixer : public CastAudioMixer {
- public:
-  explicit MockCastAudioMixer(const RealStreamFactory& real_stream_factory)
-      : CastAudioMixer(real_stream_factory) {
-    ON_CALL(*this, MakeStream(_, _))
-        .WillByDefault(Invoke(this, &MockCastAudioMixer::MakeStreamConcrete));
-  }
-
-  MOCK_METHOD2(
-      MakeStream,
-      ::media::AudioOutputStream*(const ::media::AudioParameters& params,
-                                  CastAudioManager* audio_manager));
-
- private:
-  ::media::AudioOutputStream* MakeStreamConcrete(
-      const ::media::AudioParameters& params,
-      CastAudioManager* audio_manager) {
-    return CastAudioMixer::MakeStream(params, audio_manager);
-  }
-};
-
 // Generates StrictMocks of Mixer, Manager, and Mixer OutputStream.
 class CastAudioMixerTest : public ::testing::Test {
  public:
-  CastAudioMixerTest() {}
+  CastAudioMixerTest() : source_callback_(nullptr) {}
   ~CastAudioMixerTest() override {}
 
  protected:
   void SetUp() override {
-    // |this| will outlive |mock_mixer_|
-    mock_mixer_ = new StrictMock<MockCastAudioMixer>(
-        base::Bind(&CastAudioMixerTest::MakeMixerOutputStreamProxy,
-                   base::Unretained(this)));
-    mock_manager_.reset(new StrictMock<MockCastAudioManager>(mock_mixer_));
+    mock_manager_.reset(new StrictMock<MockCastAudioManager>());
     mock_mixer_stream_.reset(new StrictMock<MockCastAudioOutputStream>(
         GetAudioParams(), mock_manager_.get()));
+
+    ON_CALL(*mock_manager_, MakeMixerOutputStream(_))
+        .WillByDefault(Return(mock_mixer_stream_.get()));
+    ON_CALL(*mock_mixer_stream_, Start(_))
+        .WillByDefault(SaveArg<0>(&source_callback_));
+    ON_CALL(*mock_mixer_stream_, Stop())
+        .WillByDefault(Assign(&source_callback_, nullptr));
   }
 
   void TearDown() override { mock_manager_->Shutdown(); }
 
   MockCastAudioManager& mock_manager() { return *mock_manager_; }
-
-  MockCastAudioMixer& mock_mixer() { return *mock_mixer_; }
-
   MockCastAudioOutputStream& mock_mixer_stream() { return *mock_mixer_stream_; }
 
   ::media::AudioOutputStream* CreateMixerStream() {
-    EXPECT_CALL(*mock_mixer_, MakeStream(_, &mock_manager()));
     return mock_manager_->MakeAudioOutputStream(
         GetAudioParams(), "", ::media::AudioManager::LogCallback());
   }
 
- private:
-  ::media::AudioOutputStream* MakeMixerOutputStreamProxy(
-      const ::media::AudioParameters& audio_params) {
-    return mock_manager_ ? mock_manager_->MakeMixerOutputStream(audio_params)
-                         : nullptr;
-  }
-
   base::MessageLoop message_loop_;
-  MockCastAudioMixer* mock_mixer_;
   std::unique_ptr<MockCastAudioManager> mock_manager_;
   std::unique_ptr<MockCastAudioOutputStream> mock_mixer_stream_;
+
+  // Saved params passed to |mock_mixer_stream_|.
+  ::media::AudioOutputStream::AudioSourceCallback* source_callback_;
 };
 
 TEST_F(CastAudioMixerTest, Volume) {
@@ -234,7 +211,7 @@
   EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
   ASSERT_TRUE(stream->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+  EXPECT_CALL(mock_mixer_stream(), Start(_));
   stream->Start(&source);
   stream->Start(&source);
 
@@ -254,7 +231,7 @@
   EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
   ASSERT_TRUE(stream->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer())).Times(2);
+  EXPECT_CALL(mock_mixer_stream(), Start(_)).Times(2);
   EXPECT_CALL(mock_mixer_stream(), Stop()).Times(2);
   stream->Start(&source);
   stream->Stop();
@@ -283,7 +260,7 @@
   for (auto* stream : streams)
     ASSERT_TRUE(stream->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+  EXPECT_CALL(mock_mixer_stream(), Start(_));
   for (unsigned int i = 0; i < streams.size(); i++)
     streams[i]->Start(sources[i].get());
 
@@ -296,7 +273,7 @@
 
     for (auto& source : sources)
       EXPECT_CALL(*source, OnMoreData(_, _, _, _));
-    mock_mixer_stream().SignalPull(&mock_mixer(), base::TimeDelta());
+    mock_mixer_stream().SignalPull(source_callback_, base::TimeDelta());
 
     EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
     stream->Close();
@@ -324,7 +301,7 @@
     ASSERT_TRUE(stream1->Open());
     ASSERT_TRUE(stream2->Open());
 
-    EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+    EXPECT_CALL(mock_mixer_stream(), Start(_));
     stream1->Start(&source);
     stream2->Start(&source);
 
@@ -338,7 +315,7 @@
   }
 }
 
-TEST_F(CastAudioMixerTest, OnErrorRecovery) {
+TEST_F(CastAudioMixerTest, OnError) {
   MockAudioSourceCallback source;
   std::vector<::media::AudioOutputStream*> streams;
 
@@ -353,79 +330,36 @@
   for (auto* stream : streams)
     ASSERT_TRUE(stream->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+  EXPECT_CALL(mock_mixer_stream(), Start(_));
   streams.front()->Start(&source);
 
-  EXPECT_CALL(mock_mixer_stream(), Stop());
-  EXPECT_CALL(mock_mixer_stream(), Close());
-  EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
-      .WillOnce(Return(&mock_mixer_stream()));
-  EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
-
-  // The MockAudioSourceCallback should not receive any call OnError.
-  mock_mixer_stream().SignalError(&mock_mixer());
-
-  // Try to add another stream.
-  streams.push_back(CreateMixerStream());
-  ASSERT_TRUE(streams.back());
-  ASSERT_TRUE(streams.back()->Open());
-
-  EXPECT_CALL(mock_mixer_stream(), Stop());
-  EXPECT_CALL(mock_mixer_stream(), Close());
-  for (auto* stream : streams) {
-    EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
-    stream->Close();
-  }
-}
-
-TEST_F(CastAudioMixerTest, OnErrorNoRecovery) {
-  MockAudioSourceCallback source;
-  std::vector<::media::AudioOutputStream*> streams;
-
-  streams.push_back(CreateMixerStream());
-  streams.push_back(CreateMixerStream());
-  for (auto* stream : streams)
-    ASSERT_TRUE(stream);
-
-  EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
-      .WillOnce(Return(&mock_mixer_stream()));
-  EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
-  for (auto* stream : streams)
-    ASSERT_TRUE(stream->Open());
-
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
-  streams.front()->Start(&source);
-
-  EXPECT_CALL(mock_mixer_stream(), Stop());
-  EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
-      .WillOnce(Return(&mock_mixer_stream()));
-  EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(false));
-  EXPECT_CALL(mock_mixer_stream(), Close()).Times(2);
+  // Note that error will only be triggered on the first stream because that
+  // is the only stream that has been started.
   EXPECT_CALL(source, OnError());
-
-  // The MockAudioSourceCallback should receive an OnError call.
-  mock_mixer_stream().SignalError(&mock_mixer());
+  mock_mixer_stream().SignalError(source_callback_);
+  base::RunLoop().RunUntilIdle();
 
   // Try to add another stream.
   streams.push_back(CreateMixerStream());
   ASSERT_TRUE(streams.back());
   ASSERT_FALSE(streams.back()->Open());
 
+  EXPECT_CALL(mock_mixer_stream(), Stop());
+  EXPECT_CALL(mock_mixer_stream(), Close());
   for (auto* stream : streams) {
     EXPECT_CALL(mock_manager(), ReleaseOutputStream(stream));
     stream->Close();
   }
+  streams.clear();
 
   // Now that the state has been refreshed, attempt to open a stream.
-  streams.clear();
   streams.push_back(CreateMixerStream());
   EXPECT_CALL(mock_manager(), MakeMixerOutputStream(_))
       .WillOnce(Return(&mock_mixer_stream()));
   EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
   ASSERT_TRUE(streams.front()->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+  EXPECT_CALL(mock_mixer_stream(), Start(_));
   streams.front()->Start(&source);
 
   EXPECT_CALL(mock_mixer_stream(), Stop());
@@ -443,14 +377,14 @@
   EXPECT_CALL(mock_mixer_stream(), Open()).WillOnce(Return(true));
   ASSERT_TRUE(stream->Open());
 
-  EXPECT_CALL(mock_mixer_stream(), Start(&mock_mixer()));
+  EXPECT_CALL(mock_mixer_stream(), Start(_));
   stream->Start(&source);
 
   // |delay| is the same because the Mixer and stream are
   // using the same AudioParameters.
   base::TimeDelta delay = base::TimeDelta::FromMicroseconds(1000);
   EXPECT_CALL(source, OnMoreData(delay, _, 0, _));
-  mock_mixer_stream().SignalPull(&mock_mixer(), delay);
+  mock_mixer_stream().SignalPull(source_callback_, delay);
 
   EXPECT_CALL(mock_mixer_stream(), Stop());
   EXPECT_CALL(mock_mixer_stream(), Close());
diff --git a/chromecast/media/audio/cast_audio_output_stream.cc b/chromecast/media/audio/cast_audio_output_stream.cc
index 0735211..d0d7419 100644
--- a/chromecast/media/audio/cast_audio_output_stream.cc
+++ b/chromecast/media/audio/cast_audio_output_stream.cc
@@ -9,19 +9,30 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "chromecast/base/metrics/cast_metrics_helper.h"
 #include "chromecast/base/task_runner_impl.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
 #include "chromecast/media/cma/base/decoder_buffer_adapter.h"
 #include "chromecast/public/media/decoder_config.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 #include "chromecast/public/media/media_pipeline_device_params.h"
 #include "chromecast/public/volume_control.h"
 #include "media/audio/audio_device_description.h"
+#include "media/base/audio_timestamp_helper.h"
 #include "media/base/decoder_buffer.h"
 
 namespace {
 const int kMaxQueuedDataMs = 1000;
+
+void SignalWaitableEvent(bool* success,
+                         base::WaitableEvent* waitable_event,
+                         bool result) {
+  *success = result;
+  waitable_event->Signal();
+}
 }  // namespace
 
 namespace chromecast {
@@ -33,17 +44,29 @@
 class CastAudioOutputStream::Backend
     : public MediaPipelineBackend::Decoder::Delegate {
  public:
-  using PushBufferCompletionCallback = base::Callback<void(bool)>;
+  using OpenCompletionCallback = base::OnceCallback<void(bool)>;
 
-  Backend() : decoder_(nullptr), first_start_(true), error_(false) {
-    thread_checker_.DetachFromThread();
+  Backend(const ::media::AudioParameters& audio_params)
+      : audio_params_(audio_params),
+        timestamp_helper_(audio_params_.sample_rate()),
+        buffer_duration_(audio_params.GetBufferDuration()),
+        first_start_(true),
+        push_in_progress_(false),
+        decoder_(nullptr),
+        source_callback_(nullptr),
+        weak_factory_(this) {
+    DETACH_FROM_THREAD(thread_checker_);
   }
-  ~Backend() override {}
+  ~Backend() override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    if (backend_ && !first_start_)  // Only stop the backend if it was started.
+      backend_->Stop();
+  }
 
-  bool Open(const ::media::AudioParameters& audio_params,
-            CastAudioManager* audio_manager) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(audio_manager);
+  void Open(MediaPipelineBackendFactory* backend_factory,
+            OpenCompletionCallback completion_cb) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(backend_factory);
     DCHECK(backend_ == nullptr);
 
     backend_task_runner_.reset(new TaskRunnerImpl());
@@ -52,38 +75,44 @@
         MediaPipelineDeviceParams::kAudioStreamSoundEffects,
         backend_task_runner_.get(), AudioContentType::kMedia,
         ::media::AudioDeviceDescription::kDefaultDeviceId);
-    backend_ = audio_manager->CreateMediaPipelineBackend(device_params);
-    if (!backend_)
-      return false;
+    backend_ = backend_factory->CreateBackend(device_params);
+    if (!backend_) {
+      std::move(completion_cb).Run(false);
+      return;
+    }
 
     decoder_ = backend_->CreateAudioDecoder();
-    if (!decoder_)
-      return false;
+    if (!decoder_) {
+      std::move(completion_cb).Run(false);
+      return;
+    }
     decoder_->SetDelegate(this);
 
     AudioConfig audio_config;
     audio_config.codec = kCodecPCM;
     audio_config.sample_format = kSampleFormatS16;
-    audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8;
-    audio_config.channel_number = audio_params.channels();
-    audio_config.samples_per_second = audio_params.sample_rate();
-    if (!decoder_->SetConfig(audio_config))
-      return false;
+    audio_config.bytes_per_channel = audio_params_.bits_per_sample() / 8;
+    audio_config.channel_number = audio_params_.channels();
+    audio_config.samples_per_second = audio_params_.sample_rate();
+    if (!decoder_->SetConfig(audio_config)) {
+      std::move(completion_cb).Run(false);
+      return;
+    }
 
-    return backend_->Initialize();
+    if (!backend_->Initialize()) {
+      std::move(completion_cb).Run(false);
+      return;
+    }
+
+    audio_bus_ = ::media::AudioBus::Create(audio_params_);
+    decoder_buffer_ = new DecoderBufferAdapter(
+        new ::media::DecoderBuffer(audio_params_.GetBytesPerBuffer()));
+    timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
+    std::move(completion_cb).Run(true);
   }
 
-  void Close() {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    if (backend_ && !first_start_)  // Only stop the backend if it was started.
-      backend_->Stop();
-    backend_.reset();
-    backend_task_runner_.reset();
-  }
-
-  void Start() {
-    DCHECK(thread_checker_.CalledOnValidThread());
+  void Start(AudioSourceCallback* source_callback) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(backend_);
 
     if (first_start_) {
@@ -92,66 +121,100 @@
     } else {
       backend_->Resume();
     }
+
+    source_callback_ = source_callback;
+    next_push_time_ = base::TimeTicks::Now();
+    if (!push_in_progress_) {
+      push_in_progress_ = true;
+      PushBuffer();
+    }
   }
 
-  void Stop() {
-    DCHECK(thread_checker_.CalledOnValidThread());
+  void Stop(base::OnceClosure completion_cb) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(backend_);
 
     backend_->Pause();
-  }
-
-  void PushBuffer(scoped_refptr<media::DecoderBufferBase> decoder_buffer,
-                  const PushBufferCompletionCallback& completion_cb) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(decoder_);
-    DCHECK(!completion_cb.is_null());
-    DCHECK(completion_cb_.is_null());
-    if (error_) {
-      completion_cb.Run(false);
-      return;
-    }
-
-    backend_buffer_ = decoder_buffer;
-    completion_cb_ = completion_cb;
-    BufferStatus status = decoder_->PushBuffer(backend_buffer_.get());
-    if (status != MediaPipelineBackend::kBufferPending)
-      OnPushBufferComplete(status);
+    source_callback_ = nullptr;
+    std::move(completion_cb).Run();
   }
 
   void SetVolume(double volume) {
-    DCHECK(thread_checker_.CalledOnValidThread());
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(decoder_);
     decoder_->SetVolume(volume);
   }
 
-  MediaPipelineBackend::AudioDecoder::RenderingDelay GetRenderingDelay() {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(decoder_);
-    return decoder_->GetRenderingDelay();
+ private:
+  void PushBuffer() {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(push_in_progress_);
+
+    if (!source_callback_) {
+      push_in_progress_ = false;
+      return;
+    }
+
+    MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay =
+        decoder_->GetRenderingDelay();
+    base::TimeDelta delay =
+        base::TimeDelta::FromMicroseconds(rendering_delay.delay_microseconds);
+    base::TimeTicks delay_timestamp =
+        base::TimeTicks() + base::TimeDelta::FromMicroseconds(
+                                rendering_delay.timestamp_microseconds);
+    int frame_count = source_callback_->OnMoreData(delay, delay_timestamp, 0,
+                                                   audio_bus_.get());
+    VLOG(3) << "frames_filled=" << frame_count << " with latency=" << delay;
+
+    DCHECK_EQ(frame_count, audio_bus_->frames());
+    DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()),
+              frame_count * audio_params_.GetBytesPerFrame());
+    audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8,
+                              decoder_buffer_->writable_data());
+    decoder_buffer_->set_timestamp(timestamp_helper_.GetTimestamp());
+    timestamp_helper_.AddFrames(frame_count);
+
+    BufferStatus status = decoder_->PushBuffer(decoder_buffer_.get());
+    if (status != MediaPipelineBackend::kBufferPending)
+      OnPushBufferComplete(status);
   }
 
- private:
   // MediaPipelineBackend::Decoder::Delegate implementation
   void OnPushBufferComplete(BufferStatus status) override {
-    DCHECK(thread_checker_.CalledOnValidThread());
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK_NE(status, MediaPipelineBackend::kBufferPending);
 
-    // |completion_cb_| may be null if OnDecoderError was called.
-    if (completion_cb_.is_null())
+    DCHECK(push_in_progress_);
+    push_in_progress_ = false;
+
+    if (!source_callback_)
       return;
 
-    base::ResetAndReturn(&completion_cb_)
-        .Run(status == MediaPipelineBackend::kBufferSuccess);
+    if (status != MediaPipelineBackend::kBufferSuccess) {
+      source_callback_->OnError();
+      return;
+    }
+
+    // Schedule next push buffer. We don't want to allow more than
+    // kMaxQueuedDataMs of queued audio.
+    const base::TimeTicks now = base::TimeTicks::Now();
+    next_push_time_ = std::max(now, next_push_time_ + buffer_duration_);
+
+    base::TimeDelta delay = (next_push_time_ - now) -
+                            base::TimeDelta::FromMilliseconds(kMaxQueuedDataMs);
+    delay = std::max(delay, base::TimeDelta());
+
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, base::Bind(&Backend::PushBuffer, weak_factory_.GetWeakPtr()),
+        delay);
+    push_in_progress_ = true;
   }
 
   void OnEndOfStream() override {}
 
   void OnDecoderError() override {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    error_ = true;
-    if (!completion_cb_.is_null())
-      OnPushBufferComplete(MediaPipelineBackend::kBufferFailed);
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    OnPushBufferComplete(MediaPipelineBackend::kBufferFailed);
   }
 
   void OnKeyStatusChanged(const std::string& key_id,
@@ -160,15 +223,21 @@
 
   void OnVideoResolutionChanged(const Size& size) override {}
 
-  std::unique_ptr<MediaPipelineBackend> backend_;
-  std::unique_ptr<TaskRunnerImpl> backend_task_runner_;
-  MediaPipelineBackend::AudioDecoder* decoder_;
-  PushBufferCompletionCallback completion_cb_;
+  const ::media::AudioParameters audio_params_;
+  std::unique_ptr<::media::AudioBus> audio_bus_;
+  scoped_refptr<media::DecoderBufferBase> decoder_buffer_;
+  ::media::AudioTimestampHelper timestamp_helper_;
+  const base::TimeDelta buffer_duration_;
   bool first_start_;
-  bool error_;
-  scoped_refptr<DecoderBufferBase> backend_buffer_;
-  base::ThreadChecker thread_checker_;
+  bool push_in_progress_;
+  base::TimeTicks next_push_time_;
+  std::unique_ptr<TaskRunnerImpl> backend_task_runner_;
+  std::unique_ptr<MediaPipelineBackend> backend_;
+  MediaPipelineBackend::AudioDecoder* decoder_;
+  AudioSourceCallback* source_callback_;
 
+  THREAD_CHECKER(thread_checker_);
+  base::WeakPtrFactory<Backend> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(Backend);
 };
 
@@ -176,23 +245,17 @@
 CastAudioOutputStream::CastAudioOutputStream(
     const ::media::AudioParameters& audio_params,
     CastAudioManager* audio_manager)
-    : audio_params_(audio_params),
-      audio_manager_(audio_manager),
-      volume_(1.0),
-      source_callback_(nullptr),
-      timestamp_helper_(audio_params_.sample_rate()),
-      backend_(new Backend()),
-      buffer_duration_(audio_params.GetBufferDuration()),
-      push_in_progress_(false),
-      weak_factory_(this) {
+    : audio_params_(audio_params), audio_manager_(audio_manager), volume_(1.0) {
   VLOG(1) << "CastAudioOutputStream " << this << " created with "
           << audio_params_.AsHumanReadableString();
 }
 
 CastAudioOutputStream::~CastAudioOutputStream() {
+  DCHECK(!backend_);
 }
 
 bool CastAudioOutputStream::Open() {
+  VLOG(1) << this << ": " << __func__;
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
 
   ::media::AudioParameters::Format format = audio_params_.format();
@@ -208,60 +271,80 @@
   DCHECK_GE(audio_params_.channels(), 1);
   DCHECK_LE(audio_params_.channels(), 2);
 
-  if (!backend_->Open(audio_params_, audio_manager_)) {
-    LOG(WARNING) << "Failed to create media pipeline backend.";
-    return false;
+  bool success = false;
+  DCHECK(!backend_);
+  backend_ = base::MakeUnique<Backend>(audio_params_);
+  {
+    base::WaitableEvent completion_event(
+        base::WaitableEvent::ResetPolicy::AUTOMATIC,
+        base::WaitableEvent::InitialState::NOT_SIGNALED);
+    audio_manager_->backend_task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &Backend::Open, base::Unretained(backend_.get()),
+            audio_manager_->backend_factory(),
+            base::BindOnce(&SignalWaitableEvent, &success, &completion_event)));
+    completion_event.Wait();
   }
 
-  audio_bus_ = ::media::AudioBus::Create(audio_params_);
-  decoder_buffer_ = new DecoderBufferAdapter(
-      new ::media::DecoderBuffer(audio_params_.GetBytesPerBuffer()));
-  timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
-
-  VLOG(1) << __FUNCTION__ << " : " << this;
-  return true;
+  if (!success)
+    LOG(WARNING) << "Failed to open audio output stream.";
+  return success;
 }
 
 void CastAudioOutputStream::Close() {
+  VLOG(1) << this << ": " << __func__;
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
-  VLOG(1) << __FUNCTION__ << " : " << this;
 
-  backend_->Close();
+  DCHECK(backend_);
+  audio_manager_->backend_task_runner()->DeleteSoon(FROM_HERE,
+                                                    backend_.release());
+
   // Signal to the manager that we're closed and can be removed.
   // This should be the last call in the function as it deletes "this".
   audio_manager_->ReleaseOutputStream(this);
 }
 
 void CastAudioOutputStream::Start(AudioSourceCallback* source_callback) {
+  VLOG(2) << this << ": " << __func__;
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
   DCHECK(source_callback);
+  DCHECK(backend_);
 
-  source_callback_ = source_callback;
-  backend_->Start();
-
-  next_push_time_ = base::TimeTicks::Now();
-  if (!push_in_progress_) {
-    audio_manager_->GetTaskRunner()->PostTask(
-        FROM_HERE, base::Bind(&CastAudioOutputStream::PushBuffer,
-                              weak_factory_.GetWeakPtr()));
-    push_in_progress_ = true;
-  }
+  audio_manager_->backend_task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&Backend::Start, base::Unretained(backend_.get()),
+                     source_callback));
 
   metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstAudio();
 }
 
 void CastAudioOutputStream::Stop() {
+  VLOG(2) << this << ": " << __func__;
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+  DCHECK(backend_);
 
-  source_callback_ = nullptr;
-  backend_->Stop();
+  base::WaitableEvent completion_event(
+      base::WaitableEvent::ResetPolicy::AUTOMATIC,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  audio_manager_->backend_task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&Backend::Stop, base::Unretained(backend_.get()),
+                     base::BindOnce(&base::WaitableEvent::Signal,
+                                    base::Unretained(&completion_event))));
+  completion_event.Wait();
 }
 
 void CastAudioOutputStream::SetVolume(double volume) {
+  VLOG(2) << this << ": " << __func__ << "(" << volume << ")";
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
 
   volume_ = volume;
-  backend_->SetVolume(volume);
+  if (backend_) {
+    audio_manager_->backend_task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&Backend::SetVolume,
+                                  base::Unretained(backend_.get()), volume_));
+  }
 }
 
 void CastAudioOutputStream::GetVolume(double* volume) {
@@ -270,67 +353,5 @@
   *volume = volume_;
 }
 
-void CastAudioOutputStream::PushBuffer() {
-  DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
-  DCHECK(push_in_progress_);
-
-  if (!source_callback_) {
-    push_in_progress_ = false;
-    return;
-  }
-
-  MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay =
-      backend_->GetRenderingDelay();
-  base::TimeDelta delay =
-      base::TimeDelta::FromMicroseconds(rendering_delay.delay_microseconds);
-  base::TimeTicks delay_timestamp =
-      base::TimeTicks() +
-      base::TimeDelta::FromMicroseconds(rendering_delay.timestamp_microseconds);
-  int frame_count =
-      source_callback_->OnMoreData(delay, delay_timestamp, 0, audio_bus_.get());
-  VLOG(3) << "frames_filled=" << frame_count << " with latency=" << delay;
-
-  DCHECK_EQ(frame_count, audio_bus_->frames());
-  DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()),
-            frame_count * audio_params_.GetBytesPerFrame());
-  audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8,
-                            decoder_buffer_->writable_data());
-  decoder_buffer_->set_timestamp(timestamp_helper_.GetTimestamp());
-  timestamp_helper_.AddFrames(frame_count);
-
-  auto completion_cb = base::Bind(&CastAudioOutputStream::OnPushBufferComplete,
-                                  weak_factory_.GetWeakPtr());
-  backend_->PushBuffer(decoder_buffer_, completion_cb);
-}
-
-void CastAudioOutputStream::OnPushBufferComplete(bool success) {
-  DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
-  DCHECK(push_in_progress_);
-
-  push_in_progress_ = false;
-
-  if (!source_callback_)
-    return;
-  if (!success) {
-    source_callback_->OnError();
-    return;
-  }
-
-  // Schedule next push buffer. We don't want to allow more than
-  // kMaxQueuedDataMs of queued audio.
-  const base::TimeTicks now = base::TimeTicks::Now();
-  next_push_time_ = std::max(now, next_push_time_ + buffer_duration_);
-
-  base::TimeDelta delay = (next_push_time_ - now) -
-                          base::TimeDelta::FromMilliseconds(kMaxQueuedDataMs);
-  delay = std::max(delay, base::TimeDelta());
-
-  audio_manager_->GetTaskRunner()->PostDelayedTask(
-      FROM_HERE, base::Bind(&CastAudioOutputStream::PushBuffer,
-                            weak_factory_.GetWeakPtr()),
-      delay);
-  push_in_progress_ = true;
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/audio/cast_audio_output_stream.h b/chromecast/media/audio/cast_audio_output_stream.h
index 7d48e90..4a1d98d 100644
--- a/chromecast/media/audio/cast_audio_output_stream.h
+++ b/chromecast/media/audio/cast_audio_output_stream.h
@@ -8,17 +8,13 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/time/time.h"
 #include "media/audio/audio_io.h"
 #include "media/base/audio_parameters.h"
-#include "media/base/audio_timestamp_helper.h"
 
 namespace chromecast {
 namespace media {
 
 class CastAudioManager;
-class DecoderBufferBase;
 
 class CastAudioOutputStream : public ::media::AudioOutputStream {
  public:
@@ -37,23 +33,12 @@
  private:
   class Backend;
 
-  void PushBuffer();
-  void OnPushBufferComplete(bool success);
-
   const ::media::AudioParameters audio_params_;
   CastAudioManager* const audio_manager_;
-
   double volume_;
-  AudioSourceCallback* source_callback_;
-  std::unique_ptr<::media::AudioBus> audio_bus_;
-  scoped_refptr<media::DecoderBufferBase> decoder_buffer_;
-  ::media::AudioTimestampHelper timestamp_helper_;
-  std::unique_ptr<Backend> backend_;
-  const base::TimeDelta buffer_duration_;
-  bool push_in_progress_;
-  base::TimeTicks next_push_time_;
 
-  base::WeakPtrFactory<CastAudioOutputStream> weak_factory_;
+  // Only valid when the stream is open.
+  std::unique_ptr<Backend> backend_;
 
   DISALLOW_COPY_AND_ASSIGN(CastAudioOutputStream);
 };
diff --git a/chromecast/media/audio/cast_audio_output_stream_unittest.cc b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
index 89744ec..f055c95 100644
--- a/chromecast/media/audio/cast_audio_output_stream_unittest.cc
+++ b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
@@ -15,14 +15,19 @@
 #include "base/time/time.h"
 #include "chromecast/base/metrics/cast_metrics_test_helper.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
+#include "chromecast/media/audio/cast_audio_mixer.h"
+#include "chromecast/media/cma/test/mock_media_pipeline_backend_factory.h"
 #include "chromecast/public/media/cast_decoder_buffer.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
+#include "chromecast/public/task_runner.h"
+#include "media/audio/mock_audio_source_callback.h"
 #include "media/audio/test_audio_thread.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using ::testing::Invoke;
-using ::testing::_;
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
 
 namespace chromecast {
 namespace media {
@@ -39,6 +44,20 @@
   return dest->frames();
 }
 
+class NotifyPushBufferCompleteTask : public chromecast::TaskRunner::Task {
+ public:
+  NotifyPushBufferCompleteTask(
+      MediaPipelineBackend::Decoder::Delegate* delegate)
+      : delegate_(delegate) {}
+  ~NotifyPushBufferCompleteTask() override = default;
+  void Run() override {
+    delegate_->OnPushBufferComplete(MediaPipelineBackend::kBufferSuccess);
+  }
+
+ private:
+  MediaPipelineBackend::Decoder::Delegate* const delegate_;
+};
+
 class FakeAudioDecoder : public MediaPipelineBackend::AudioDecoder {
  public:
   enum PipelineStatus {
@@ -48,8 +67,9 @@
     PIPELINE_STATUS_ASYNC_ERROR,
   };
 
-  FakeAudioDecoder()
-      : volume_(1.0f),
+  FakeAudioDecoder(const MediaPipelineDeviceParams& params)
+      : params_(params),
+        volume_(1.0f),
         pipeline_status_(PIPELINE_STATUS_OK),
         pending_push_(false),
         pushed_buffer_count_(0),
@@ -99,7 +119,8 @@
   void set_pipeline_status(PipelineStatus status) {
     if (status == PIPELINE_STATUS_OK && pending_push_) {
       pending_push_ = false;
-      delegate_->OnPushBufferComplete(MediaPipelineBackend::kBufferSuccess);
+      params_.task_runner->PostTask(new NotifyPushBufferCompleteTask(delegate_),
+                                    0);
     }
     pipeline_status_ = status;
   }
@@ -110,6 +131,7 @@
   CastDecoderBuffer* last_buffer() { return last_buffer_; }
 
  private:
+  const MediaPipelineDeviceParams params_;
   AudioConfig config_;
   float volume_;
 
@@ -125,13 +147,14 @@
  public:
   enum State { kStateStopped, kStateRunning, kStatePaused };
 
-  FakeMediaPipelineBackend() : state_(kStateStopped), audio_decoder_(nullptr) {}
+  FakeMediaPipelineBackend(const MediaPipelineDeviceParams& params)
+      : params_(params), state_(kStateStopped), audio_decoder_(nullptr) {}
   ~FakeMediaPipelineBackend() override {}
 
   // MediaPipelineBackend implementation:
   AudioDecoder* CreateAudioDecoder() override {
     DCHECK(!audio_decoder_);
-    audio_decoder_ = base::MakeUnique<FakeAudioDecoder>();
+    audio_decoder_ = base::MakeUnique<FakeAudioDecoder>(params_);
     return audio_decoder_.get();
   }
   VideoDecoder* CreateVideoDecoder() override {
@@ -166,62 +189,17 @@
   FakeAudioDecoder* decoder() const { return audio_decoder_.get(); }
 
  private:
+  const MediaPipelineDeviceParams params_;
   State state_;
   std::unique_ptr<FakeAudioDecoder> audio_decoder_;
 };
 
-class MockAudioSourceCallback
-    : public ::media::AudioOutputStream::AudioSourceCallback {
- public:
-  // ::media::AudioOutputStream::AudioSourceCallback overrides.
-  MOCK_METHOD4(OnMoreData,
-               int(base::TimeDelta, base::TimeTicks, int, ::media::AudioBus*));
-  MOCK_METHOD0(OnError, void());
-};
-
-class FakeAudioManager : public CastAudioManager {
- public:
-  FakeAudioManager()
-      : CastAudioManager(base::MakeUnique<::media::TestAudioThread>(),
-                         nullptr,
-                         nullptr,
-                         nullptr),
-        media_pipeline_backend_(nullptr) {}
-  ~FakeAudioManager() override {}
-
-  // CastAudioManager overrides.
-  std::unique_ptr<MediaPipelineBackend> CreateMediaPipelineBackend(
-      const MediaPipelineDeviceParams& params) override {
-    DCHECK(GetTaskRunner()->BelongsToCurrentThread());
-    DCHECK(!media_pipeline_backend_);
-
-    std::unique_ptr<FakeMediaPipelineBackend> backend(
-        new FakeMediaPipelineBackend());
-    // Cache the backend locally to be used by tests.
-    media_pipeline_backend_ = backend.get();
-    return std::move(backend);
-  }
-  void ReleaseOutputStream(::media::AudioOutputStream* stream) override {
-    DCHECK(media_pipeline_backend_);
-    media_pipeline_backend_ = nullptr;
-    CastAudioManager::ReleaseOutputStream(stream);
-  }
-
-  // Returns the MediaPipelineBackend being used by the AudioOutputStream.
-  // Note: here is a valid MediaPipelineBackend only while the stream is open.
-  // Returns NULL at all other times.
-  FakeMediaPipelineBackend* media_pipeline_backend() {
-    return media_pipeline_backend_;
-  }
-
- private:
-  FakeMediaPipelineBackend* media_pipeline_backend_;
-};
-
 class CastAudioOutputStreamTest : public ::testing::Test {
  public:
   CastAudioOutputStreamTest()
-      : format_(::media::AudioParameters::AUDIO_PCM_LINEAR),
+      : media_thread_("CastMediaThread"),
+        media_pipeline_backend_(nullptr),
+        format_(::media::AudioParameters::AUDIO_PCM_LINEAR),
         channel_layout_(::media::CHANNEL_LAYOUT_MONO),
         sample_rate_(::media::AudioParameters::kAudioCDSampleRate),
         bits_per_sample_(16),
@@ -231,7 +209,18 @@
  protected:
   void SetUp() override {
     metrics::InitializeMetricsHelperForTesting();
-    audio_manager_ = base::MakeUnique<FakeAudioManager>();
+
+    CHECK(media_thread_.Start());
+    auto backend_factory =
+        base::MakeUnique<NiceMock<MockMediaPipelineBackendFactory>>();
+    ON_CALL(*backend_factory, CreateBackend(_))
+        .WillByDefault(Invoke([this](const MediaPipelineDeviceParams& params) {
+          media_pipeline_backend_ = new FakeMediaPipelineBackend(params);
+          return base::WrapUnique(media_pipeline_backend_);
+        }));
+    audio_manager_ = base::MakeUnique<CastAudioManager>(
+        base::MakeUnique<::media::TestAudioThread>(), nullptr,
+        std::move(backend_factory), media_thread_.task_runner(), false);
   }
 
   void TearDown() override { audio_manager_->Shutdown(); }
@@ -241,9 +230,7 @@
                                     bits_per_sample_, frames_per_buffer_);
   }
 
-  FakeMediaPipelineBackend* GetBackend() {
-    return audio_manager_->media_pipeline_backend();
-  }
+  FakeMediaPipelineBackend* GetBackend() { return media_pipeline_backend_; }
 
   FakeAudioDecoder* GetAudio() {
     FakeMediaPipelineBackend* backend = GetBackend();
@@ -269,7 +256,10 @@
   }
 
   base::MessageLoop message_loop_;
-  std::unique_ptr<FakeAudioManager> audio_manager_;
+  base::Thread media_thread_;
+  std::unique_ptr<CastAudioManager> audio_manager_;
+  // MockMediaPipelineBackendFactory* backend_factory_;
+  FakeMediaPipelineBackend* media_pipeline_backend_;
   // AudioParameters used to create AudioOutputStream.
   // Tests can modify these parameters before calling CreateStream.
   ::media::AudioParameters::Format format_;
@@ -281,7 +271,7 @@
 
 TEST_F(CastAudioOutputStreamTest, Format) {
   ::media::AudioParameters::Format format[] = {
-      //::media::AudioParameters::AUDIO_PCM_LINEAR,
+      ::media::AudioParameters::AUDIO_PCM_LINEAR,
       ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY};
   for (size_t i = 0; i < arraysize(format); ++i) {
     format_ = format[i];
@@ -350,7 +340,6 @@
 TEST_F(CastAudioOutputStreamTest, DeviceState) {
   ::media::AudioOutputStream* stream = CreateStream();
   ASSERT_TRUE(stream);
-  EXPECT_FALSE(GetAudio());
 
   EXPECT_TRUE(stream->Open());
   FakeAudioDecoder* audio_decoder = GetAudio();
@@ -359,17 +348,18 @@
   ASSERT_TRUE(backend);
   EXPECT_EQ(FakeMediaPipelineBackend::kStateStopped, backend->state());
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
-  stream->Start(source_callback.get());
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
+  stream->Start(&source_callback);
+  media_thread_.FlushForTesting();
   EXPECT_EQ(FakeMediaPipelineBackend::kStateRunning, backend->state());
 
   stream->Stop();
+  media_thread_.FlushForTesting();
   EXPECT_EQ(FakeMediaPipelineBackend::kStatePaused, backend->state());
 
   stream->Close();
-  EXPECT_FALSE(GetAudio());
 }
 
 TEST_F(CastAudioOutputStreamTest, PushFrame) {
@@ -383,12 +373,12 @@
   EXPECT_EQ(0u, audio_decoder->pushed_buffer_count());
   EXPECT_FALSE(audio_decoder->last_buffer());
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
   // No error must be reported to source callback.
-  EXPECT_CALL(*source_callback, OnError()).Times(0);
-  stream->Start(source_callback.get());
+  EXPECT_CALL(source_callback, OnError()).Times(0);
+  stream->Start(&source_callback);
   RunMessageLoopFor(2);
   stream->Stop();
 
@@ -418,12 +408,12 @@
   ASSERT_TRUE(audio_decoder);
   audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_BUSY);
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
   // No error must be reported to source callback.
-  EXPECT_CALL(*source_callback, OnError()).Times(0);
-  stream->Start(source_callback.get());
+  EXPECT_CALL(source_callback, OnError()).Times(0);
+  stream->Start(&source_callback);
   RunMessageLoopFor(5);
   // Make sure that one frame was pushed.
   EXPECT_EQ(1u, audio_decoder->pushed_buffer_count());
@@ -451,12 +441,12 @@
   ASSERT_TRUE(audio_decoder);
   audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_ERROR);
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
   // AudioOutputStream must report error to source callback.
-  EXPECT_CALL(*source_callback, OnError());
-  stream->Start(source_callback.get());
+  EXPECT_CALL(source_callback, OnError());
+  stream->Start(&source_callback);
   RunMessageLoopFor(2);
   // Make sure that AudioOutputStream attempted to push the initial frame.
   EXPECT_LT(0u, audio_decoder->pushed_buffer_count());
@@ -475,12 +465,12 @@
   audio_decoder->set_pipeline_status(
       FakeAudioDecoder::PIPELINE_STATUS_ASYNC_ERROR);
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
   // AudioOutputStream must report error to source callback.
-  EXPECT_CALL(*source_callback, OnError());
-  stream->Start(source_callback.get());
+  EXPECT_CALL(source_callback, OnError());
+  stream->Start(&source_callback);
   RunMessageLoopFor(5);
 
   // Make sure that one frame was pushed.
@@ -503,6 +493,7 @@
   EXPECT_EQ(1.0f, audio_decoder->volume());
 
   stream->SetVolume(0.5);
+  media_thread_.FlushForTesting();
   stream->GetVolume(&volume);
   EXPECT_EQ(0.5, volume);
   EXPECT_EQ(0.5f, audio_decoder->volume());
@@ -515,13 +506,13 @@
   ASSERT_TRUE(stream);
   ASSERT_TRUE(stream->Open());
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
-  ON_CALL(*source_callback, OnMoreData(_, _, _, _))
-      .WillByDefault(Invoke(OnMoreData));
-  stream->Start(source_callback.get());
+  ::media::MockAudioSourceCallback source_callback;
+  EXPECT_CALL(source_callback, OnMoreData(_, _, _, _))
+      .WillRepeatedly(Invoke(OnMoreData));
+  stream->Start(&source_callback);
   RunMessageLoopFor(2);
   stream->Stop();
-  stream->Start(source_callback.get());
+  stream->Start(&source_callback);
   RunMessageLoopFor(2);
 
   FakeAudioDecoder* audio_device = GetAudio();
@@ -549,14 +540,14 @@
       MediaPipelineBackend::AudioDecoder::RenderingDelay(kDelayUs,
                                                          kDelayTimestampUs));
 
-  auto source_callback(base::MakeUnique<MockAudioSourceCallback>());
+  ::media::MockAudioSourceCallback source_callback;
   const base::TimeDelta delay(base::TimeDelta::FromMicroseconds(kDelayUs));
   const base::TimeTicks delay_timestamp(
       base::TimeTicks() + base::TimeDelta::FromMicroseconds(kDelayTimestampUs));
-  EXPECT_CALL(*source_callback, OnMoreData(delay, delay_timestamp, _, _))
+  EXPECT_CALL(source_callback, OnMoreData(delay, delay_timestamp, _, _))
       .WillRepeatedly(Invoke(OnMoreData));
 
-  stream->Start(source_callback.get());
+  stream->Start(&source_callback);
   RunMessageLoopFor(2);
   stream->Stop();
   stream->Close();
diff --git a/chromecast/media/cma/BUILD.gn b/chromecast/media/cma/BUILD.gn
index 03b6384e..2ba1312 100644
--- a/chromecast/media/cma/BUILD.gn
+++ b/chromecast/media/cma/BUILD.gn
@@ -14,6 +14,35 @@
   ]
 }
 
+source_set("test_support") {
+  testonly = true
+  sources = [
+    "test/frame_generator_for_test.cc",
+    "test/frame_generator_for_test.h",
+    "test/frame_segmenter_for_test.cc",
+    "test/frame_segmenter_for_test.h",
+    "test/mock_frame_consumer.cc",
+    "test/mock_frame_consumer.h",
+    "test/mock_frame_provider.cc",
+    "test/mock_frame_provider.h",
+    "test/mock_media_pipeline_backend.cc",
+    "test/mock_media_pipeline_backend.h",
+    "test/mock_media_pipeline_backend_factory.cc",
+    "test/mock_media_pipeline_backend_factory.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chromecast/media/cma/backend",
+    "//chromecast/media/cma/base",
+    "//media",
+    "//media/base:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//ui/gfx/geometry",
+  ]
+}
+
 source_set("unittests") {
   testonly = true
   sources = [
@@ -27,37 +56,17 @@
     "base/demuxer_stream_for_test.h",
     "base/multi_demuxer_stream_adapter_unittest.cc",
     "pipeline/audio_video_pipeline_impl_unittest.cc",
-    "test/frame_generator_for_test.cc",
-    "test/frame_generator_for_test.h",
-    "test/frame_segmenter_for_test.cc",
-    "test/frame_segmenter_for_test.h",
-    "test/mock_frame_consumer.cc",
-    "test/mock_frame_consumer.h",
-    "test/mock_frame_provider.cc",
-    "test/mock_frame_provider.h",
-    "test/mock_media_pipeline_backend.cc",
-    "test/mock_media_pipeline_backend.h",
   ]
 
   deps = [
+    ":test_support",
     "//base",
-    "//base:i18n",
     "//base/test:test_support",
     "//chromecast/base",
-    "//chromecast/base/metrics:test_support",
-    "//chromecast/common/media:interfaces",
     "//chromecast/media",
-    "//chromecast/media/audio",
-    "//chromecast/media/cma/backend",
-    "//chromecast/media/cma/base",
-    "//chromecast/media/cma/pipeline",
-    "//chromecast/public",
     "//media",
-    "//media/base:test_support",
     "//testing/gmock",
     "//testing/gtest",
-    "//ui/display",
-    "//ui/gfx/geometry",
   ]
 
   data = [
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index 4d096ddd..5016ccaa 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -8,8 +8,9 @@
 
 source_set("backend") {
   sources = [
-    "media_pipeline_backend_factory.cc",
     "media_pipeline_backend_factory.h",
+    "media_pipeline_backend_factory_impl.cc",
+    "media_pipeline_backend_factory_impl.h",
     "media_pipeline_backend_manager.cc",
     "media_pipeline_backend_manager.h",
     "media_pipeline_backend_wrapper.cc",
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_factory.h b/chromecast/media/cma/backend/media_pipeline_backend_factory.h
index 087d20e4..2f1b010 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_factory.h
+++ b/chromecast/media/cma/backend/media_pipeline_backend_factory.h
@@ -6,41 +6,23 @@
 #define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_BACKEND_FACTORY_H_
 
 #include <memory>
-#include <string>
-
-#include "base/macros.h"
 
 namespace chromecast {
 namespace media {
 
 class MediaPipelineBackend;
-class MediaPipelineBackendManager;
 struct MediaPipelineDeviceParams;
 
-// Creates MediaPipelineBackends using a given MediaPipelineBackendManager.
+// Abstract base class to create MediaPipelineBackend.
 class MediaPipelineBackendFactory {
  public:
-  // TODO(slan): Use a static Create method once all of the constructor
-  // dependencies are removed from the internal implemenation.
-  explicit MediaPipelineBackendFactory(
-      MediaPipelineBackendManager* media_pipeline_backend_manager);
-  virtual ~MediaPipelineBackendFactory();
+  virtual ~MediaPipelineBackendFactory() {}
 
   virtual std::unique_ptr<MediaPipelineBackend> CreateBackend(
-      const MediaPipelineDeviceParams& params);
-
- protected:
-  MediaPipelineBackendManager* media_pipeline_backend_manager() {
-    return media_pipeline_backend_manager_;
-  }
-
- private:
-  media::MediaPipelineBackendManager* const media_pipeline_backend_manager_;
-
-  DISALLOW_COPY_AND_ASSIGN(MediaPipelineBackendFactory);
+      const MediaPipelineDeviceParams& params) = 0;
 };
 
-}  // media
-}  // chromecast
+}  // namespace media
+}  // namespace chromecast
 
 #endif  // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_BACKEND_FACTORY_H_
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_factory.cc b/chromecast/media/cma/backend/media_pipeline_backend_factory_impl.cc
similarity index 81%
rename from chromecast/media/cma/backend/media_pipeline_backend_factory.cc
rename to chromecast/media/cma/backend/media_pipeline_backend_factory_impl.cc
index d6df9d7..cb327293 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_factory.cc
+++ b/chromecast/media/cma/backend/media_pipeline_backend_factory_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory_impl.h"
 
 #include "chromecast/media/cma/backend/media_pipeline_backend_manager.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
@@ -11,16 +11,16 @@
 namespace chromecast {
 namespace media {
 
-MediaPipelineBackendFactory::MediaPipelineBackendFactory(
+MediaPipelineBackendFactoryImpl::MediaPipelineBackendFactoryImpl(
     MediaPipelineBackendManager* media_pipeline_backend_manager)
     : media_pipeline_backend_manager_(media_pipeline_backend_manager) {
   DCHECK(media_pipeline_backend_manager_);
 }
 
-MediaPipelineBackendFactory::~MediaPipelineBackendFactory() {}
+MediaPipelineBackendFactoryImpl::~MediaPipelineBackendFactoryImpl() {}
 
 std::unique_ptr<MediaPipelineBackend>
-MediaPipelineBackendFactory::CreateBackend(
+MediaPipelineBackendFactoryImpl::CreateBackend(
     const MediaPipelineDeviceParams& params) {
   return media_pipeline_backend_manager_->CreateMediaPipelineBackend(params);
 }
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_factory_impl.h b/chromecast/media/cma/backend/media_pipeline_backend_factory_impl.h
new file mode 100644
index 0000000..c2b4bbc
--- /dev/null
+++ b/chromecast/media/cma/backend/media_pipeline_backend_factory_impl.h
@@ -0,0 +1,44 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_BACKEND_FACTORY_IMPL_H_
+#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_BACKEND_FACTORY_IMPL_H_
+
+#include "base/macros.h"
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
+
+namespace chromecast {
+namespace media {
+
+class MediaPipelineBackend;
+class MediaPipelineBackendManager;
+struct MediaPipelineDeviceParams;
+
+// Creates MediaPipelineBackends using a given MediaPipelineBackendManager.
+class MediaPipelineBackendFactoryImpl : public MediaPipelineBackendFactory {
+ public:
+  // TODO(slan): Use a static Create method once all of the constructor
+  // dependencies are removed from the internal implemenation.
+  explicit MediaPipelineBackendFactoryImpl(
+      MediaPipelineBackendManager* media_pipeline_backend_manager);
+  ~MediaPipelineBackendFactoryImpl() override;
+
+  std::unique_ptr<MediaPipelineBackend> CreateBackend(
+      const MediaPipelineDeviceParams& params) override;
+
+ protected:
+  MediaPipelineBackendManager* media_pipeline_backend_manager() {
+    return media_pipeline_backend_manager_;
+  }
+
+ private:
+  media::MediaPipelineBackendManager* const media_pipeline_backend_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaPipelineBackendFactoryImpl);
+};
+
+}  // media
+}  // chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_PIPELINE_BACKEND_FACTORY_IMPL_H_
diff --git a/chromecast/media/cma/test/mock_media_pipeline_backend_factory.cc b/chromecast/media/cma/test/mock_media_pipeline_backend_factory.cc
new file mode 100644
index 0000000..9f4d86a4
--- /dev/null
+++ b/chromecast/media/cma/test/mock_media_pipeline_backend_factory.cc
@@ -0,0 +1,14 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/test/mock_media_pipeline_backend_factory.h"
+
+namespace chromecast {
+namespace media {
+
+MockMediaPipelineBackendFactory::MockMediaPipelineBackendFactory() = default;
+MockMediaPipelineBackendFactory::~MockMediaPipelineBackendFactory() = default;
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/cma/test/mock_media_pipeline_backend_factory.h b/chromecast/media/cma/test/mock_media_pipeline_backend_factory.h
new file mode 100644
index 0000000..f8d87aa
--- /dev/null
+++ b/chromecast/media/cma/test/mock_media_pipeline_backend_factory.h
@@ -0,0 +1,29 @@
+// 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 CHROMECAST_MEDIA_CMA_TEST_MOCK_MEDIA_PIPELINE_BACKEND_FACTORY_H_
+#define CHROMECAST_MEDIA_CMA_TEST_MOCK_MEDIA_PIPELINE_BACKEND_FACTORY_H_
+
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h"
+#include "chromecast/public/media/media_pipeline_backend.h"
+#include "chromecast/public/media/media_pipeline_device_params.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chromecast {
+namespace media {
+
+class MockMediaPipelineBackendFactory : public MediaPipelineBackendFactory {
+ public:
+  MockMediaPipelineBackendFactory();
+  ~MockMediaPipelineBackendFactory() override;
+
+  MOCK_METHOD1(
+      CreateBackend,
+      std::unique_ptr<MediaPipelineBackend>(const MediaPipelineDeviceParams&));
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_TEST_MOCK_MEDIA_PIPELINE_BACKEND_FACTORY_H_
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index f82029d..ae2fbef 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -23,6 +23,7 @@
     ":authpolicy_proto",
     ":biod_proto",
     ":cryptohome_proto",
+    ":login_manager_proto",
     ":media_perception_proto",
     ":power_manager_proto",
     "//base",
@@ -749,6 +750,14 @@
   proto_out_dir = "chromeos/dbus/cryptohome"
 }
 
+proto_library("login_manager_proto") {
+  sources = [
+    "//third_party/cros_system_api/dbus/login_manager/arc.proto",
+  ]
+
+  proto_out_dir = "chromeos/dbus/login_manager"
+}
+
 proto_library("attestation_proto") {
   sources = [
     "dbus/proto/attestation.proto",
diff --git a/chromeos/dbus/session_manager_client.cc b/chromeos/dbus/session_manager_client.cc
index 2184de5..c4cda06 100644
--- a/chromeos/dbus/session_manager_client.cc
+++ b/chromeos/dbus/session_manager_client.cc
@@ -25,6 +25,7 @@
 #include "chromeos/cryptohome/cryptohome_parameters.h"
 #include "chromeos/dbus/blocking_method_caller.h"
 #include "chromeos/dbus/cryptohome_client.h"
+#include "chromeos/dbus/login_manager/arc.pb.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "crypto/sha2.h"
 #include "dbus/bus.h"
@@ -402,16 +403,20 @@
   }
 
   void StartArcInstance(const cryptohome::Identification& cryptohome_id,
-                        bool disable_boot_completed_broadcast,
-                        bool enable_vendor_privileged,
+                        bool skip_boot_completed_broadcast,
+                        bool scan_vendor_priv_app,
                         const StartArcInstanceCallback& callback) override {
     dbus::MethodCall method_call(
         login_manager::kSessionManagerInterface,
         login_manager::kSessionManagerStartArcInstance);
     dbus::MessageWriter writer(&method_call);
-    writer.AppendString(cryptohome_id.id());
-    writer.AppendBool(disable_boot_completed_broadcast);
-    writer.AppendBool(enable_vendor_privileged);
+
+    login_manager::StartArcInstanceRequest request;
+    request.set_account_id(cryptohome_id.id());
+    request.set_skip_boot_completed_broadcast(skip_boot_completed_broadcast);
+    request.set_scan_vendor_priv_app(scan_vendor_priv_app);
+    writer.AppendProtoAsArrayOfBytes(request);
+
     session_manager_proxy_->CallMethodWithErrorCallback(
         &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
         base::Bind(&SessionManagerClientImpl::OnStartArcInstanceSucceeded,
diff --git a/chromeos/dbus/session_manager_client.h b/chromeos/dbus/session_manager_client.h
index bd6f3324..ea5d610 100644
--- a/chromeos/dbus/session_manager_client.h
+++ b/chromeos/dbus/session_manager_client.h
@@ -301,8 +301,8 @@
       base::Callback<void(StartArcInstanceResult result,
                           const std::string& container_instance_id)>;
   virtual void StartArcInstance(const cryptohome::Identification& cryptohome_id,
-                                bool disable_boot_completed_broadcast,
-                                bool enable_vendor_privileged,
+                                bool skip_boot_completed_broadcast,
+                                bool scan_vendor_priv_app,
                                 const StartArcInstanceCallback& callback) = 0;
 
   // Asynchronously stops the ARC instance.  Upon completion, invokes
diff --git a/chromeos/printing/printer_configuration.h b/chromeos/printing/printer_configuration.h
index 18d72bc..2ed1130 100644
--- a/chromeos/printing/printer_configuration.h
+++ b/chromeos/printing/printer_configuration.h
@@ -47,8 +47,9 @@
     SRC_POLICY,
   };
 
-  // An enumeration of printer protocols.  Do not change these values as they
-  // are used in enums.xml.
+  // An enumeration of printer protocols.
+  // These values are written to logs.  New enum values can be added, but
+  // existing enums must never be renumbered or deleted and reused.
   enum PrinterProtocol {
     kUnknown = 0,
     kUsb = 1,
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 1deb2cad..482b3b8 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -195,7 +195,6 @@
       "//components/link_header_util:unit_tests",
       "//components/navigation_interception:unit_tests",
       "//components/network_hints/renderer:unit_tests",
-      "//components/offline_pages/content:unit_tests",
       "//components/offline_pages/content/background_loader:unit_tests",
       "//components/offline_pages/core:unit_tests",
       "//components/offline_pages/core/background:unit_tests",
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index c9443f48..9b890790 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -73,6 +73,7 @@
     "//ash/shared:app_types",
     "//base",
     "//chromeos",
+    "//chromeos:login_manager_proto",
     "//chromeos:power_manager_proto",
     "//components/exo",
     "//components/google/core/browser",
diff --git a/components/arc/arc_session.cc b/components/arc/arc_session.cc
index 62c9e33..5cd62ef 100644
--- a/components/arc/arc_session.cc
+++ b/components/arc/arc_session.cc
@@ -369,18 +369,18 @@
   const cryptohome::Identification cryptohome_id(
       user_manager->GetPrimaryUser()->GetAccountId());
 
-  bool disable_boot_completed_broadcast =
+  const bool skip_boot_completed_broadcast =
       !base::FeatureList::IsEnabled(arc::kBootCompletedBroadcastFeature);
 
   // We only enable /vendor/priv-app when voice interaction is enabled because
   // voice interaction service apk would be bundled in this location.
-  bool enable_vendor_privileged =
+  const bool scan_vendor_priv_app =
       chromeos::switches::IsVoiceInteractionEnabled();
 
   chromeos::SessionManagerClient* session_manager_client =
       chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
   session_manager_client->StartArcInstance(
-      cryptohome_id, disable_boot_completed_broadcast, enable_vendor_privileged,
+      cryptohome_id, skip_boot_completed_broadcast, scan_vendor_priv_app,
       base::Bind(&ArcSessionImpl::OnInstanceStarted, weak_factory_.GetWeakPtr(),
                  base::Passed(&socket_fd)));
 }
diff --git a/components/cast_channel/BUILD.gn b/components/cast_channel/BUILD.gn
new file mode 100644
index 0000000..ef44a6e
--- /dev/null
+++ b/components/cast_channel/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("cast_channel") {
+  sources = [
+    "cast_channel_enum.cc",
+    "cast_channel_enum.h",
+  ]
+  deps = [
+    "//base",
+  ]
+}
diff --git a/components/cast_channel/OWNERS b/components/cast_channel/OWNERS
new file mode 100644
index 0000000..fc1829b
--- /dev/null
+++ b/components/cast_channel/OWNERS
@@ -0,0 +1,5 @@
+mfoltz@chromium.org
+kmarshall@chromium.org
+wez@chromium.org
+
+# COMPONENT: Internals>Cast>API
diff --git a/components/cast_channel/cast_channel_enum.cc b/components/cast_channel/cast_channel_enum.cc
new file mode 100644
index 0000000..9e426adc
--- /dev/null
+++ b/components/cast_channel/cast_channel_enum.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/cast_channel/cast_channel_enum.h"
+
+#include "base/logging.h"
+
+namespace cast_channel {
+
+#define CAST_CHANNEL_TYPE_TO_STRING(enum) \
+  case enum:                              \
+    return #enum
+
+std::string ReadyStateToString(ReadyState ready_state) {
+  switch (ready_state) {
+    CAST_CHANNEL_TYPE_TO_STRING(ReadyState::NONE);
+    CAST_CHANNEL_TYPE_TO_STRING(ReadyState::CONNECTING);
+    CAST_CHANNEL_TYPE_TO_STRING(ReadyState::OPEN);
+    CAST_CHANNEL_TYPE_TO_STRING(ReadyState::CLOSING);
+    CAST_CHANNEL_TYPE_TO_STRING(ReadyState::CLOSED);
+  }
+  NOTREACHED() << "Unknown ready_state " << ReadyStateToString(ready_state);
+  return "Unknown ready_state";
+}
+
+std::string ChannelErrorToString(ChannelError channel_error) {
+  switch (channel_error) {
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::NONE);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::CHANNEL_NOT_OPEN);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::AUTHENTICATION_ERROR);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::CONNECT_ERROR);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::CAST_SOCKET_ERROR);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::TRANSPORT_ERROR);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::INVALID_MESSAGE);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::INVALID_CHANNEL_ID);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::CONNECT_TIMEOUT);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::PING_TIMEOUT);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelError::UNKNOWN);
+  }
+  NOTREACHED() << "Unknown channel_error "
+               << ChannelErrorToString(channel_error);
+  return "Unknown channel_error";
+}
+
+std::string ChannelAuthTypeToString(ChannelAuthType channel_auth) {
+  switch (channel_auth) {
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelAuthType::NONE);
+    CAST_CHANNEL_TYPE_TO_STRING(ChannelAuthType::SSL_VERIFIED);
+  }
+  NOTREACHED() << "Unknown channel_auth "
+               << ChannelAuthTypeToString(channel_auth);
+  return "Unknown channel_auth";
+}
+
+}  // namespace cast_channel
diff --git a/components/cast_channel/cast_channel_enum.h b/components/cast_channel/cast_channel_enum.h
new file mode 100644
index 0000000..e176cbd
--- /dev/null
+++ b/components/cast_channel/cast_channel_enum.h
@@ -0,0 +1,48 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CAST_CHANNEL_CAST_CHANNEL_ENUM_H_
+#define COMPONENTS_CAST_CHANNEL_CAST_CHANNEL_ENUM_H_
+
+#include <string>
+
+namespace cast_channel {
+
+// Maps to enum ReadyState in cast_channel.idl
+enum class ReadyState {
+  NONE,
+  CONNECTING,
+  OPEN,
+  CLOSING,
+  CLOSED,
+};
+
+// Maps to enum ChannelError in cast_channel.idl
+enum class ChannelError {
+  NONE,
+  CHANNEL_NOT_OPEN,
+  AUTHENTICATION_ERROR,
+  CONNECT_ERROR,
+  CAST_SOCKET_ERROR,
+  TRANSPORT_ERROR,
+  INVALID_MESSAGE,
+  INVALID_CHANNEL_ID,
+  CONNECT_TIMEOUT,
+  PING_TIMEOUT,
+  UNKNOWN,
+};
+
+// Maps to enum ChannelAuth in cast_channel.idl
+enum class ChannelAuthType {
+  NONE,
+  SSL_VERIFIED,
+};
+
+std::string ReadyStateToString(ReadyState ready_state);
+std::string ChannelErrorToString(ChannelError channel_error);
+std::string ChannelAuthTypeToString(ChannelAuthType channel_auth);
+
+}  // namespace cast_channel
+
+#endif  // COMPONENTS_CAST_CHANNEL_CAST_CHANNEL_ENUM_H_
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
index fefad0f1..a26fccc 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/BrotliTest.java
@@ -17,8 +17,6 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        // Load library first to create MockCertVerifier.
-        System.loadLibrary("cronet_tests");
         assertTrue(Http2TestServer.startHttp2TestServer(
                 getContext(), SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM));
     }
@@ -26,7 +24,9 @@
     @Override
     protected void tearDown() throws Exception {
         assertTrue(Http2TestServer.shutdownHttp2TestServer());
-        mCronetEngine.shutdown();
+        if (mCronetEngine != null) {
+            mCronetEngine.shutdown();
+        }
         super.tearDown();
     }
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
index c76189e..4985301 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate.cc
@@ -86,7 +86,9 @@
   }
 
   std::vector<DataReductionProxyServer> proxies_for_http =
-      config_->GetProxiesForHttp();
+      params::IsIncludedInHoldbackFieldTrial()
+          ? std::vector<DataReductionProxyServer>()
+          : config_->GetProxiesForHttp();
 
   // Remove the proxies that are unsupported for this request.
   proxies_for_http.erase(
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
index 462c92b..5c4f2c1 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_delegate_unittest.cc
@@ -860,6 +860,36 @@
   }
 }
 
+TEST_F(DataReductionProxyDelegateTest, Holdback) {
+  const char kResponseHeaders[] =
+      "HTTP/1.1 200 OK\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy-Suffix\r\n"
+      "Content-Length: 10\r\n\r\n";
+
+  const struct {
+    bool holdback;
+  } tests[] = {
+      {
+          true,
+      },
+      {
+          false,
+      },
+  };
+  for (const auto& test : tests) {
+    base::FieldTrialList field_trial_list(nullptr);
+    ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
+        "DataCompressionProxyHoldback", test.holdback ? "Enabled" : "Control"));
+
+    base::HistogramTester histogram_tester;
+    FetchURLRequest(GURL("http://example.com/path/"), nullptr, kResponseHeaders,
+                    10);
+    histogram_tester.ExpectTotalCount(
+        "DataReductionProxy.SuccessfulRequestCompletionCounts",
+        test.holdback ? 0 : 1);
+  }
+}
+
 TEST_F(DataReductionProxyDelegateTest, OnCompletedSizeFor304) {
   int64_t baseline_received_bytes = total_received_bytes();
   int64_t baseline_original_received_bytes = total_original_received_bytes();
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
index dd29ebf..df46721 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
@@ -164,6 +164,9 @@
 // Saver proxy.
 void VerifyHttpRequestHeaders(bool via_chrome_proxy,
                               const net::HttpRequestHeaders& headers) {
+  // If holdback is enabled, then |via_chrome_proxy| should be false.
+  DCHECK(!params::IsIncludedInHoldbackFieldTrial() || !via_chrome_proxy);
+
   if (via_chrome_proxy) {
     DCHECK(headers.HasHeader(chrome_proxy_ect_header()));
     std::string chrome_proxy_header_value;
@@ -283,16 +286,17 @@
   DataReductionProxyData::ClearData(request);
 
   if (params::IsIncludedInHoldbackFieldTrial()) {
-    if (!WasEligibleWithoutHoldback(*request, proxy_info, proxy_retry_info))
-      return;
-    // For the holdback field trial, still log UMA as if the proxy was used.
-    data = DataReductionProxyData::GetDataAndCreateIfNecessary(request);
-    if (data)
-      data->set_used_data_reduction_proxy(true);
-
-    headers->RemoveHeader(chrome_proxy_header());
-    VerifyHttpRequestHeaders(false, *headers);
-    return;
+    if (WasEligibleWithoutHoldback(*request, proxy_info, proxy_retry_info)) {
+      // For the holdback field trial, still log UMA as if the proxy was used.
+      data = DataReductionProxyData::GetDataAndCreateIfNecessary(request);
+      if (data)
+        data->set_used_data_reduction_proxy(true);
+    }
+    // If holdback is enabled, |proxy_info| must not contain a data reduction
+    // proxy.
+    DCHECK(proxy_info.is_empty() ||
+           !data_reduction_proxy_config_->IsDataReductionProxy(
+               proxy_info.proxy_server(), nullptr));
   }
 
   bool using_data_reduction_proxy = true;
@@ -306,6 +310,9 @@
                  proxy_info.proxy_server(), nullptr)) {
     using_data_reduction_proxy = false;
   }
+  // If holdback is enabled, |using_data_reduction_proxy| must be false.
+  DCHECK(!params::IsIncludedInHoldbackFieldTrial() ||
+         !using_data_reduction_proxy);
 
   LoFiDecider* lofi_decider = nullptr;
   if (data_reduction_proxy_io_data_)
diff --git a/components/download/internal/BUILD.gn b/components/download/internal/BUILD.gn
index d1e1e43..dc74506 100644
--- a/components/download/internal/BUILD.gn
+++ b/components/download/internal/BUILD.gn
@@ -21,6 +21,8 @@
     "download_driver.h",
     "download_service_impl.cc",
     "download_service_impl.h",
+    "download_store.cc",
+    "download_store.h",
     "driver_entry.cc",
     "driver_entry.h",
     "entry.cc",
@@ -28,8 +30,8 @@
     "model.h",
     "model_impl.cc",
     "model_impl.h",
-    "noop_store.cc",
-    "noop_store.h",
+    "proto_conversions.cc",
+    "proto_conversions.h",
     "scheduler/battery_listener.cc",
     "scheduler/battery_listener.h",
     "scheduler/network_listener.cc",
@@ -39,7 +41,9 @@
 
   deps = [
     "//base",
+    "//components/download/internal/proto",
     "//components/download/public",
+    "//components/leveldb_proto",
     "//net",
   ]
 }
@@ -50,7 +54,9 @@
   visibility = [ "//components/download:unit_tests" ]
 
   sources = [
+    "download_store_unittest.cc",
     "model_impl_unittest.cc",
+    "proto_conversions_unittest.cc",
     "scheduler/battery_listener_unittest.cc",
     "scheduler/network_listener_unittest.cc",
   ]
@@ -58,7 +64,9 @@
   deps = [
     ":internal",
     "//base/test:test_support",
+    "//components/download/internal/proto",
     "//components/download/internal/test:test_support",
+    "//components/leveldb_proto:test_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/components/download/internal/DEPS b/components/download/internal/DEPS
index 4ca86d9b..6a99578 100644
--- a/components/download/internal/DEPS
+++ b/components/download/internal/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "-components/download/content",
   "-content",
+  "+components/leveldb_proto",
   "+base",
   "+net",
 ]
diff --git a/components/download/internal/download_store.cc b/components/download/internal/download_store.cc
new file mode 100644
index 0000000..a7890e89
--- /dev/null
+++ b/components/download/internal/download_store.cc
@@ -0,0 +1,89 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/download_store.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/leveldb_proto/proto_database_impl.h"
+
+namespace download {
+
+namespace {
+
+const char kDatabaseClientName[] = "DownloadService";
+using KeyVector = std::vector<std::string>;
+using ProtoEntryVector = std::vector<protodb::Entry>;
+using KeyProtoEntryVector = std::vector<std::pair<std::string, protodb::Entry>>;
+
+}  // namespace
+
+DownloadStore::DownloadStore(
+    const base::FilePath& database_dir,
+    std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db)
+    : db_(std::move(db)),
+      database_dir_(database_dir),
+      is_initialized_(false),
+      weak_factory_(this) {}
+
+DownloadStore::~DownloadStore() = default;
+
+bool DownloadStore::IsInitialized() {
+  return is_initialized_;
+}
+
+void DownloadStore::Initialize(InitCallback callback) {
+  DCHECK(!IsInitialized());
+  db_->InitWithOptions(
+      kDatabaseClientName, leveldb_proto::Options(database_dir_),
+      base::BindOnce(&DownloadStore::OnDatabaseInited,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void DownloadStore::OnDatabaseInited(InitCallback callback, bool success) {
+  if (!success) {
+    std::move(callback).Run(success, base::MakeUnique<std::vector<Entry>>());
+    return;
+  }
+
+  db_->LoadEntries(base::BindOnce(&DownloadStore::OnDatabaseLoaded,
+                                  weak_factory_.GetWeakPtr(),
+                                  std::move(callback)));
+}
+
+void DownloadStore::OnDatabaseLoaded(InitCallback callback,
+                                     bool success,
+                                     std::unique_ptr<ProtoEntryVector> protos) {
+  if (!success) {
+    std::move(callback).Run(success, base::MakeUnique<std::vector<Entry>>());
+    return;
+  }
+
+  auto entries = ProtoConversions::EntryVectorFromProto(std::move(protos));
+  is_initialized_ = true;
+  std::move(callback).Run(success, std::move(entries));
+}
+
+void DownloadStore::Update(const Entry& entry, StoreCallback callback) {
+  DCHECK(IsInitialized());
+  auto entries_to_save = base::MakeUnique<KeyProtoEntryVector>();
+  protodb::Entry proto = ProtoConversions::EntryToProto(entry);
+  entries_to_save->emplace_back(entry.guid, std::move(proto));
+  db_->UpdateEntries(std::move(entries_to_save), base::MakeUnique<KeyVector>(),
+                     std::move(callback));
+}
+
+void DownloadStore::Remove(const std::string& guid, StoreCallback callback) {
+  DCHECK(IsInitialized());
+  auto keys_to_remove = base::MakeUnique<KeyVector>();
+  keys_to_remove->push_back(guid);
+  db_->UpdateEntries(base::MakeUnique<KeyProtoEntryVector>(),
+                     std::move(keys_to_remove), std::move(callback));
+}
+
+}  // namespace download
diff --git a/components/download/internal/download_store.h b/components/download/internal/download_store.h
new file mode 100644
index 0000000..e662e80c
--- /dev/null
+++ b/components/download/internal/download_store.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/download/internal/store.h"
+#include "components/leveldb_proto/proto_database.h"
+
+namespace protodb {
+class Entry;
+
+}  // namespace
+
+namespace download {
+
+// DownloadStore provides a layer around a LevelDB proto database that persists
+// the download request, scheduling params and metadata to the disk. The data is
+// read during initialization and presented to the caller after converting to
+// Entry entries.
+class DownloadStore : public Store {
+ public:
+  DownloadStore(
+      const base::FilePath& database_dir,
+      std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db);
+  ~DownloadStore() override;
+
+  // Store implementation.
+  bool IsInitialized() override;
+  void Initialize(InitCallback callback) override;
+  void Update(const Entry& entry, StoreCallback callback) override;
+  void Remove(const std::string& guid, StoreCallback callback) override;
+
+ private:
+  void OnDatabaseInited(InitCallback callback, bool success);
+  void OnDatabaseLoaded(InitCallback callback,
+                        bool success,
+                        std::unique_ptr<std::vector<protodb::Entry>> protos);
+
+  std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db_;
+  base::FilePath database_dir_;
+  bool is_initialized_;
+
+  base::WeakPtrFactory<DownloadStore> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadStore);
+};
+
+}  // namespace download
+
+#endif  // COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
diff --git a/components/download/internal/download_store_unittest.cc b/components/download/internal/download_store_unittest.cc
new file mode 100644
index 0000000..6a69d5b5
--- /dev/null
+++ b/components/download/internal/download_store_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/download_store.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "components/leveldb_proto/testing/fake_db.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace download {
+
+class DownloadStoreTest : public testing::Test {
+ public:
+  DownloadStoreTest() : db_(nullptr) {}
+
+  ~DownloadStoreTest() override = default;
+
+  void CreateDatabase() {
+    auto db = base::MakeUnique<leveldb_proto::test::FakeDB<protodb::Entry>>(
+        &db_entries_);
+    db_ = db.get();
+    store_.reset(new DownloadStore(
+        base::FilePath(FILE_PATH_LITERAL("/test/db/fakepath")), std::move(db)));
+  }
+
+  void InitCallback(std::vector<Entry>* loaded_entries,
+                    bool success,
+                    std::unique_ptr<std::vector<Entry>> entries) {
+    loaded_entries->swap(*entries);
+  }
+
+  void LoadCallback(std::vector<protodb::Entry>* loaded_entries,
+                    bool success,
+                    std::unique_ptr<std::vector<protodb::Entry>> entries) {
+    loaded_entries->swap(*entries);
+  }
+
+  MOCK_METHOD1(StoreCallback, void(bool));
+
+  void PrepopulateSampleEntries() {
+    Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+    Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+    db_entries_.insert(
+        std::make_pair(item1.guid, ProtoConversions::EntryToProto(item1)));
+    db_entries_.insert(
+        std::make_pair(item2.guid, ProtoConversions::EntryToProto(item2)));
+  }
+
+ protected:
+  std::map<std::string, protodb::Entry> db_entries_;
+  leveldb_proto::test::FakeDB<protodb::Entry>* db_;
+  std::unique_ptr<DownloadStore> store_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadStoreTest);
+};
+
+TEST_F(DownloadStoreTest, Initialize) {
+  PrepopulateSampleEntries();
+  CreateDatabase();
+  ASSERT_FALSE(store_->IsInitialized());
+
+  std::vector<Entry> preloaded_entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &preloaded_entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+
+  ASSERT_TRUE(store_->IsInitialized());
+  ASSERT_EQ(2u, preloaded_entries.size());
+}
+
+TEST_F(DownloadStoreTest, Update) {
+  PrepopulateSampleEntries();
+  CreateDatabase();
+
+  std::vector<Entry> preloaded_entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &preloaded_entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+  ASSERT_TRUE(store_->IsInitialized());
+  ASSERT_EQ(2u, preloaded_entries.size());
+
+  Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  EXPECT_CALL(*this, StoreCallback(true)).Times(2);
+  store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+                                   base::Unretained(this)));
+  db_->UpdateCallback(true);
+  store_->Update(item2, base::Bind(&DownloadStoreTest::StoreCallback,
+                                   base::Unretained(this)));
+  db_->UpdateCallback(true);
+
+  // Query the database directly and check for the entry.
+  auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+  db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+                              base::Unretained(this), protos.get()));
+  db_->LoadCallback(true);
+  ASSERT_EQ(4u, protos->size());
+  ASSERT_TRUE(test::CompareEntryList(
+      {preloaded_entries[0], preloaded_entries[1], item1, item2},
+      *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+TEST_F(DownloadStoreTest, Remove) {
+  PrepopulateSampleEntries();
+  CreateDatabase();
+
+  std::vector<Entry> preloaded_entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &preloaded_entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+  ASSERT_EQ(2u, preloaded_entries.size());
+
+  // Remove the entry.
+  EXPECT_CALL(*this, StoreCallback(true)).Times(1);
+  store_->Remove(
+      preloaded_entries[0].guid,
+      base::Bind(&DownloadStoreTest::StoreCallback, base::Unretained(this)));
+  db_->UpdateCallback(true);
+
+  // Query the database directly and check for the entry removed.
+  auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+  db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+                              base::Unretained(this), protos.get()));
+  db_->LoadCallback(true);
+  ASSERT_EQ(1u, protos->size());
+  ASSERT_TRUE(test::CompareEntryList(
+      {preloaded_entries[1]},
+      *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+TEST_F(DownloadStoreTest, InitializeFailed) {
+  PrepopulateSampleEntries();
+  CreateDatabase();
+
+  std::vector<Entry> preloaded_entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &preloaded_entries));
+  db_->InitCallback(false);
+  ASSERT_FALSE(store_->IsInitialized());
+  ASSERT_TRUE(preloaded_entries.empty());
+}
+
+TEST_F(DownloadStoreTest, InitialLoadFailed) {
+  PrepopulateSampleEntries();
+  CreateDatabase();
+
+  std::vector<Entry> preloaded_entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &preloaded_entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(false);
+  ASSERT_FALSE(store_->IsInitialized());
+  ASSERT_TRUE(preloaded_entries.empty());
+}
+
+TEST_F(DownloadStoreTest, UnsuccessfulUpdateOrRemove) {
+  Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  CreateDatabase();
+
+  std::vector<Entry> entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+  ASSERT_TRUE(store_->IsInitialized());
+  ASSERT_TRUE(entries.empty());
+
+  // Update failed.
+  EXPECT_CALL(*this, StoreCallback(false)).Times(1);
+  store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+                                   base::Unretained(this)));
+  db_->UpdateCallback(false);
+
+  // Remove failed.
+  EXPECT_CALL(*this, StoreCallback(false)).Times(1);
+  store_->Remove(item1.guid, base::Bind(&DownloadStoreTest::StoreCallback,
+                                        base::Unretained(this)));
+  db_->UpdateCallback(false);
+}
+
+TEST_F(DownloadStoreTest, AddThenRemove) {
+  CreateDatabase();
+
+  std::vector<Entry> entries;
+  store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+                                base::Unretained(this), &entries));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+  ASSERT_TRUE(entries.empty());
+
+  Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  EXPECT_CALL(*this, StoreCallback(true)).Times(2);
+  store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+                                   base::Unretained(this)));
+  db_->UpdateCallback(true);
+  store_->Update(item2, base::Bind(&DownloadStoreTest::StoreCallback,
+                                   base::Unretained(this)));
+  db_->UpdateCallback(true);
+
+  // Query the database directly and check for the entry.
+  auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+  db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+                              base::Unretained(this), protos.get()));
+  db_->LoadCallback(true);
+  ASSERT_EQ(2u, protos->size());
+
+  // Remove the entry.
+  EXPECT_CALL(*this, StoreCallback(true)).Times(1);
+  store_->Remove(item1.guid, base::Bind(&DownloadStoreTest::StoreCallback,
+                                        base::Unretained(this)));
+  db_->UpdateCallback(true);
+
+  // Query the database directly and check for the entry removed.
+  protos->clear();
+  db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+                              base::Unretained(this), protos.get()));
+  db_->LoadCallback(true);
+  ASSERT_EQ(1u, protos->size());
+  ASSERT_TRUE(test::CompareEntryList(
+      {item2}, *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+}  // namespace download
diff --git a/components/download/internal/model.h b/components/download/internal/model.h
index ce5de88e..d7f6d10 100644
--- a/components/download/internal/model.h
+++ b/components/download/internal/model.h
@@ -32,11 +32,6 @@
     // callback.  If |success| is true it can be accessed now.
     virtual void OnInitialized(bool success) = 0;
 
-    // Called asynchronously in response to a Model::Destroy call.  If |success|
-    // is |false|, destruction of the Model and/or the underlying Store failed.
-    // Destruction of the Model is effectively complete after this callback.
-    virtual void OnDestroyed(bool success) = 0;
-
     // Called when an Entry addition is complete.  |success| determines whether
     // or not the entry has been successfully persisted to the Store.
     virtual void OnItemAdded(bool success,
@@ -64,9 +59,6 @@
   // The Model can be used after that call.
   virtual void Initialize() = 0;
 
-  // Destroys the Model.  Client::OnDestroyed() will be called in response.
-  virtual void Destroy() = 0;
-
   // Adds |entry| to this Model and attempts to write |entry| to the Store.
   // Client::OnItemAdded() will be called in response asynchronously.
   virtual void Add(const Entry& entry) = 0;
diff --git a/components/download/internal/model_impl.cc b/components/download/internal/model_impl.cc
index f061b5d..3ab384b 100644
--- a/components/download/internal/model_impl.cc
+++ b/components/download/internal/model_impl.cc
@@ -20,13 +20,8 @@
 
 void ModelImpl::Initialize() {
   DCHECK(!store_->IsInitialized());
-  store_->Initialize(base::Bind(&ModelImpl::OnInitializedFinished,
-                                weak_ptr_factory_.GetWeakPtr()));
-}
-
-void ModelImpl::Destroy() {
-  store_->Destroy(base::Bind(&ModelImpl::OnDestroyFinished,
-                             weak_ptr_factory_.GetWeakPtr()));
+  store_->Initialize(base::BindOnce(&ModelImpl::OnInitializedFinished,
+                                    weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ModelImpl::Add(const Entry& entry) {
@@ -35,9 +30,9 @@
 
   entries_.emplace(entry.guid, base::MakeUnique<Entry>(entry));
 
-  store_->Update(entry, base::Bind(&ModelImpl::OnAddFinished,
-                                   weak_ptr_factory_.GetWeakPtr(), entry.client,
-                                   entry.guid));
+  store_->Update(entry, base::BindOnce(&ModelImpl::OnAddFinished,
+                                       weak_ptr_factory_.GetWeakPtr(),
+                                       entry.client, entry.guid));
 }
 
 void ModelImpl::Update(const Entry& entry) {
@@ -45,9 +40,9 @@
   DCHECK(entries_.find(entry.guid) != entries_.end());
 
   entries_[entry.guid] = base::MakeUnique<Entry>(entry);
-  store_->Update(entry, base::Bind(&ModelImpl::OnUpdateFinished,
-                                   weak_ptr_factory_.GetWeakPtr(), entry.client,
-                                   entry.guid));
+  store_->Update(entry, base::BindOnce(&ModelImpl::OnUpdateFinished,
+                                       weak_ptr_factory_.GetWeakPtr(),
+                                       entry.client, entry.guid));
 }
 
 void ModelImpl::Remove(const std::string& guid) {
@@ -59,8 +54,8 @@
   DownloadClient client = it->second->client;
   entries_.erase(it);
   store_->Remove(guid,
-                 base::Bind(&ModelImpl::OnRemoveFinished,
-                            weak_ptr_factory_.GetWeakPtr(), client, guid));
+                 base::BindOnce(&ModelImpl::OnRemoveFinished,
+                                weak_ptr_factory_.GetWeakPtr(), client, guid));
 }
 
 Entry* ModelImpl::Get(const std::string& guid) {
@@ -90,12 +85,6 @@
   client_->OnInitialized(true);
 }
 
-void ModelImpl::OnDestroyFinished(bool success) {
-  store_.reset();
-  entries_.clear();
-  client_->OnDestroyed(success);
-}
-
 void ModelImpl::OnAddFinished(DownloadClient client,
                               const std::string& guid,
                               bool success) {
diff --git a/components/download/internal/model_impl.h b/components/download/internal/model_impl.h
index 8c785019..3f4d198 100644
--- a/components/download/internal/model_impl.h
+++ b/components/download/internal/model_impl.h
@@ -28,7 +28,6 @@
 
   // Model implementation.
   void Initialize() override;
-  void Destroy() override;
   void Add(const Entry& entry) override;
   void Update(const Entry& entry) override;
   void Remove(const std::string& guid) override;
@@ -40,7 +39,6 @@
 
   void OnInitializedFinished(bool success,
                              std::unique_ptr<std::vector<Entry>> entries);
-  void OnDestroyFinished(bool success);
   void OnAddFinished(DownloadClient client,
                      const std::string& guid,
                      bool success);
diff --git a/components/download/internal/model_impl_unittest.cc b/components/download/internal/model_impl_unittest.cc
index 19a78391..c0b3f215 100644
--- a/components/download/internal/model_impl_unittest.cc
+++ b/components/download/internal/model_impl_unittest.cc
@@ -52,15 +52,10 @@
 TEST_F(DownloadServiceModelImplTest, SuccessfulLifecycle) {
   InSequence sequence;
   EXPECT_CALL(client_, OnInitialized(true)).Times(1);
-  EXPECT_CALL(client_, OnDestroyed(true)).Times(1);
 
   model_->Initialize();
   EXPECT_TRUE(store_->init_called());
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
-
-  model_->Destroy();
-  EXPECT_TRUE(store_->destroy_called());
-  store_->TriggerDestroy(true);
 }
 
 TEST_F(DownloadServiceModelImplTest, SuccessfulInitWithEntries) {
@@ -75,8 +70,8 @@
   EXPECT_TRUE(store_->init_called());
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
 
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
 }
 
 TEST_F(DownloadServiceModelImplTest, BadInit) {
@@ -87,20 +82,6 @@
   store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
 }
 
-TEST_F(DownloadServiceModelImplTest, BadDestroy) {
-  InSequence sequence;
-  EXPECT_CALL(client_, OnInitialized(true)).Times(1);
-  EXPECT_CALL(client_, OnDestroyed(false)).Times(1);
-
-  model_->Initialize();
-  EXPECT_TRUE(store_->init_called());
-  store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
-
-  model_->Destroy();
-  EXPECT_TRUE(store_->destroy_called());
-  store_->TriggerDestroy(false);
-}
-
 TEST_F(DownloadServiceModelImplTest, Add) {
   Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
   Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
@@ -114,15 +95,13 @@
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
 
   model_->Add(entry1);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
-  EXPECT_TRUE(
-      test::SuperficialEntryCompare(&entry1, store_->LastUpdatedEntry()));
+  EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry1, store_->LastUpdatedEntry()));
   store_->TriggerUpdate(true);
 
   model_->Add(entry2);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
-  EXPECT_TRUE(
-      test::SuperficialEntryCompare(&entry2, store_->LastUpdatedEntry()));
+  EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry()));
 
   store_->TriggerUpdate(false);
   EXPECT_EQ(nullptr, model_->Get(entry2.guid));
@@ -150,18 +129,16 @@
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
 
   model_->Update(entry2);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
-  EXPECT_TRUE(
-      test::SuperficialEntryCompare(&entry2, store_->LastUpdatedEntry()));
+  EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry()));
   store_->TriggerUpdate(true);
 
   model_->Update(entry3);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry3, model_->Get(entry3.guid)));
-  EXPECT_TRUE(
-      test::SuperficialEntryCompare(&entry3, store_->LastUpdatedEntry()));
+  EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry3, store_->LastUpdatedEntry()));
 
   store_->TriggerUpdate(false);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry3, model_->Get(entry3.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid)));
 }
 
 TEST_F(DownloadServiceModelImplTest, Remove) {
@@ -201,7 +178,7 @@
   model_->Initialize();
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
 
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry, model_->Get(entry.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid)));
   EXPECT_EQ(nullptr, model_->Get(base::GenerateGUID()));
 }
 
@@ -218,8 +195,7 @@
 
   std::vector<Entry*> expected_peek = {&entry1, &entry2};
 
-  EXPECT_TRUE(
-      test::SuperficialEntryListCompare(expected_peek, model_->PeekEntries()));
+  EXPECT_TRUE(test::CompareEntryList(expected_peek, model_->PeekEntries()));
 }
 
 TEST_F(DownloadServiceModelImplTest, TestRemoveAfterAdd) {
@@ -234,7 +210,7 @@
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
 
   model_->Add(entry);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry, model_->Get(entry.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid)));
 
   model_->Remove(entry.guid);
   EXPECT_EQ(nullptr, model_->Get(entry.guid));
@@ -259,10 +235,10 @@
 
   model_->Initialize();
   store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
 
   model_->Update(entry2);
-  EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
+  EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
 
   model_->Remove(entry2.guid);
   EXPECT_EQ(nullptr, model_->Get(entry2.guid));
diff --git a/components/download/internal/proto/BUILD.gn b/components/download/internal/proto/BUILD.gn
new file mode 100644
index 0000000..c9132cb
--- /dev/null
+++ b/components/download/internal/proto/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+  sources = [
+    "entry.proto",
+    "request.proto",
+    "scheduling.proto",
+  ]
+}
diff --git a/components/download/internal/proto/entry.proto b/components/download/internal/proto/entry.proto
new file mode 100644
index 0000000..d5f9dfed
--- /dev/null
+++ b/components/download/internal/proto/entry.proto
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+import "request.proto";
+import "scheduling.proto";
+
+// This should stay in sync with the DownloadClient enum
+// (components/download/public/clients.h).
+enum DownloadClient {
+  INVALID = 0;
+  TEST = 1;
+  OFFLINE_PAGE_PREFETCH = 2;
+  BOUNDARY = 3;
+}
+
+// Stores the request params, internal state, metrics and metadata associated
+// with a download request.
+message Entry {
+  // This should stay in sync with the State enum
+  // (components/download/internal/entry.h).
+  enum State {
+    NEW = 0;
+    AVAILABLE = 1;
+    ACTIVE = 2;
+    PAUSED = 3;
+    COMPLETE = 4;
+    WATCHDOG = 5;
+  }
+
+  // Identification Parameters.
+  optional DownloadClient name_space = 1;
+  optional string guid = 2;
+
+  // Requested Parameters.
+  optional SchedulingParams scheduling_params = 3;
+  optional RequestParams request_params = 4;
+
+  // Internal Tracking State.
+  optional State state = 5;
+}
diff --git a/components/download/internal/proto/request.proto b/components/download/internal/proto/request.proto
new file mode 100644
index 0000000..173c6856
--- /dev/null
+++ b/components/download/internal/proto/request.proto
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+message RequestHeader {
+  optional string key = 1;
+  optional string value = 2;
+}
+
+// Stores the HTTP request params associated with a download request.
+message RequestParams {
+  optional string url = 1;
+  optional string method = 2;
+  repeated RequestHeader headers = 3;
+}
diff --git a/components/download/internal/proto/scheduling.proto b/components/download/internal/proto/scheduling.proto
new file mode 100644
index 0000000..2b8cc7ac
--- /dev/null
+++ b/components/download/internal/proto/scheduling.proto
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+// Stores the scheduling params associated with a download request.
+message SchedulingParams {
+  // This should stay in sync with the NetworkRequirements enum
+  // (components/download/public/download_params.h).
+  enum NetworkRequirements {
+    NONE = 0;
+    OPTIMISTIC = 1;
+    UNMETERED = 2;
+  }
+
+  // This should stay in sync with the BatteryRequirements enum
+  // (components/download/public/download_params.h).
+  enum BatteryRequirements {
+    BATTERY_INSENSITIVE = 0;
+    BATTERY_SENSITIVE = 1;
+  }
+
+  // This should stay in sync with the Priority enum
+  // (components/download/public/download_params.h).
+  enum Priority {
+    LOW = 0;
+    NORMAL = 1;
+    HIGH = 2;
+    UI = 3;
+  }
+
+  // Uses internal time representation.
+  optional int64 cancel_time = 2;
+
+  optional Priority priority = 3;
+  optional NetworkRequirements network_requirements = 4;
+  optional BatteryRequirements battery_requirements = 5;
+}
diff --git a/components/download/internal/proto_conversions.cc b/components/download/internal/proto_conversions.cc
new file mode 100644
index 0000000..dc4187a
--- /dev/null
+++ b/components/download/internal/proto_conversions.cc
@@ -0,0 +1,285 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "components/download/internal/proto_conversions.h"
+#include "net/http/http_request_headers.h"
+
+namespace download {
+
+protodb::Entry_State ProtoConversions::RequestStateToProto(Entry::State state) {
+  switch (state) {
+    case Entry::State::NEW:
+      return protodb::Entry_State_NEW;
+    case Entry::State::AVAILABLE:
+      return protodb::Entry_State_AVAILABLE;
+    case Entry::State::ACTIVE:
+      return protodb::Entry_State_ACTIVE;
+    case Entry::State::PAUSED:
+      return protodb::Entry_State_PAUSED;
+    case Entry::State::COMPLETE:
+      return protodb::Entry_State_COMPLETE;
+    case Entry::State::WATCHDOG:
+      return protodb::Entry_State_WATCHDOG;
+  }
+
+  NOTREACHED();
+  return protodb::Entry_State_NEW;
+}
+
+Entry::State ProtoConversions::RequestStateFromProto(
+    protodb::Entry_State state) {
+  switch (state) {
+    case protodb::Entry_State_NEW:
+      return Entry::State::NEW;
+    case protodb::Entry_State_AVAILABLE:
+      return Entry::State::AVAILABLE;
+    case protodb::Entry_State_ACTIVE:
+      return Entry::State::ACTIVE;
+    case protodb::Entry_State_PAUSED:
+      return Entry::State::PAUSED;
+    case protodb::Entry_State_COMPLETE:
+      return Entry::State::COMPLETE;
+    case protodb::Entry_State_WATCHDOG:
+      return Entry::State::WATCHDOG;
+  }
+
+  NOTREACHED();
+  return Entry::State::NEW;
+}
+
+protodb::DownloadClient ProtoConversions::DownloadClientToProto(
+    DownloadClient client) {
+  switch (client) {
+    case DownloadClient::INVALID:
+      return protodb::DownloadClient::INVALID;
+    case DownloadClient::TEST:
+      return protodb::DownloadClient::TEST;
+    case DownloadClient::OFFLINE_PAGE_PREFETCH:
+      return protodb::DownloadClient::OFFLINE_PAGE_PREFETCH;
+    case DownloadClient::BOUNDARY:
+      return protodb::DownloadClient::BOUNDARY;
+  }
+
+  NOTREACHED();
+  return protodb::DownloadClient::INVALID;
+}
+
+DownloadClient ProtoConversions::DownloadClientFromProto(
+    protodb::DownloadClient client) {
+  switch (client) {
+    case protodb::DownloadClient::INVALID:
+      return DownloadClient::INVALID;
+    case protodb::DownloadClient::TEST:
+      return DownloadClient::TEST;
+    case protodb::DownloadClient::OFFLINE_PAGE_PREFETCH:
+      return DownloadClient::OFFLINE_PAGE_PREFETCH;
+    case protodb::DownloadClient::BOUNDARY:
+      return DownloadClient::BOUNDARY;
+  }
+
+  NOTREACHED();
+  return DownloadClient::INVALID;
+}
+
+SchedulingParams::NetworkRequirements
+ProtoConversions::NetworkRequirementsFromProto(
+    protodb::SchedulingParams_NetworkRequirements network_requirements) {
+  switch (network_requirements) {
+    case protodb::SchedulingParams_NetworkRequirements_NONE:
+      return SchedulingParams::NetworkRequirements::NONE;
+    case protodb::SchedulingParams_NetworkRequirements_OPTIMISTIC:
+      return SchedulingParams::NetworkRequirements::OPTIMISTIC;
+    case protodb::SchedulingParams_NetworkRequirements_UNMETERED:
+      return SchedulingParams::NetworkRequirements::UNMETERED;
+  }
+
+  NOTREACHED();
+  return SchedulingParams::NetworkRequirements::NONE;
+}
+
+protodb::SchedulingParams_NetworkRequirements
+ProtoConversions::NetworkRequirementsToProto(
+    SchedulingParams::NetworkRequirements network_requirements) {
+  switch (network_requirements) {
+    case SchedulingParams::NetworkRequirements::NONE:
+      return protodb::SchedulingParams_NetworkRequirements_NONE;
+    case SchedulingParams::NetworkRequirements::OPTIMISTIC:
+      return protodb::SchedulingParams_NetworkRequirements_OPTIMISTIC;
+    case SchedulingParams::NetworkRequirements::UNMETERED:
+      return protodb::SchedulingParams_NetworkRequirements_UNMETERED;
+  }
+
+  NOTREACHED();
+  return protodb::SchedulingParams_NetworkRequirements_NONE;
+}
+
+SchedulingParams::BatteryRequirements
+ProtoConversions::BatteryRequirementsFromProto(
+    protodb::SchedulingParams_BatteryRequirements battery_requirements) {
+  switch (battery_requirements) {
+    case protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE:
+      return SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+    case protodb::SchedulingParams_BatteryRequirements_BATTERY_SENSITIVE:
+      return SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+  }
+
+  NOTREACHED();
+  return SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+}
+
+protodb::SchedulingParams_BatteryRequirements
+ProtoConversions::BatteryRequirementsToProto(
+    SchedulingParams::BatteryRequirements battery_requirements) {
+  switch (battery_requirements) {
+    case SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE:
+      return protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE;
+    case SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE:
+      return protodb::SchedulingParams_BatteryRequirements_BATTERY_SENSITIVE;
+  }
+
+  NOTREACHED();
+  return protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE;
+}
+
+SchedulingParams::Priority ProtoConversions::SchedulingPriorityFromProto(
+    protodb::SchedulingParams_Priority priority) {
+  switch (priority) {
+    case protodb::SchedulingParams_Priority_LOW:
+      return SchedulingParams::Priority::LOW;
+    case protodb::SchedulingParams_Priority_NORMAL:
+      return SchedulingParams::Priority::NORMAL;
+    case protodb::SchedulingParams_Priority_HIGH:
+      return SchedulingParams::Priority::HIGH;
+    case protodb::SchedulingParams_Priority_UI:
+      return SchedulingParams::Priority::UI;
+  }
+
+  NOTREACHED();
+  return SchedulingParams::Priority::LOW;
+}
+
+protodb::SchedulingParams_Priority ProtoConversions::SchedulingPriorityToProto(
+    SchedulingParams::Priority priority) {
+  switch (priority) {
+    case SchedulingParams::Priority::LOW:
+      return protodb::SchedulingParams_Priority_LOW;
+    case SchedulingParams::Priority::NORMAL:
+      return protodb::SchedulingParams_Priority_NORMAL;
+    case SchedulingParams::Priority::HIGH:
+      return protodb::SchedulingParams_Priority_HIGH;
+    case SchedulingParams::Priority::UI:
+      return protodb::SchedulingParams_Priority_UI;
+  }
+
+  NOTREACHED();
+  return protodb::SchedulingParams_Priority_LOW;
+}
+
+SchedulingParams ProtoConversions::SchedulingParamsFromProto(
+    const protodb::SchedulingParams& proto) {
+  SchedulingParams scheduling_params;
+
+  scheduling_params.cancel_time =
+      base::Time::FromInternalValue(proto.cancel_time());
+  scheduling_params.priority = SchedulingPriorityFromProto(proto.priority());
+  scheduling_params.network_requirements =
+      NetworkRequirementsFromProto(proto.network_requirements());
+  scheduling_params.battery_requirements =
+      BatteryRequirementsFromProto(proto.battery_requirements());
+
+  return scheduling_params;
+}
+
+void ProtoConversions::SchedulingParamsToProto(
+    const SchedulingParams& scheduling_params,
+    protodb::SchedulingParams* proto) {
+  proto->set_cancel_time(scheduling_params.cancel_time.ToInternalValue());
+  proto->set_priority(SchedulingPriorityToProto(scheduling_params.priority));
+  proto->set_network_requirements(
+      NetworkRequirementsToProto(scheduling_params.network_requirements));
+  proto->set_battery_requirements(
+      BatteryRequirementsToProto(scheduling_params.battery_requirements));
+}
+
+RequestParams ProtoConversions::RequestParamsFromProto(
+    const protodb::RequestParams& proto) {
+  RequestParams request_params;
+  request_params.url = GURL(proto.url());
+  request_params.method = proto.method();
+
+  for (int i = 0; i < proto.headers_size(); i++) {
+    protodb::RequestHeader header = proto.headers(i);
+    request_params.request_headers.SetHeader(header.key(), header.value());
+  }
+
+  return request_params;
+}
+
+void ProtoConversions::RequestParamsToProto(const RequestParams& request_params,
+                                            protodb::RequestParams* proto) {
+  proto->set_url(request_params.url.spec());
+  proto->set_method(request_params.method);
+
+  int i = 0;
+  net::HttpRequestHeaders::Iterator iter(request_params.request_headers);
+  while (iter.GetNext()) {
+    protodb::RequestHeader* header = proto->add_headers();
+    header->set_key(iter.name());
+    header->set_value(iter.value());
+    i++;
+  }
+}
+
+Entry ProtoConversions::EntryFromProto(const protodb::Entry& proto) {
+  Entry entry;
+
+  entry.guid = proto.guid();
+  entry.client = DownloadClientFromProto(proto.name_space());
+  entry.scheduling_params =
+      SchedulingParamsFromProto(proto.scheduling_params());
+  entry.request_params = RequestParamsFromProto(proto.request_params());
+  entry.state = RequestStateFromProto(proto.state());
+
+  return entry;
+}
+
+protodb::Entry ProtoConversions::EntryToProto(const Entry& entry) {
+  protodb::Entry proto;
+
+  proto.set_guid(entry.guid);
+  proto.set_name_space(DownloadClientToProto(entry.client));
+  SchedulingParamsToProto(entry.scheduling_params,
+                          proto.mutable_scheduling_params());
+  RequestParamsToProto(entry.request_params, proto.mutable_request_params());
+  proto.set_state(RequestStateToProto(entry.state));
+
+  return proto;
+}
+
+std::unique_ptr<std::vector<Entry>> ProtoConversions::EntryVectorFromProto(
+    std::unique_ptr<std::vector<protodb::Entry>> protos) {
+  auto entries = base::MakeUnique<std::vector<Entry>>();
+  for (auto& proto : *protos) {
+    entries->push_back(EntryFromProto(proto));
+  }
+
+  return entries;
+}
+
+std::unique_ptr<std::vector<protodb::Entry>>
+ProtoConversions::EntryVectorToProto(
+    std::unique_ptr<std::vector<Entry>> entries) {
+  auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+  for (auto& entry : *entries) {
+    protos->push_back(EntryToProto(entry));
+  }
+
+  return protos;
+}
+
+}  // namespace download
diff --git a/components/download/internal/proto_conversions.h b/components/download/internal/proto_conversions.h
new file mode 100644
index 0000000..4d2e4e5
--- /dev/null
+++ b/components/download/internal/proto_conversions.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
+
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto/request.pb.h"
+#include "components/download/internal/proto/scheduling.pb.h"
+
+namespace download {
+
+class ProtoConversions {
+ public:
+  static Entry EntryFromProto(const protodb::Entry& proto);
+
+  static protodb::Entry EntryToProto(const Entry& entry);
+
+  static std::unique_ptr<std::vector<Entry>> EntryVectorFromProto(
+      std::unique_ptr<std::vector<protodb::Entry>> proto);
+
+  static std::unique_ptr<std::vector<protodb::Entry>> EntryVectorToProto(
+      std::unique_ptr<std::vector<Entry>> entries);
+
+ protected:
+  static protodb::Entry_State RequestStateToProto(Entry::State state);
+  static Entry::State RequestStateFromProto(protodb::Entry_State state);
+
+  static protodb::DownloadClient DownloadClientToProto(DownloadClient client);
+  static DownloadClient DownloadClientFromProto(protodb::DownloadClient client);
+
+  static SchedulingParams::NetworkRequirements NetworkRequirementsFromProto(
+      protodb::SchedulingParams_NetworkRequirements network_requirements);
+  static protodb::SchedulingParams_NetworkRequirements
+  NetworkRequirementsToProto(
+      SchedulingParams::NetworkRequirements network_requirements);
+
+  static SchedulingParams::BatteryRequirements BatteryRequirementsFromProto(
+      protodb::SchedulingParams_BatteryRequirements battery_requirements);
+  static protodb::SchedulingParams_BatteryRequirements
+  BatteryRequirementsToProto(
+      SchedulingParams::BatteryRequirements battery_requirements);
+
+  static SchedulingParams::Priority SchedulingPriorityFromProto(
+      protodb::SchedulingParams_Priority priority);
+  static protodb::SchedulingParams_Priority SchedulingPriorityToProto(
+      SchedulingParams::Priority priority);
+
+  static SchedulingParams SchedulingParamsFromProto(
+      const protodb::SchedulingParams& proto);
+  static void SchedulingParamsToProto(const SchedulingParams& scheduling_params,
+                                      protodb::SchedulingParams* proto);
+
+  static RequestParams RequestParamsFromProto(
+      const protodb::RequestParams& proto);
+  static void RequestParamsToProto(const RequestParams& request_params,
+                                   protodb::RequestParams* proto);
+};
+
+}  // namespace download
+
+#endif  // COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
diff --git a/components/download/internal/proto_conversions_unittest.cc b/components/download/internal/proto_conversions_unittest.cc
new file mode 100644
index 0000000..04198c5
--- /dev/null
+++ b/components/download/internal/proto_conversions_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+std::string TEST_URL = "https://google.com";
+
+}  // namespace
+
+namespace download {
+
+class ProtoConversionsTest : public testing::Test, public ProtoConversions {
+ public:
+  ~ProtoConversionsTest() override {}
+};
+
+TEST_F(ProtoConversionsTest, StateConversion) {
+  Entry::State states[] = {Entry::State::NEW,      Entry::State::AVAILABLE,
+                           Entry::State::ACTIVE,   Entry::State::PAUSED,
+                           Entry::State::COMPLETE, Entry::State::WATCHDOG};
+  for (auto state : states) {
+    ASSERT_EQ(state, RequestStateFromProto(RequestStateToProto(state)));
+  }
+}
+
+TEST_F(ProtoConversionsTest, DownloadClientConversion) {
+  DownloadClient clients[] = {DownloadClient::INVALID, DownloadClient::TEST,
+                              DownloadClient::OFFLINE_PAGE_PREFETCH,
+                              DownloadClient::BOUNDARY};
+  for (auto client : clients) {
+    ASSERT_EQ(client, DownloadClientFromProto(DownloadClientToProto(client)));
+  }
+}
+
+TEST_F(ProtoConversionsTest, NetworkRequirementsConversion) {
+  SchedulingParams::NetworkRequirements values[] = {
+      SchedulingParams::NetworkRequirements::NONE,
+      SchedulingParams::NetworkRequirements::OPTIMISTIC,
+      SchedulingParams::NetworkRequirements::UNMETERED};
+  for (auto value : values) {
+    ASSERT_EQ(value,
+              NetworkRequirementsFromProto(NetworkRequirementsToProto(value)));
+  }
+}
+
+TEST_F(ProtoConversionsTest, BatteryRequirementsConversion) {
+  SchedulingParams::BatteryRequirements values[] = {
+      SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE,
+      SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE};
+  for (auto value : values) {
+    ASSERT_EQ(value,
+              BatteryRequirementsFromProto(BatteryRequirementsToProto(value)));
+  }
+}
+
+TEST_F(ProtoConversionsTest, SchedulingPriorityConversion) {
+  SchedulingParams::Priority values[] = {
+      SchedulingParams::Priority::LOW, SchedulingParams::Priority::NORMAL,
+      SchedulingParams::Priority::HIGH, SchedulingParams::Priority::UI,
+      SchedulingParams::Priority::DEFAULT};
+  for (auto value : values) {
+    ASSERT_EQ(value,
+              SchedulingPriorityFromProto(SchedulingPriorityToProto(value)));
+  }
+}
+
+TEST_F(ProtoConversionsTest, SchedulingParamsConversion) {
+  SchedulingParams expected;
+  expected.cancel_time = base::Time::Now();
+  expected.priority = SchedulingParams::Priority::DEFAULT;
+  expected.network_requirements =
+      SchedulingParams::NetworkRequirements::OPTIMISTIC;
+  expected.battery_requirements =
+      SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+
+  protodb::SchedulingParams proto;
+  SchedulingParamsToProto(expected, &proto);
+  SchedulingParams actual = SchedulingParamsFromProto(proto);
+  EXPECT_EQ(expected.cancel_time, actual.cancel_time);
+  EXPECT_EQ(SchedulingParams::Priority::DEFAULT, actual.priority);
+  EXPECT_EQ(SchedulingParams::NetworkRequirements::OPTIMISTIC,
+            actual.network_requirements);
+  EXPECT_EQ(SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+            actual.battery_requirements);
+}
+
+TEST_F(ProtoConversionsTest, RequestParamsWithHeadersConversion) {
+  RequestParams expected;
+  expected.url = GURL(TEST_URL);
+  expected.method = "GET";
+  expected.request_headers.SetHeader("key1", "value1");
+  expected.request_headers.SetHeader("key2", "value2");
+
+  protodb::RequestParams proto;
+  RequestParamsToProto(expected, &proto);
+  RequestParams actual = RequestParamsFromProto(proto);
+
+  EXPECT_EQ(expected.url, actual.url);
+  EXPECT_EQ(expected.method, actual.method);
+
+  std::string out;
+  actual.request_headers.GetHeader("key1", &out);
+  EXPECT_EQ("value1", out);
+  actual.request_headers.GetHeader("key2", &out);
+  EXPECT_EQ("value2", out);
+  EXPECT_EQ(expected.request_headers.ToString(),
+            actual.request_headers.ToString());
+}
+
+TEST_F(ProtoConversionsTest, EntryConversion) {
+  Entry expected = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+  Entry actual = EntryFromProto(EntryToProto(expected));
+  EXPECT_TRUE(test::CompareEntry(&expected, &actual));
+
+  expected = test::BuildEntry(
+      DownloadClient::TEST, base::GenerateGUID(), base::Time::Now(),
+      SchedulingParams::NetworkRequirements::OPTIMISTIC,
+      SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+      SchedulingParams::Priority::HIGH, GURL(TEST_URL), "GET",
+      Entry::State::ACTIVE);
+  actual = EntryFromProto(EntryToProto(expected));
+  EXPECT_TRUE(test::CompareEntry(&expected, &actual));
+}
+
+TEST_F(ProtoConversionsTest, EntryVectorConversion) {
+  std::vector<Entry> expected;
+  expected.push_back(
+      test::BuildEntry(DownloadClient::TEST, base::GenerateGUID()));
+  expected.push_back(test::BuildEntry(DownloadClient::OFFLINE_PAGE_PREFETCH,
+                                      base::GenerateGUID()));
+  expected.push_back(test::BuildEntry(
+      DownloadClient::TEST, base::GenerateGUID(), base::Time::Now(),
+      SchedulingParams::NetworkRequirements::OPTIMISTIC,
+      SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+      SchedulingParams::Priority::HIGH, GURL(TEST_URL), "GET",
+      Entry::State::ACTIVE));
+
+  auto actual = EntryVectorFromProto(
+      EntryVectorToProto(base::MakeUnique<std::vector<Entry>>(expected)));
+  EXPECT_TRUE(test::CompareEntryList(expected, *actual));
+}
+
+}  // namespace download
diff --git a/components/download/internal/store.h b/components/download/internal/store.h
index 04a39bf..4622b26a 100644
--- a/components/download/internal/store.h
+++ b/components/download/internal/store.h
@@ -10,8 +10,6 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "base/sequenced_task_runner.h"
 
 namespace download {
 
@@ -36,10 +34,6 @@
   // Store.
   virtual void Initialize(InitCallback callback) = 0;
 
-  // Destroyes the store and asynchronously returns whether or not that
-  // destruction was successful.
-  virtual void Destroy(StoreCallback callback) = 0;
-
   // Adds or updates |entry| in this Store asynchronously and returns whether or
   // not that was successful.
   virtual void Update(const Entry& entry, StoreCallback callback) = 0;
diff --git a/components/download/internal/test/BUILD.gn b/components/download/internal/test/BUILD.gn
index 87032dc..3ee693e 100644
--- a/components/download/internal/test/BUILD.gn
+++ b/components/download/internal/test/BUILD.gn
@@ -12,6 +12,8 @@
     "entry_utils.h",
     "mock_model_client.cc",
     "mock_model_client.h",
+    "noop_store.cc",
+    "noop_store.h",
     "test_download_driver.cc",
     "test_download_driver.h",
     "test_store.cc",
diff --git a/components/download/internal/test/entry_utils.cc b/components/download/internal/test/entry_utils.cc
index 33b4abf..b3b8e850 100644
--- a/components/download/internal/test/entry_utils.cc
+++ b/components/download/internal/test/entry_utils.cc
@@ -9,19 +9,41 @@
 namespace download {
 namespace test {
 
-bool SuperficialEntryCompare(const Entry* const& expected,
-                             const Entry* const& actual) {
+bool CompareEntry(const Entry* const& expected, const Entry* const& actual) {
   if (expected == nullptr || actual == nullptr)
     return expected == actual;
 
+  // TODO(shaktisahu): Add operator== in Entry.
   return expected->client == actual->client && expected->guid == actual->guid &&
+         expected->scheduling_params.cancel_time ==
+             actual->scheduling_params.cancel_time &&
+         expected->scheduling_params.network_requirements ==
+             actual->scheduling_params.network_requirements &&
+         expected->scheduling_params.battery_requirements ==
+             actual->scheduling_params.battery_requirements &&
+         expected->scheduling_params.priority ==
+             actual->scheduling_params.priority &&
+         expected->request_params.url == actual->request_params.url &&
+         expected->request_params.method == actual->request_params.method &&
+         expected->request_params.request_headers.ToString() ==
+             actual->request_params.request_headers.ToString() &&
          expected->state == actual->state;
 }
 
-bool SuperficialEntryListCompare(const std::vector<Entry*>& expected,
-                                 const std::vector<Entry*>& actual) {
+bool CompareEntryList(const std::vector<Entry*>& expected,
+                      const std::vector<Entry*>& actual) {
   return std::is_permutation(actual.cbegin(), actual.cend(), expected.cbegin(),
-                             SuperficialEntryCompare);
+                             CompareEntry);
+}
+
+bool EntryComparison(const Entry& expected, const Entry& actual) {
+  return CompareEntry(&expected, &actual);
+}
+
+bool CompareEntryList(const std::vector<Entry>& list1,
+                      const std::vector<Entry>& list2) {
+  return std::is_permutation(list1.begin(), list1.end(), list2.begin(),
+                             EntryComparison);
 }
 
 Entry BuildEntry(DownloadClient client, const std::string& guid) {
@@ -31,5 +53,25 @@
   return entry;
 }
 
+Entry BuildEntry(DownloadClient client,
+                 const std::string& guid,
+                 base::Time cancel_time,
+                 SchedulingParams::NetworkRequirements network_requirements,
+                 SchedulingParams::BatteryRequirements battery_requirements,
+                 SchedulingParams::Priority priority,
+                 const GURL& url,
+                 const std::string& request_method,
+                 Entry::State state) {
+  Entry entry = BuildEntry(client, guid);
+  entry.scheduling_params.cancel_time = cancel_time;
+  entry.scheduling_params.network_requirements = network_requirements;
+  entry.scheduling_params.battery_requirements = battery_requirements;
+  entry.scheduling_params.priority = priority;
+  entry.request_params.url = url;
+  entry.request_params.method = request_method;
+  entry.state = state;
+  return entry;
+}
+
 }  // namespace test
 }  // namespace download
diff --git a/components/download/internal/test/entry_utils.h b/components/download/internal/test/entry_utils.h
index 2a7b422..54144be6 100644
--- a/components/download/internal/test/entry_utils.h
+++ b/components/download/internal/test/entry_utils.h
@@ -13,14 +13,26 @@
 namespace download {
 namespace test {
 
-bool SuperficialEntryCompare(const Entry* const& expected,
-                             const Entry* const& actual);
+bool CompareEntry(const Entry* const& expected, const Entry* const& actual);
 
-bool SuperficialEntryListCompare(const std::vector<Entry*>& a,
-                                 const std::vector<Entry*>& b);
+bool CompareEntryList(const std::vector<Entry*>& a,
+                      const std::vector<Entry*>& b);
+
+bool CompareEntryList(const std::vector<Entry>& list1,
+                      const std::vector<Entry>& list2);
 
 Entry BuildEntry(DownloadClient client, const std::string& guid);
 
+Entry BuildEntry(DownloadClient client,
+                 const std::string& guid,
+                 base::Time cancel_time,
+                 SchedulingParams::NetworkRequirements network_requirements,
+                 SchedulingParams::BatteryRequirements battery_requirements,
+                 SchedulingParams::Priority priority,
+                 const GURL& url,
+                 const std::string& request_method,
+                 Entry::State state);
+
 }  // namespace test
 }  // namespace download
 
diff --git a/components/download/internal/test/mock_model_client.h b/components/download/internal/test/mock_model_client.h
index 2b82f0c..cd9a7f8 100644
--- a/components/download/internal/test/mock_model_client.h
+++ b/components/download/internal/test/mock_model_client.h
@@ -20,7 +20,6 @@
 
   // Model::Client implementation.
   MOCK_METHOD1(OnInitialized, void(bool));
-  MOCK_METHOD1(OnDestroyed, void(bool));
   MOCK_METHOD3(OnItemAdded, void(bool, DownloadClient, const std::string&));
   MOCK_METHOD3(OnItemUpdated, void(bool, DownloadClient, const std::string&));
   MOCK_METHOD3(OnItemRemoved, void(bool, DownloadClient, const std::string&));
diff --git a/components/download/internal/noop_store.cc b/components/download/internal/test/noop_store.cc
similarity index 85%
rename from components/download/internal/noop_store.cc
rename to components/download/internal/test/noop_store.cc
index 42785cb..59931bb6 100644
--- a/components/download/internal/noop_store.cc
+++ b/components/download/internal/test/noop_store.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/download/internal/noop_store.h"
+#include "components/download/internal/test/noop_store.h"
 
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -27,11 +27,6 @@
                      std::move(callback)));
 }
 
-void NoopStore::Destroy(StoreCallback callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
-}
-
 void NoopStore::Update(const Entry& entry, StoreCallback callback) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
diff --git a/components/download/internal/noop_store.h b/components/download/internal/test/noop_store.h
similarity index 84%
rename from components/download/internal/noop_store.h
rename to components/download/internal/test/noop_store.h
index 4bad9ed..edc03d70 100644
--- a/components/download/internal/noop_store.h
+++ b/components/download/internal/test/noop_store.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
-#define COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_TEST_NOOP_STORE_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_TEST_NOOP_STORE_H_
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -25,7 +25,6 @@
   // Store implementation.
   bool IsInitialized() override;
   void Initialize(InitCallback callback) override;
-  void Destroy(StoreCallback callback) override;
   void Update(const Entry& entry, StoreCallback callback) override;
   void Remove(const std::string& guid, StoreCallback callback) override;
 
@@ -43,4 +42,4 @@
 
 }  // namespace download
 
-#endif  // COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
+#endif  // COMPONENTS_DOWNLOAD_INTERNAL_TEST_NOOP_STORE_H_
diff --git a/components/download/internal/test/test_store.cc b/components/download/internal/test/test_store.cc
index 01faec7..c9e472b94 100644
--- a/components/download/internal/test/test_store.cc
+++ b/components/download/internal/test/test_store.cc
@@ -9,8 +9,7 @@
 namespace download {
 namespace test {
 
-TestStore::TestStore()
-    : ready_(false), init_called_(false), destroy_called_(false) {}
+TestStore::TestStore() : ready_(false), init_called_(false) {}
 
 TestStore::~TestStore() {}
 
@@ -23,11 +22,6 @@
   init_callback_ = std::move(callback);
 }
 
-void TestStore::Destroy(StoreCallback callback) {
-  destroy_called_ = true;
-  destroy_callback_ = std::move(callback);
-}
-
 void TestStore::Update(const Entry& entry, StoreCallback callback) {
   updated_entries_.push_back(entry);
   update_callback_ = std::move(callback);
@@ -46,11 +40,6 @@
   std::move(init_callback_).Run(success, std::move(entries));
 }
 
-void TestStore::TriggerDestroy(bool success) {
-  DCHECK(destroy_callback_);
-  std::move(destroy_callback_).Run(success);
-}
-
 void TestStore::TriggerUpdate(bool success) {
   DCHECK(update_callback_);
   std::move(update_callback_).Run(success);
diff --git a/components/download/internal/test/test_store.h b/components/download/internal/test/test_store.h
index e64d8ba..2cb5f57 100644
--- a/components/download/internal/test/test_store.h
+++ b/components/download/internal/test/test_store.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "components/download/internal/store.h"
 
@@ -25,13 +26,11 @@
   // Store implementation.
   bool IsInitialized() override;
   void Initialize(InitCallback callback) override;
-  void Destroy(StoreCallback callback) override;
   void Update(const Entry& entry, StoreCallback callback) override;
   void Remove(const std::string& guid, StoreCallback callback) override;
 
   // Callback trigger methods.
   void TriggerInit(bool success, std::unique_ptr<std::vector<Entry>> entries);
-  void TriggerDestroy(bool success);
   void TriggerUpdate(bool success);
   void TriggerRemove(bool success);
 
@@ -39,7 +38,6 @@
   const Entry* LastUpdatedEntry() const;
   std::string LastRemovedEntry() const;
   bool init_called() const { return init_called_; }
-  bool destroy_called() const { return destroy_called_; }
   const std::vector<Entry>& updated_entries() const { return updated_entries_; }
   const std::vector<std::string>& removed_entries() const {
     return removed_entries_;
@@ -49,13 +47,11 @@
   bool ready_;
 
   bool init_called_;
-  bool destroy_called_;
 
   std::vector<Entry> updated_entries_;
   std::vector<std::string> removed_entries_;
 
   InitCallback init_callback_;
-  StoreCallback destroy_callback_;
   StoreCallback update_callback_;
   StoreCallback remove_callback_;
 
diff --git a/components/exo/shell_surface.cc b/components/exo/shell_surface.cc
index 998769b..955dc0a1 100644
--- a/components/exo/shell_surface.cc
+++ b/components/exo/shell_surface.cc
@@ -108,17 +108,23 @@
     if (component != HTNOWHERE && component != HTCLIENT)
       return true;
 
-    // If there is an underlay, test against it's bounds instead since it will
-    // be equal or larger than the surface's bounds.
+    // If there is an underlay, test against it first as it's bounds may be
+    // larger than the surface's bounds.
     aura::Window* shadow_underlay =
         static_cast<ShellSurface*>(
             widget_->widget_delegate()->GetContentsView())
             ->shadow_underlay();
     if (shadow_underlay) {
-      aura::Window::ConvertPointToTarget(window, shadow_underlay, &local_point);
-      return gfx::Rect(shadow_underlay->layer()->size()).Contains(local_point);
+      gfx::Point local_point_in_shadow_underlay = local_point;
+      aura::Window::ConvertPointToTarget(window, shadow_underlay,
+                                         &local_point_in_shadow_underlay);
+      if (gfx::Rect(shadow_underlay->layer()->size())
+              .Contains(local_point_in_shadow_underlay)) {
+        return true;
+      }
     }
 
+    // Otherwise, fallback to hit test on the surface.
     aura::Window::ConvertPointToTarget(window, surface->window(), &local_point);
     return surface->HitTestRect(gfx::Rect(local_point, gfx::Size(1, 1)));
   }
@@ -128,7 +134,8 @@
     aura::Window* window = static_cast<aura::Window*>(root);
     Surface* surface = ShellSurface::GetMainSurface(window);
 
-    // Send events which are outside of the surface's bounds to the underlay.
+    // Send events which wouldn't be handled by the surface, to the shadow
+    // underlay.
     aura::Window* shadow_underlay =
         static_cast<ShellSurface*>(
             widget_->widget_delegate()->GetContentsView())
@@ -136,8 +143,12 @@
     if (surface && event->IsLocatedEvent() && shadow_underlay) {
       gfx::Point local_point = event->AsLocatedEvent()->location();
       int component = widget_->non_client_view()->NonClientHitTest(local_point);
-      if (component == HTNOWHERE)
-        return shadow_underlay;
+      if (component == HTNOWHERE) {
+        aura::Window::ConvertPointToTarget(window, surface->window(),
+                                           &local_point);
+        if (!surface->HitTestRect(gfx::Rect(local_point, gfx::Size(1, 1))))
+          return shadow_underlay;
+      }
     }
     return aura::WindowTargeter::FindTargetForEvent(root, event);
   }
diff --git a/components/feature_engagement_tracker/internal/availability_store.cc b/components/feature_engagement_tracker/internal/availability_store.cc
index 3c0163c8..8877818 100644
--- a/components/feature_engagement_tracker/internal/availability_store.cc
+++ b/components/feature_engagement_tracker/internal/availability_store.cc
@@ -107,10 +107,11 @@
   }
 
   // Write all changes to the DB.
-  db->UpdateEntries(std::move(additions), std::move(deletes),
-                    base::BindOnce(&OnDBUpdateComplete, std::move(db),
-                                   std::move(on_loaded_callback),
-                                   std::move(feature_availabilities)));
+  auto* db_ptr = db.get();
+  db_ptr->UpdateEntries(std::move(additions), std::move(deletes),
+                        base::BindOnce(&OnDBUpdateComplete, std::move(db),
+                                       std::move(on_loaded_callback),
+                                       std::move(feature_availabilities)));
 }
 
 void OnDBInitComplete(
@@ -128,9 +129,10 @@
     return;
   }
 
-  db->LoadEntries(base::BindOnce(&OnDBLoadComplete, std::move(db),
-                                 std::move(feature_filter),
-                                 std::move(on_loaded_callback), current_day));
+  auto* db_ptr = db.get();
+  db_ptr->LoadEntries(base::BindOnce(
+      &OnDBLoadComplete, std::move(db), std::move(feature_filter),
+      std::move(on_loaded_callback), current_day));
 }
 
 }  // namespace
@@ -142,10 +144,11 @@
     FeatureVector feature_filter,
     AvailabilityStore::OnLoadedCallback on_loaded_callback,
     uint32_t current_day) {
-  db->Init(kDatabaseUMAName, storage_dir,
-           base::BindOnce(&OnDBInitComplete, std::move(db),
-                          std::move(feature_filter),
-                          std::move(on_loaded_callback), current_day));
+  auto* db_ptr = db.get();
+  db_ptr->Init(kDatabaseUMAName, storage_dir,
+               base::BindOnce(&OnDBInitComplete, std::move(db),
+                              std::move(feature_filter),
+                              std::move(on_loaded_callback), current_day));
 }
 
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc b/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
index c252b63..6bcf53c 100644
--- a/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
+++ b/components/feature_engagement_tracker/internal/system_time_provider_unittest.cc
@@ -23,7 +23,7 @@
   exploded_time.millisecond = 0;
 
   base::Time out_time;
-  EXPECT_TRUE(base::Time::FromLocalExploded(exploded_time, &out_time));
+  EXPECT_TRUE(base::Time::FromUTCExploded(exploded_time, &out_time));
   return out_time;
 }
 
diff --git a/components/ntp_snippets/content_suggestions_service.h b/components/ntp_snippets/content_suggestions_service.h
index 40c442b..ddeae6e3e 100644
--- a/components/ntp_snippets/content_suggestions_service.h
+++ b/components/ntp_snippets/content_suggestions_service.h
@@ -15,7 +15,6 @@
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/scoped_observer.h"
-#include "base/supports_user_data.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/time/time.h"
 #include "components/history/core/browser/history_service.h"
@@ -48,7 +47,6 @@
 // Retrieves suggestions from a number of ContentSuggestionsProviders and serves
 // them grouped into categories. There can be at most one provider per category.
 class ContentSuggestionsService : public KeyedService,
-                                  public base::SupportsUserData,
                                   public ContentSuggestionsProvider::Observer,
                                   public SigninManagerBase::Observer,
                                   public history::HistoryServiceObserver {
diff --git a/components/offline_pages/content/BUILD.gn b/components/offline_pages/content/BUILD.gn
deleted file mode 100644
index 5b1ddb5..0000000
--- a/components/offline_pages/content/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-if (is_android) {
-  import("//build/config/android/rules.gni")
-}
-
-static_library("content") {
-  sources = [
-    "prefetch_service_factory.cc",
-    "prefetch_service_factory.h",
-    "suggested_articles_observer.cc",
-    "suggested_articles_observer.h",
-  ]
-
-  deps = [
-    "//base",
-    "//components/keyed_service/content",
-    "//components/ntp_snippets",
-    "//components/offline_pages/core",
-    "//components/offline_pages/core:switches",
-    "//components/offline_pages/core/prefetch",
-    "//content/public/browser",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "suggested_articles_observer_unittest.cc",
-  ]
-
-  deps = [
-    ":content",
-    "//base",
-    "//base/test:test_support",
-    "//components/offline_pages/core",
-    "//components/offline_pages/core:test_support",
-    "//components/offline_pages/core/prefetch",
-    "//content/test:test_support",
-    "//testing/gtest",
-    "//url",
-  ]
-}
diff --git a/components/offline_pages/content/DEPS b/components/offline_pages/content/DEPS
deleted file mode 100644
index 4edd3b1..0000000
--- a/components/offline_pages/content/DEPS
+++ /dev/null
@@ -1,6 +0,0 @@
-include_rules = [
-  "+components/keyed_service/content",
-  "+components/ntp_snippets",
-  "+content/public/browser",
-  "+content/public/test",
-]
diff --git a/components/offline_pages/content/suggested_articles_observer.cc b/components/offline_pages/content/suggested_articles_observer.cc
deleted file mode 100644
index 4b60a28..0000000
--- a/components/offline_pages/content/suggested_articles_observer.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/offline_pages/content/suggested_articles_observer.h"
-
-#include <unordered_set>
-
-#include "base/memory/ptr_util.h"
-#include "components/ntp_snippets/category.h"
-#include "components/ntp_snippets/category_status.h"
-#include "components/offline_pages/content/prefetch_service_factory.h"
-#include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/offline_page_feature.h"
-#include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
-#include "components/offline_pages/core/prefetch/prefetch_service.h"
-
-using ntp_snippets::Category;
-using ntp_snippets::ContentSuggestion;
-
-namespace offline_pages {
-
-namespace {
-
-int kOfflinePageSuggestedArticlesObserverUserDataKey;
-
-const ntp_snippets::Category& ArticlesCategory() {
-  static ntp_snippets::Category articles =
-      Category::FromKnownCategory(ntp_snippets::KnownCategories::ARTICLES);
-  return articles;
-}
-
-ClientId CreateClientIDFromSuggestionId(const ContentSuggestion::ID& id) {
-  return ClientId(kSuggestedArticlesNamespace, id.id_within_category());
-}
-
-// The default delegate that contains external dependencies for the Offline Page
-// Suggestions Observer.  This is unused in tests, which implement their own
-// Delegate.
-class DefaultDelegate : public SuggestedArticlesObserver::Delegate {
- public:
-  explicit DefaultDelegate(ntp_snippets::ContentSuggestionsService* service);
-  ~DefaultDelegate() override = default;
-
-  const std::vector<ContentSuggestion>& GetSuggestions(
-      const Category& category) override;
-  PrefetchService* GetPrefetchService(
-      content::BrowserContext* context) override;
-
- private:
-  ntp_snippets::ContentSuggestionsService* service_;
-};
-
-DefaultDelegate::DefaultDelegate(
-    ntp_snippets::ContentSuggestionsService* service)
-    : service_(service) {}
-
-const std::vector<ContentSuggestion>& DefaultDelegate::GetSuggestions(
-    const Category& category) {
-  return service_->GetSuggestionsForCategory(category);
-}
-
-PrefetchService* DefaultDelegate::GetPrefetchService(
-    content::BrowserContext* context) {
-  return PrefetchServiceFactory::GetForBrowserContext(context);
-}
-
-}  // namespace
-
-// static
-void SuggestedArticlesObserver::ObserveContentSuggestionsService(
-    content::BrowserContext* browser_context,
-    ntp_snippets::ContentSuggestionsService* service) {
-  if (!offline_pages::IsPrefetchingOfflinePagesEnabled())
-    return;
-
-  auto suggestions_observer = base::MakeUnique<SuggestedArticlesObserver>(
-      browser_context, base::MakeUnique<DefaultDelegate>(service));
-  service->AddObserver(suggestions_observer.get());
-  service->SetUserData(&kOfflinePageSuggestedArticlesObserverUserDataKey,
-                       std::move(suggestions_observer));
-}
-
-SuggestedArticlesObserver::SuggestedArticlesObserver(
-    content::BrowserContext* browser_context,
-    std::unique_ptr<Delegate> delegate)
-    : browser_context_(browser_context), delegate_(std::move(delegate)) {}
-
-SuggestedArticlesObserver::~SuggestedArticlesObserver() = default;
-
-void SuggestedArticlesObserver::OnNewSuggestions(Category category) {
-  // TODO(dewittj): Change this to check whether a given category is not
-  // a _remote_ category.
-  if (category != ArticlesCategory() ||
-      category_status_ != ntp_snippets::CategoryStatus::AVAILABLE) {
-    return;
-  }
-
-  const std::vector<ContentSuggestion>& suggestions =
-      delegate_->GetSuggestions(ArticlesCategory());
-  if (suggestions.empty())
-    return;
-
-  std::vector<PrefetchDispatcher::PrefetchURL> prefetch_urls;
-  for (const ContentSuggestion& suggestion : suggestions) {
-    prefetch_urls.push_back(
-        {CreateClientIDFromSuggestionId(suggestion.id()), suggestion.url()});
-  }
-
-  PrefetchService* service = delegate_->GetPrefetchService(browser_context_);
-  if (service == nullptr) {
-    DVLOG(1) << "PrefetchService unavailable to the "
-                "SuggestedArticlesObserver.";
-    return;
-  }
-  service->GetDispatcher()->AddCandidatePrefetchURLs(prefetch_urls);
-}
-
-void SuggestedArticlesObserver::OnCategoryStatusChanged(
-    Category category,
-    ntp_snippets::CategoryStatus new_status) {
-  if (category != ArticlesCategory() || category_status_ == new_status)
-    return;
-
-  category_status_ = new_status;
-
-  if (category_status_ ==
-          ntp_snippets::CategoryStatus::CATEGORY_EXPLICITLY_DISABLED ||
-      category_status_ ==
-          ntp_snippets::CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED) {
-    PrefetchService* service = delegate_->GetPrefetchService(browser_context_);
-    if (service == nullptr) {
-      DVLOG(1) << "PrefetchService unavailable to the "
-                  "SuggestedArticlesObserver.";
-      return;
-    }
-    service->GetDispatcher()->RemoveAllUnprocessedPrefetchURLs(
-        kSuggestedArticlesNamespace);
-  }
-}
-
-void SuggestedArticlesObserver::OnSuggestionInvalidated(
-    const ContentSuggestion::ID& suggestion_id) {
-  PrefetchService* service = delegate_->GetPrefetchService(browser_context_);
-  if (service == nullptr) {
-    DVLOG(1) << "PrefetchService unavailable to the "
-                "SuggestedArticlesObserver.";
-    return;
-  }
-  service->GetDispatcher()->RemovePrefetchURLsByClientId(
-      CreateClientIDFromSuggestionId(suggestion_id));
-}
-
-void SuggestedArticlesObserver::OnFullRefreshRequired() {
-  PrefetchService* service = delegate_->GetPrefetchService(browser_context_);
-  if (service == nullptr) {
-    DVLOG(1) << "PrefetchService unavailable to the "
-                "SuggestedArticlesObserver.";
-    return;
-  }
-  service->GetDispatcher()->RemoveAllUnprocessedPrefetchURLs(
-      kSuggestedArticlesNamespace);
-  OnNewSuggestions(ArticlesCategory());
-}
-
-void SuggestedArticlesObserver::ContentSuggestionsServiceShutdown() {
-  // No need to do anything here, we will just stop getting events.
-}
-
-}  // namespace offline_pages
diff --git a/components/offline_pages/content/suggested_articles_observer.h b/components/offline_pages/content/suggested_articles_observer.h
deleted file mode 100644
index 6448c01..0000000
--- a/components/offline_pages/content/suggested_articles_observer.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_OFFLINE_PAGES_CONTENT_SUGGESTED_ARTICLES_OBSERVER_H_
-#define COMPONENTS_OFFLINE_PAGES_CONTENT_SUGGESTED_ARTICLES_OBSERVER_H_
-
-#include <memory>
-
-#include "base/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "components/ntp_snippets/content_suggestions_service.h"
-
-namespace content {
-class BrowserContext;
-}  // namespace content
-
-namespace ntp_snippets {
-class Category;
-}
-
-namespace offline_pages {
-class PrefetchService;
-
-// Observes the ContentSuggestionsService, listening for new suggestions in the
-// ARTICLES category.  When those suggestions arrive, it then forwards them to
-// the Prefetch Service, which does not know about Content Suggestions
-// specifically.
-class SuggestedArticlesObserver
-    : public ntp_snippets::ContentSuggestionsService::Observer,
-      public base::SupportsUserData::Data {
- public:
-  // Delegate exists to allow for dependency injection in unit tests.
-  // SuggestedArticlesObserver implements its own delegate, |DefaultDelegate| in
-  // the .cc file that forwards to the ContentSuggestionsService and the
-  // PrefetchServiceFactory.  Code inside |DefaultDelegate| should be as simple
-  // as possible, since it will only be covered by instrumentation/browser
-  // tests.
-  class Delegate {
-   public:
-    virtual const std::vector<ntp_snippets::ContentSuggestion>& GetSuggestions(
-        const ntp_snippets::Category& category) = 0;
-    virtual PrefetchService* GetPrefetchService(
-        content::BrowserContext* context) = 0;
-    virtual ~Delegate() = default;
-  };
-
-  // This API creates a new SuggestedArticlesObserver and adds it as an
-  // observer to the ContentSuggestionsService provided.  Its lifetime is
-  // managed by the ContentSuggestionsService.
-  static void ObserveContentSuggestionsService(
-      content::BrowserContext* browser_context,
-      ntp_snippets::ContentSuggestionsService* service);
-
-  SuggestedArticlesObserver(content::BrowserContext* browser_context,
-                            std::unique_ptr<Delegate> delegate);
-  ~SuggestedArticlesObserver() override;
-
-  // ContentSuggestionsService::Observer overrides.
-  void OnNewSuggestions(ntp_snippets::Category category) override;
-  void OnCategoryStatusChanged(
-      ntp_snippets::Category category,
-      ntp_snippets::CategoryStatus new_status) override;
-  void OnSuggestionInvalidated(
-      const ntp_snippets::ContentSuggestion::ID& suggestion_id) override;
-  void OnFullRefreshRequired() override;
-  void ContentSuggestionsServiceShutdown() override;
-
- private:
-  content::BrowserContext* browser_context_;
-  ntp_snippets::CategoryStatus category_status_ =
-      ntp_snippets::CategoryStatus::INITIALIZING;
-  std::unique_ptr<Delegate> delegate_;
-
-  DISALLOW_COPY_AND_ASSIGN(SuggestedArticlesObserver);
-};
-
-}  // namespace offline_pages
-
-#endif  // COMPONENTS_OFFLINE_PAGES_CONTENT_SUGGESTED_ARTICLES_OBSERVER_H_
diff --git a/components/offline_pages/core/DEPS b/components/offline_pages/core/DEPS
index a232688..5ef8eb9f 100644
--- a/components/offline_pages/core/DEPS
+++ b/components/offline_pages/core/DEPS
@@ -1,5 +1,7 @@
 include_rules = [
   "+components/keyed_service",
+  "+components/gcm_driver",
+  "+components/ntp_snippets",
   "+components/version_info",
   "+net",
   "+sql",
diff --git a/components/offline_pages/core/prefetch/BUILD.gn b/components/offline_pages/core/prefetch/BUILD.gn
index ae365d2b..58ca40a7 100644
--- a/components/offline_pages/core/prefetch/BUILD.gn
+++ b/components/offline_pages/core/prefetch/BUILD.gn
@@ -16,6 +16,9 @@
     "prefetch_dispatcher.h",
     "prefetch_dispatcher_impl.cc",
     "prefetch_dispatcher_impl.h",
+    "prefetch_gcm_app_handler.cc",
+    "prefetch_gcm_app_handler.h",
+    "prefetch_gcm_handler.h",
     "prefetch_item.cc",
     "prefetch_item.h",
     "prefetch_proto_utils.cc",
@@ -27,6 +30,8 @@
     "prefetch_service_impl.h",
     "prefetch_types.cc",
     "prefetch_types.h",
+    "suggested_articles_observer.cc",
+    "suggested_articles_observer.h",
   ]
 
   public_deps = [
@@ -34,8 +39,12 @@
   ]
   deps = [
     "//base",
+    "//components/gcm_driver",
+    "//components/gcm_driver/common",
     "//components/keyed_service/core",
+    "//components/ntp_snippets",
     "//components/offline_pages/core",
+    "//components/offline_pages/core:switches",
     "//net:net",
     "//url",
   ]
@@ -62,11 +71,13 @@
     "prefetch_request_operation_response_unittest.cc",
     "prefetch_request_test_base.cc",
     "prefetch_request_test_base.h",
+    "suggested_articles_observer_unittest.cc",
   ]
 
   deps = [
     ":prefetch",
     "//components/offline_pages/core",
+    "//components/offline_pages/core:test_support",
     "//net:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.cc b/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.cc
new file mode 100644
index 0000000..e6baf087c
--- /dev/null
+++ b/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
+
+#include "base/memory/ptr_util.h"
+#include "components/offline_pages/core/prefetch/prefetch_service.h"
+
+namespace offline_pages {
+namespace {
+const char kPrefetchingOfflinePagesAppId[] =
+    "com.google.chrome.OfflinePagePrefetch";
+}
+
+PrefetchGCMAppHandler::PrefetchGCMAppHandler() {}
+PrefetchGCMAppHandler::~PrefetchGCMAppHandler() = default;
+
+void PrefetchGCMAppHandler::ShutdownHandler() {
+  NOTIMPLEMENTED();
+}
+
+void PrefetchGCMAppHandler::OnStoreReset() {
+  NOTIMPLEMENTED();
+}
+
+void PrefetchGCMAppHandler::OnMessage(const std::string& app_id,
+                                      const gcm::IncomingMessage& message) {
+  NOTIMPLEMENTED();
+}
+
+void PrefetchGCMAppHandler::OnMessagesDeleted(const std::string& app_id) {
+  NOTIMPLEMENTED();
+}
+
+void PrefetchGCMAppHandler::OnSendError(
+    const std::string& app_id,
+    const gcm::GCMClient::SendErrorDetails& send_error_details) {
+  NOTIMPLEMENTED();
+}
+
+void PrefetchGCMAppHandler::OnSendAcknowledged(const std::string& app_id,
+                                               const std::string& message_id) {
+  NOTIMPLEMENTED();
+}
+
+bool PrefetchGCMAppHandler::CanHandle(const std::string& app_id) const {
+  return app_id == GetAppId();
+}
+
+gcm::GCMAppHandler* PrefetchGCMAppHandler::AsGCMAppHandler() {
+  return (gcm::GCMAppHandler*)this;
+}
+
+std::string PrefetchGCMAppHandler::GetAppId() const {
+  return kPrefetchingOfflinePagesAppId;
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h b/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h
new file mode 100644
index 0000000..beea579
--- /dev/null
+++ b/components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h
@@ -0,0 +1,50 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_APP_HANDLER_H_
+#define COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_APP_HANDLER_H_
+
+#include <string>
+
+#include "components/gcm_driver/common/gcm_messages.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/gcm_driver.h"
+#include "components/gcm_driver/gcm_profile_service.h"
+#include "components/offline_pages/core/prefetch/prefetch_gcm_handler.h"
+#include "url/gurl.h"
+
+namespace offline_pages {
+
+// Receives GCM messages and other channel status messages on behalf of the
+// prefetch system.
+class PrefetchGCMAppHandler : public gcm::GCMAppHandler,
+                              public PrefetchGCMHandler {
+ public:
+  PrefetchGCMAppHandler();
+  ~PrefetchGCMAppHandler() override;
+
+  // gcm::GCMAppHandler implementation.
+  void ShutdownHandler() override;
+  void OnStoreReset() override;
+  void OnMessage(const std::string& app_id,
+                 const gcm::IncomingMessage& message) override;
+  void OnMessagesDeleted(const std::string& app_id) override;
+  void OnSendError(
+      const std::string& app_id,
+      const gcm::GCMClient::SendErrorDetails& send_error_details) override;
+  void OnSendAcknowledged(const std::string& app_id,
+                          const std::string& message_id) override;
+  bool CanHandle(const std::string& app_id) const override;
+
+  // offline_pages::PrefetchGCMHandler implementation.
+  gcm::GCMAppHandler* AsGCMAppHandler() override;
+  std::string GetAppId() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrefetchGCMAppHandler);
+};
+
+}  // namespace offline_pages
+
+#endif  // COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_APP_HANDLER_H_
diff --git a/components/offline_pages/core/prefetch/prefetch_gcm_handler.h b/components/offline_pages/core/prefetch/prefetch_gcm_handler.h
new file mode 100644
index 0000000..7ce70f41
--- /dev/null
+++ b/components/offline_pages/core/prefetch/prefetch_gcm_handler.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_HANDLER_H_
+#define COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_HANDLER_H_
+
+#include <string>
+
+namespace gcm {
+class GCMAppHandler;
+}  // namespace gcm
+
+namespace offline_pages {
+
+class PrefetchGCMHandler;
+
+// Main class and entry point for the Offline Pages Prefetching feature, that
+// controls the lifetime of all major subcomponents of the prefetching system.
+class PrefetchGCMHandler {
+ public:
+  virtual ~PrefetchGCMHandler() = default;
+
+  // Returns the GCMAppHandler for this object.  Can return |nullptr| in unit
+  // tests.
+  virtual gcm::GCMAppHandler* AsGCMAppHandler() = 0;
+
+  // The app ID to register with at the GCM layer.
+  virtual std::string GetAppId() const = 0;
+
+  // TODO(dewittj): Add methods for acquiring an Instance ID token to this
+  // interface.
+};
+
+}  // namespace offline_pages
+
+#endif  // COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_PREFETCH_GCM_HANDLER_H_
diff --git a/components/offline_pages/core/prefetch/prefetch_service.h b/components/offline_pages/core/prefetch/prefetch_service.h
index 9e5ec6c..5731dd25 100644
--- a/components/offline_pages/core/prefetch/prefetch_service.h
+++ b/components/offline_pages/core/prefetch/prefetch_service.h
@@ -7,9 +7,14 @@
 
 #include "components/keyed_service/core/keyed_service.h"
 
+namespace ntp_snippets {
+class ContentSuggestionsService;
+}
+
 namespace offline_pages {
 
 class PrefetchDispatcher;
+class PrefetchGCMHandler;
 
 // Main class and entry point for the Offline Pages Prefetching feature, that
 // controls the lifetime of all major subcomponents of the prefetching system.
@@ -18,6 +23,12 @@
   ~PrefetchService() override = default;
 
   virtual PrefetchDispatcher* GetDispatcher() = 0;
+  virtual PrefetchGCMHandler* GetPrefetchGCMHandler() = 0;
+
+  // Called at construction of the ContentSuggestionsService to begin observing
+  // events related to incoming articles.
+  virtual void ObserveContentSuggestionsService(
+      ntp_snippets::ContentSuggestionsService* service) = 0;
 };
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/prefetch_service_impl.cc b/components/offline_pages/core/prefetch/prefetch_service_impl.cc
index 9e86aba..bf3a027 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_service_impl.cc
@@ -11,14 +11,26 @@
 
 namespace offline_pages {
 
-PrefetchServiceImpl::PrefetchServiceImpl()
-    : dispatcher_(base::MakeUnique<PrefetchDispatcherImpl>()) {}
+PrefetchServiceImpl::PrefetchServiceImpl(
+    std::unique_ptr<PrefetchGCMHandler> gcm_handler)
+    : gcm_handler_(std::move(gcm_handler)),
+      dispatcher_(base::MakeUnique<PrefetchDispatcherImpl>()) {}
 
 PrefetchServiceImpl::~PrefetchServiceImpl() = default;
 
+PrefetchGCMHandler* PrefetchServiceImpl::GetPrefetchGCMHandler() {
+  return gcm_handler_.get();
+}
+
 PrefetchDispatcher* PrefetchServiceImpl::GetDispatcher() {
   return dispatcher_.get();
-};
+}
+
+void PrefetchServiceImpl::ObserveContentSuggestionsService(
+    ntp_snippets::ContentSuggestionsService* service) {
+  suggested_articles_observer_ =
+      base::MakeUnique<SuggestedArticlesObserver>(service, this);
+}
 
 void PrefetchServiceImpl::Shutdown() {}
 
diff --git a/components/offline_pages/core/prefetch/prefetch_service_impl.h b/components/offline_pages/core/prefetch/prefetch_service_impl.h
index c476f13..cb50f9421 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_impl.h
+++ b/components/offline_pages/core/prefetch/prefetch_service_impl.h
@@ -8,22 +8,33 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "components/offline_pages/core/prefetch/prefetch_gcm_handler.h"
 #include "components/offline_pages/core/prefetch/prefetch_service.h"
+#include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
+
+namespace ntp_snippets {
+class ContentSuggestionsService;
+}
 
 namespace offline_pages {
 
 class PrefetchServiceImpl : public PrefetchService {
  public:
-  PrefetchServiceImpl();
+  PrefetchServiceImpl(std::unique_ptr<PrefetchGCMHandler> gcm_handler);
   ~PrefetchServiceImpl() override;
 
   // PrefetchService implementation:
+  void ObserveContentSuggestionsService(
+      ntp_snippets::ContentSuggestionsService* service) override;
   PrefetchDispatcher* GetDispatcher() override;
+  PrefetchGCMHandler* GetPrefetchGCMHandler() override;
 
   // KeyedService implementation:
   void Shutdown() override;
 
  private:
+  std::unique_ptr<SuggestedArticlesObserver> suggested_articles_observer_;
+  std::unique_ptr<PrefetchGCMHandler> gcm_handler_;
   std::unique_ptr<PrefetchDispatcher> dispatcher_;
 
   DISALLOW_COPY_AND_ASSIGN(PrefetchServiceImpl);
diff --git a/components/offline_pages/core/prefetch/prefetch_service_impl_unittest.cc b/components/offline_pages/core/prefetch/prefetch_service_impl_unittest.cc
new file mode 100644
index 0000000..5dfee742
--- /dev/null
+++ b/components/offline_pages/core/prefetch/prefetch_service_impl_unittest.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/offline_pages/core/prefetch/prefetch_service_impl.h"
+
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace offline_pages {
+
+TEST(PrefetchServiceTest, ServiceDoesNotCrash) {
+  PrefetchServiceImpl service(nullptr);
+
+  service.AddCandidatePrefetchURLs(std::vector<PrefetchService::PrefetchURL>());
+  service.RemoveAllUnprocessedPrefetchURLs(kSuggestedArticlesNamespace);
+  service.RemovePrefetchURLsByClientId({kSuggestedArticlesNamespace, "123"});
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/suggested_articles_observer.cc b/components/offline_pages/core/prefetch/suggested_articles_observer.cc
new file mode 100644
index 0000000..2a1990e6
--- /dev/null
+++ b/components/offline_pages/core/prefetch/suggested_articles_observer.cc
@@ -0,0 +1,115 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
+
+#include <unordered_set>
+
+#include "base/memory/ptr_util.h"
+#include "components/ntp_snippets/category.h"
+#include "components/ntp_snippets/category_status.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "components/offline_pages/core/offline_page_feature.h"
+#include "components/offline_pages/core/offline_page_item.h"
+#include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
+#include "components/offline_pages/core/prefetch/prefetch_service_impl.h"
+
+using ntp_snippets::Category;
+using ntp_snippets::ContentSuggestion;
+
+namespace offline_pages {
+
+namespace {
+
+const ntp_snippets::Category& ArticlesCategory() {
+  static ntp_snippets::Category articles =
+      Category::FromKnownCategory(ntp_snippets::KnownCategories::ARTICLES);
+  return articles;
+}
+
+ClientId CreateClientIDFromSuggestionId(const ContentSuggestion::ID& id) {
+  return ClientId(kSuggestedArticlesNamespace, id.id_within_category());
+}
+
+}  // namespace
+
+SuggestedArticlesObserver::SuggestedArticlesObserver(
+    ntp_snippets::ContentSuggestionsService* content_suggestions_service,
+    PrefetchService* prefetch_service)
+    : content_suggestions_service_(content_suggestions_service),
+      prefetch_service_(prefetch_service) {
+  // The content suggestions service can be |nullptr| in tests.
+  if (content_suggestions_service_)
+    content_suggestions_service_->AddObserver(this);
+}
+
+SuggestedArticlesObserver::~SuggestedArticlesObserver() = default;
+
+void SuggestedArticlesObserver::OnNewSuggestions(Category category) {
+  // TODO(dewittj): Change this to check whether a given category is not
+  // a _remote_ category.
+  if (category != ArticlesCategory() ||
+      category_status_ != ntp_snippets::CategoryStatus::AVAILABLE) {
+    return;
+  }
+
+  const std::vector<ContentSuggestion>& suggestions =
+      test_articles_ ? *test_articles_
+                     : content_suggestions_service_->GetSuggestionsForCategory(
+                           ArticlesCategory());
+  if (suggestions.empty())
+    return;
+
+  std::vector<PrefetchDispatcher::PrefetchURL> prefetch_urls;
+  for (const ContentSuggestion& suggestion : suggestions) {
+    prefetch_urls.push_back(
+        {CreateClientIDFromSuggestionId(suggestion.id()), suggestion.url()});
+  }
+
+  prefetch_service_->GetDispatcher()->AddCandidatePrefetchURLs(prefetch_urls);
+}
+
+void SuggestedArticlesObserver::OnCategoryStatusChanged(
+    Category category,
+    ntp_snippets::CategoryStatus new_status) {
+  if (category != ArticlesCategory() || category_status_ == new_status)
+    return;
+
+  category_status_ = new_status;
+
+  if (category_status_ ==
+          ntp_snippets::CategoryStatus::CATEGORY_EXPLICITLY_DISABLED ||
+      category_status_ ==
+          ntp_snippets::CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED) {
+    prefetch_service_->GetDispatcher()->RemoveAllUnprocessedPrefetchURLs(
+        kSuggestedArticlesNamespace);
+  }
+}
+
+void SuggestedArticlesObserver::OnSuggestionInvalidated(
+    const ContentSuggestion::ID& suggestion_id) {
+  prefetch_service_->GetDispatcher()->RemovePrefetchURLsByClientId(
+      CreateClientIDFromSuggestionId(suggestion_id));
+}
+
+void SuggestedArticlesObserver::OnFullRefreshRequired() {
+  prefetch_service_->GetDispatcher()->RemoveAllUnprocessedPrefetchURLs(
+      kSuggestedArticlesNamespace);
+  OnNewSuggestions(ArticlesCategory());
+}
+
+void SuggestedArticlesObserver::ContentSuggestionsServiceShutdown() {
+  // No need to do anything here, we will just stop getting events.
+}
+
+std::vector<ntp_snippets::ContentSuggestion>*
+SuggestedArticlesObserver::GetTestingArticles() {
+  if (!test_articles_) {
+    test_articles_ =
+        base::MakeUnique<std::vector<ntp_snippets::ContentSuggestion>>();
+  }
+  return test_articles_.get();
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/suggested_articles_observer.h b/components/offline_pages/core/prefetch/suggested_articles_observer.h
new file mode 100644
index 0000000..1af83d71
--- /dev/null
+++ b/components/offline_pages/core/prefetch/suggested_articles_observer.h
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_SUGGESTED_ARTICLES_OBSERVER_H_
+#define COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_SUGGESTED_ARTICLES_OBSERVER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/ntp_snippets/content_suggestions_service.h"
+#include "components/offline_pages/core/prefetch/prefetch_service.h"
+
+namespace ntp_snippets {
+class Category;
+}
+
+namespace offline_pages {
+
+// Observes the ContentSuggestionsService, listening for new suggestions in the
+// ARTICLES category.  When those suggestions arrive, it then forwards them to
+// the Prefetch Service, which does not know about Content Suggestions
+// specifically.
+class SuggestedArticlesObserver
+    : public ntp_snippets::ContentSuggestionsService::Observer {
+ public:
+  SuggestedArticlesObserver(
+      // This can be |nullptr| in test.
+      ntp_snippets::ContentSuggestionsService* content_suggestions_service,
+      PrefetchService* prefetch_service);
+  ~SuggestedArticlesObserver() override;
+
+  // ContentSuggestionsService::Observer overrides.
+  void OnNewSuggestions(ntp_snippets::Category category) override;
+  void OnCategoryStatusChanged(
+      ntp_snippets::Category category,
+      ntp_snippets::CategoryStatus new_status) override;
+  void OnSuggestionInvalidated(
+      const ntp_snippets::ContentSuggestion::ID& suggestion_id) override;
+  void OnFullRefreshRequired() override;
+  void ContentSuggestionsServiceShutdown() override;
+
+  // Returns a pointer to the list of testing articles. If there is no such
+  // list, allocates one before returning the list.  The observer owns the list.
+  std::vector<ntp_snippets::ContentSuggestion>* GetTestingArticles();
+
+ private:
+  // Unowned, only used when we are called by observer methods (so the
+  // pointer will be valid).
+  ntp_snippets::ContentSuggestionsService* content_suggestions_service_;
+
+  // This class is owned by the prefetch service.
+  PrefetchService* prefetch_service_;
+
+  // Normally null, but can be set in tests to override the default behavior.
+  std::unique_ptr<std::vector<ntp_snippets::ContentSuggestion>> test_articles_;
+
+  ntp_snippets::CategoryStatus category_status_ =
+      ntp_snippets::CategoryStatus::INITIALIZING;
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestedArticlesObserver);
+};
+
+}  // namespace offline_pages
+
+#endif  // COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_SUGGESTED_ARTICLES_OBSERVER_H_
diff --git a/components/offline_pages/content/suggested_articles_observer_unittest.cc b/components/offline_pages/core/prefetch/suggested_articles_observer_unittest.cc
similarity index 69%
rename from components/offline_pages/content/suggested_articles_observer_unittest.cc
rename to components/offline_pages/core/prefetch/suggested_articles_observer_unittest.cc
index 6fae70d8..99595db 100644
--- a/components/offline_pages/content/suggested_articles_observer_unittest.cc
+++ b/components/offline_pages/core/prefetch/suggested_articles_observer_unittest.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/offline_pages/content/suggested_articles_observer.h"
+#include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
 
 #include "base/run_loop.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
 #include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
+#include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
 #include "components/offline_pages/core/prefetch/prefetch_service.h"
 #include "components/offline_pages/core/stub_offline_page_model.h"
-#include "content/public/test/test_browser_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -67,36 +67,13 @@
  public:
   TestingPrefetchService() = default;
 
+  PrefetchGCMHandler* GetPrefetchGCMHandler() override { return nullptr; }
   PrefetchDispatcher* GetDispatcher() override { return &dispatcher; };
-
+  void ObserveContentSuggestionsService(
+      ntp_snippets::ContentSuggestionsService* content_suggestions_service)
+      override {}
   TestingPrefetchDispatcher dispatcher;
 };
-
-class TestDelegate : public SuggestedArticlesObserver::Delegate {
- public:
-  TestDelegate() = default;
-  ~TestDelegate() override = default;
-
-  const std::vector<ContentSuggestion>& GetSuggestions(
-      const Category& category) override {
-    get_suggestions_count++;
-    return suggestions;
-  }
-
-  PrefetchService* GetPrefetchService(
-      content::BrowserContext* context) override {
-    return &prefetch_service;
-  }
-
-  TestingPrefetchService prefetch_service;
-
-  // Public for test manipulation.
-  std::vector<ContentSuggestion> suggestions;
-
-  // Signals that delegate was called.
-  int get_suggestions_count = 0;
-};
-
 }  // namespace
 
 class OfflinePageSuggestedArticlesObserverTest : public testing::Test {
@@ -104,23 +81,13 @@
   OfflinePageSuggestedArticlesObserverTest() = default;
 
   void SetUp() override {
-    observer_ =
-        base::MakeUnique<SuggestedArticlesObserver>(&context_, MakeDelegate());
-  }
-
-  virtual std::unique_ptr<SuggestedArticlesObserver::Delegate> MakeDelegate() {
-    auto delegate_ptr = base::MakeUnique<TestDelegate>();
-    test_delegate_ = delegate_ptr.get();
-    return std::move(delegate_ptr);
+    observer_ = base::MakeUnique<SuggestedArticlesObserver>(
+        nullptr, test_prefetch_service());
   }
 
   SuggestedArticlesObserver* observer() { return observer_.get(); }
 
-  TestDelegate* test_delegate() { return test_delegate_; }
-
-  TestingPrefetchService* test_prefetch_service() {
-    return &(test_delegate()->prefetch_service);
-  }
+  TestingPrefetchService* test_prefetch_service() { return &prefetch_service_; }
 
   TestingPrefetchDispatcher* test_prefetch_dispatcher() {
     return &(test_prefetch_service()->dispatcher);
@@ -129,36 +96,16 @@
  protected:
   Category category =
       Category::FromKnownCategory(ntp_snippets::KnownCategories::ARTICLES);
-  content::TestBrowserContext context_;
 
  private:
   std::unique_ptr<SuggestedArticlesObserver> observer_;
-  TestDelegate* test_delegate_;
+  TestingPrefetchService prefetch_service_;
 };
 
 TEST_F(OfflinePageSuggestedArticlesObserverTest,
-       CallsDelegateOnNewSuggestions) {
-  // We should not do anything if the category is not loaded.
-  observer()->OnNewSuggestions(category);
-  EXPECT_EQ(0, test_delegate()->get_suggestions_count);
-  EXPECT_EQ(0, test_prefetch_dispatcher()->new_suggestions_count);
-
-  // Once the category becomes available, new suggestions should cause us to ask
-  // the delegate for suggestion URLs.
-  observer()->OnCategoryStatusChanged(category,
-                                      ntp_snippets::CategoryStatus::AVAILABLE);
-  observer()->OnNewSuggestions(category);
-  EXPECT_EQ(1, test_delegate()->get_suggestions_count);
-
-  // We expect that no pages were forwarded to the prefetch service since no
-  // pages were prepopulated.
-  EXPECT_EQ(0, test_prefetch_dispatcher()->new_suggestions_count);
-}
-
-TEST_F(OfflinePageSuggestedArticlesObserverTest,
        ForwardsSuggestionsToPrefetchService) {
   const GURL test_url_1("https://www.example.com/1");
-  test_delegate()->suggestions.push_back(
+  observer()->GetTestingArticles()->push_back(
       ContentSuggestionFromTestURL(test_url_1));
 
   observer()->OnCategoryStatusChanged(category,
@@ -176,9 +123,9 @@
 TEST_F(OfflinePageSuggestedArticlesObserverTest, RemovesAllOnBadStatus) {
   const GURL test_url_1("https://www.example.com/1");
   const GURL test_url_2("https://www.example.com/2");
-  test_delegate()->suggestions.push_back(
+  observer()->GetTestingArticles()->push_back(
       ContentSuggestionFromTestURL(test_url_1));
-  test_delegate()->suggestions.push_back(
+  observer()->GetTestingArticles()->push_back(
       ContentSuggestionFromTestURL(test_url_2));
 
   observer()->OnCategoryStatusChanged(category,
@@ -197,7 +144,7 @@
 
 TEST_F(OfflinePageSuggestedArticlesObserverTest, RemovesClientIdOnInvalidated) {
   const GURL test_url_1("https://www.example.com/1");
-  test_delegate()->suggestions.push_back(
+  observer()->GetTestingArticles()->push_back(
       ContentSuggestionFromTestURL(test_url_1));
   observer()->OnCategoryStatusChanged(category,
                                       ntp_snippets::CategoryStatus::AVAILABLE);
diff --git a/components/sync/OWNERS b/components/sync/OWNERS
index 7f146cf..5e1a5c4 100644
--- a/components/sync/OWNERS
+++ b/components/sync/OWNERS
@@ -1,4 +1,3 @@
-maxbogue@chromium.org
 pavely@chromium.org
 skym@chromium.org
 stanisc@chromium.org
diff --git a/components/sync/engine_impl/sync_scheduler_impl.cc b/components/sync/engine_impl/sync_scheduler_impl.cc
index 4c5ebce..c2055eb 100644
--- a/components/sync/engine_impl/sync_scheduler_impl.cc
+++ b/components/sync/engine_impl/sync_scheduler_impl.cc
@@ -774,14 +774,15 @@
 void SyncSchedulerImpl::PerformDelayedNudge() {
   // Circumstances may have changed since we scheduled this delayed nudge.
   // We must check to see if it's OK to run the job before we do so.
-  if (CanRunNudgeJobNow(NORMAL_PRIORITY))
+  if (CanRunNudgeJobNow(NORMAL_PRIORITY)) {
     TrySyncCycleJob();
-
-  // We're not responsible for setting up any retries here.  The functions that
-  // first put us into a state that prevents successful sync cycles (eg. global
-  // throttling, type throttling, network errors, transient errors) will also
-  // setup the appropriate retry logic (eg. retry after timeout, exponential
-  // backoff, retry when the network changes).
+  } else {
+    // If we set |waiting_interal_| while this PerformDelayedNudge was pending
+    // callback scheduled to |retry_timer_|, it's possible we didn't re-schedule
+    // because this PerformDelayedNudge was going to execute sooner. If that's
+    // the case, we need to make sure we setup to waiting callback now.
+    RestartWaiting();
+  }
 }
 
 void SyncSchedulerImpl::ExponentialBackoffRetry() {
diff --git a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
index fa5e417..71493bd 100644
--- a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
@@ -83,7 +83,7 @@
   RunLoop();
 }
 
-void PumpLoopFor(base::TimeDelta time) {
+void PumpLoopFor(TimeDelta time) {
   // Allow the loop to run for the specified amount of time.
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, base::Bind(&QuitLoopNow), time);
@@ -245,7 +245,7 @@
     return scheduler_->nudge_tracker_.IsAnyTypeBlocked();
   }
 
-  base::TimeDelta GetRetryTimerDelay() {
+  TimeDelta GetRetryTimerDelay() {
     EXPECT_TRUE(scheduler_->retry_timer_.IsRunning());
     return scheduler_->retry_timer_.GetCurrentDelay();
   }
@@ -256,7 +256,7 @@
     return MockInvalidation::Build(version, payload);
   }
 
-  base::TimeDelta GetTypeBlockingTime(ModelType type) {
+  TimeDelta GetTypeBlockingTime(ModelType type) {
     NudgeTracker::TypeTrackerMap::const_iterator tracker_it =
         scheduler_->nudge_tracker_.type_trackers_.find(type);
     DCHECK(tracker_it != scheduler_->nudge_tracker_.type_trackers_.end());
@@ -285,6 +285,11 @@
     return scheduler_->pending_wakeup_timer_.IsRunning();
   }
 
+  TimeDelta GetPendingWakeupTimerDelay() {
+    EXPECT_TRUE(scheduler_->pending_wakeup_timer_.IsRunning());
+    return scheduler_->pending_wakeup_timer_.GetCurrentDelay();
+  }
+
  private:
   syncable::Directory* directory() {
     return test_user_share_.user_share()->directory.get();
@@ -714,7 +719,7 @@
 
   // Set the start time to |poll_interval| in the future.
   TimeTicks optimal_start = TimeTicks::Now() + poll_interval;
-  StartSyncScheduler(base::Time::Now() + base::TimeDelta::FromMinutes(10));
+  StartSyncScheduler(base::Time::Now() + TimeDelta::FromMinutes(10));
 
   // Run again to wait for polling.
   RunLoop();
@@ -1448,7 +1453,7 @@
 
   // The new optimal time is now, since the desired poll should have happened
   // in the past.
-  optimal_job_time = base::TimeTicks::Now();
+  optimal_job_time = TimeTicks::Now();
   RunLoop();
   Mock::VerifyAndClearExpectations(syncer());
   ASSERT_EQ(kMinNumSamples, times.size());
@@ -1627,7 +1632,7 @@
   StartSyncScheduler(base::Time());
 
   SyncShareTimes times;
-  base::TimeDelta delay = base::TimeDelta::FromMilliseconds(10);
+  TimeDelta delay = TimeDelta::FromMilliseconds(10);
   scheduler()->OnReceivedGuRetryDelay(delay);
   EXPECT_EQ(delay, GetRetryTimerDelay());
 
@@ -1650,7 +1655,7 @@
 
   StartSyncScheduler(base::Time());
 
-  base::TimeDelta delay = base::TimeDelta::FromMilliseconds(10);
+  TimeDelta delay = TimeDelta::FromMilliseconds(10);
   scheduler()->OnReceivedGuRetryDelay(delay);
 
   EXPECT_CALL(*syncer(), NormalSyncShare(_, _, _))
@@ -1679,8 +1684,8 @@
   StartSyncScheduler(base::Time());
 
   SyncShareTimes times;
-  base::TimeDelta delay1 = base::TimeDelta::FromMilliseconds(100);
-  base::TimeDelta delay2 = base::TimeDelta::FromMilliseconds(200);
+  TimeDelta delay1 = TimeDelta::FromMilliseconds(100);
+  TimeDelta delay2 = TimeDelta::FromMilliseconds(200);
 
   scheduler()->ScheduleLocalNudge(ModelTypeSet(THEMES), FROM_HERE);
   scheduler()->OnReceivedGuRetryDelay(delay1);
@@ -1761,14 +1766,14 @@
   EXPECT_TRUE(GetBackedOffTypes().HasAll(types));
   EXPECT_FALSE(scheduler()->IsBackingOff());
   EXPECT_FALSE(scheduler()->IsCurrentlyThrottled());
-  base::TimeDelta first_blocking_time = GetTypeBlockingTime(THEMES);
+  TimeDelta first_blocking_time = GetTypeBlockingTime(THEMES);
 
   SetTypeBlockingMode(THEMES, WaitInterval::EXPONENTIAL_BACKOFF_RETRYING);
   // This won't cause a sync cycle because the types are backed off.
   scheduler()->ScheduleLocalNudge(types, FROM_HERE);
   PumpLoop();
   PumpLoop();
-  base::TimeDelta second_blocking_time = GetTypeBlockingTime(THEMES);
+  TimeDelta second_blocking_time = GetTypeBlockingTime(THEMES);
 
   // The Exponential backoff should be between previous backoff 1.5 and 2.5
   // times.
@@ -1900,4 +1905,48 @@
   StopSyncScheduler();
 }
 
+TEST_F(SyncSchedulerImplTest, InterleavedNudgesStillRestart) {
+  UseMockDelayProvider();
+  EXPECT_CALL(*delay(), GetDelay(_))
+      .WillOnce(Return(long_delay()))
+      .RetiresOnSaturation();
+  TimeDelta poll(TimeDelta::FromDays(1));
+  scheduler()->OnReceivedLongPollIntervalUpdate(poll);
+
+  StartSyncScheduler(base::Time());
+  scheduler()->ScheduleLocalNudge({THEMES}, FROM_HERE);
+  PumpLoop();  // To get PerformDelayedNudge called.
+  EXPECT_FALSE(BlockTimerIsRunning());
+  EXPECT_FALSE(scheduler()->IsBackingOff());
+
+  // This is the tricky piece. We have a gap while the sync job is bouncing to
+  // get onto the |pending_wakeup_timer_|, should be scheduled with no delay.
+  scheduler()->ScheduleLocalNudge({TYPED_URLS}, FROM_HERE);
+  EXPECT_TRUE(BlockTimerIsRunning());
+  EXPECT_EQ(TimeDelta(), GetPendingWakeupTimerDelay());
+  EXPECT_FALSE(scheduler()->IsBackingOff());
+
+  // Setup mock as we're about to attempt to sync.
+  SyncShareTimes times;
+  EXPECT_CALL(*syncer(), NormalSyncShare(_, _, _))
+      .WillOnce(DoAll(Invoke(test_util::SimulateCommitFailed),
+                      RecordSyncShare(&times, false)));
+  // Triggers the THEMES TrySyncCycleJobImpl(), which we've setup to fail. Its
+  // RestartWaiting won't schedule a delayed retry, as the TYPED_URLS nudge has
+  // a smaller delay. We verify this by making sure the delay is still zero.
+  PumpLoop();
+  EXPECT_TRUE(BlockTimerIsRunning());
+  EXPECT_EQ(TimeDelta(), GetPendingWakeupTimerDelay());
+  EXPECT_TRUE(scheduler()->IsBackingOff());
+
+  // Triggers TYPED_URLS PerformDelayedNudge(), which should no-op, because
+  // we're no long healthy, and normal priorities shouldn't go through, but it
+  // does need to setup the |pending_wakeup_timer_|. The delay should be ~60
+  // seconds, so verifying it's greater than 50 should be safe.
+  PumpLoop();
+  EXPECT_TRUE(BlockTimerIsRunning());
+  EXPECT_LT(TimeDelta::FromSeconds(50), GetPendingWakeupTimerDelay());
+  EXPECT_TRUE(scheduler()->IsBackingOff());
+}
+
 }  // namespace syncer
diff --git a/content/browser/accessibility/browser_accessibility_com_win.cc b/content/browser/accessibility/browser_accessibility_com_win.cc
index bc22482..69a6ed8 100644
--- a/content/browser/accessibility/browser_accessibility_com_win.cc
+++ b/content/browser/accessibility/browser_accessibility_com_win.cc
@@ -633,21 +633,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!role)
-    return E_INVALIDARG;
-
-  BrowserAccessibilityComWin* target = GetTargetFromChildID(var_id);
-  if (!target)
-    return E_INVALIDARG;
-
-  if (!target->role_name().empty()) {
-    role->vt = VT_BSTR;
-    role->bstrVal = SysAllocString(target->role_name().c_str());
-  } else {
-    role->vt = VT_I4;
-    role->lVal = target->ia_role();
-  }
-  return S_OK;
+  return AXPlatformNodeWin::get_accRole(var_id, role);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_accState(VARIANT var_id,
diff --git a/content/browser/frame_host/cross_process_frame_connector.cc b/content/browser/frame_host/cross_process_frame_connector.cc
index 1ca49b4..50d8b58 100644
--- a/content/browser/frame_host/cross_process_frame_connector.cc
+++ b/content/browser/frame_host/cross_process_frame_connector.cc
@@ -246,6 +246,7 @@
 
 void CrossProcessFrameConnector::OnUpdateViewportIntersection(
     const gfx::Rect& viewport_intersection) {
+  viewport_intersection_rect_ = viewport_intersection;
   if (view_)
     view_->UpdateViewportIntersection(viewport_intersection);
 }
diff --git a/content/browser/frame_host/cross_process_frame_connector.h b/content/browser/frame_host/cross_process_frame_connector.h
index 641a5100..3a7d977 100644
--- a/content/browser/frame_host/cross_process_frame_connector.h
+++ b/content/browser/frame_host/cross_process_frame_connector.h
@@ -133,12 +133,18 @@
   // Returns the view for the top-level frame under the same WebContents.
   RenderWidgetHostViewBase* GetRootRenderWidgetHostView();
 
+  const gfx::Rect& viewport_intersection() const {
+    return viewport_intersection_rect_;
+  }
+
   // Exposed for tests.
   RenderWidgetHostViewBase* GetRootRenderWidgetHostViewForTesting() {
     return GetRootRenderWidgetHostView();
   }
 
  private:
+  friend class MockCrossProcessFrameConnector;
+
   // Handlers for messages received from the parent frame.
   void OnFrameRectChanged(const gfx::Rect& frame_rect);
   void OnUpdateViewportIntersection(const gfx::Rect& viewport_intersection);
@@ -157,6 +163,7 @@
   RenderWidgetHostViewChildFrame* view_;
 
   gfx::Rect child_frame_rect_;
+  gfx::Rect viewport_intersection_rect_;
 
   bool is_scroll_bubbling_;
 };
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.cc b/content/browser/frame_host/render_widget_host_view_child_frame.cc
index b979394..cbd7258 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.cc
@@ -614,6 +614,11 @@
   return true;
 }
 
+void RenderWidgetHostViewChildFrame::WillSendScreenRects() {
+  if (frame_connector_)
+    UpdateViewportIntersection(frame_connector_->viewport_intersection());
+}
+
 #if defined(OS_MACOSX)
 ui::AcceleratedWidgetMac*
 RenderWidgetHostViewChildFrame::GetAcceleratedWidgetMac() const {
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame.h b/content/browser/frame_host/render_widget_host_view_child_frame.h
index 70a3b2fc..e7c5915 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame.h
+++ b/content/browser/frame_host/render_widget_host_view_child_frame.h
@@ -151,6 +151,8 @@
 
   bool IsRenderWidgetHostViewChildFrame() override;
 
+  void WillSendScreenRects() override;
+
 #if defined(OS_MACOSX)
   // RenderWidgetHostView implementation.
   ui::AcceleratedWidgetMac* GetAcceleratedWidgetMac() const override;
diff --git a/content/browser/frame_host/render_widget_host_view_child_frame_unittest.cc b/content/browser/frame_host/render_widget_host_view_child_frame_unittest.cc
index 3c04ce9..d589fd2 100644
--- a/content/browser/frame_host/render_widget_host_view_child_frame_unittest.cc
+++ b/content/browser/frame_host/render_widget_host_view_child_frame_unittest.cc
@@ -53,6 +53,8 @@
   void SelectAll() override {}
 };
 
+}  // namespace
+
 class MockCrossProcessFrameConnector : public CrossProcessFrameConnector {
  public:
   MockCrossProcessFrameConnector() : CrossProcessFrameConnector(nullptr) {}
@@ -63,6 +65,10 @@
     last_surface_info_ = surface_info;
   }
 
+  void SetViewportIntersection(const gfx::Rect& intersection) {
+    viewport_intersection_rect_ = intersection;
+  }
+
   RenderWidgetHostViewBase* GetParentRenderWidgetHostView() override {
     return nullptr;
   }
@@ -70,8 +76,6 @@
   cc::SurfaceInfo last_surface_info_;
 };
 
-}  // namespace
-
 class RenderWidgetHostViewChildFrameTest : public testing::Test {
  public:
   RenderWidgetHostViewChildFrameTest()
@@ -244,4 +248,25 @@
   EXPECT_TRUE(view_->has_frame());
 }
 
+// Tests that the viewport intersection rect is dispatched to the RenderWidget
+// whenever screen rects are updated.
+TEST_F(RenderWidgetHostViewChildFrameTest, ViewportIntersectionUpdated) {
+  gfx::Rect intersection_rect(5, 5, 100, 80);
+  test_frame_connector_->SetViewportIntersection(intersection_rect);
+
+  MockRenderProcessHost* process =
+      static_cast<MockRenderProcessHost*>(widget_host_->GetProcess());
+  process->Init();
+
+  widget_host_->Init();
+
+  const IPC::Message* intersection_update =
+      process->sink().GetUniqueMessageMatching(
+          ViewMsg_SetViewportIntersection::ID);
+  ASSERT_TRUE(intersection_update);
+  std::tuple<gfx::Rect> sent_rect;
+  ViewMsg_SetViewportIntersection::Read(intersection_update, &sent_rect);
+  EXPECT_EQ(intersection_rect, std::get<0>(sent_rect));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 7556afa..4e79b0bf 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -485,6 +485,7 @@
 
   last_view_screen_rect_ = view_->GetViewBounds();
   last_window_screen_rect_ = view_->GetBoundsInRootWindow();
+  view_->WillSendScreenRects();
   Send(new ViewMsg_UpdateScreenRects(
       GetRoutingID(), last_view_screen_rect_, last_window_screen_rect_));
   waiting_for_screen_rects_ack_ = true;
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 1f1a6b9..3f7581a 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -317,6 +317,10 @@
   // need to also be resolved.
   virtual bool IsRenderWidgetHostViewChildFrame();
 
+  // Notify the View that a screen rect update is being sent to the
+  // RenderWidget. Related platform-specific updates can be sent from here.
+  virtual void WillSendScreenRects() {}
+
   // Returns true if the current view is in virtual reality mode.
   virtual bool IsInVR() const;
 
diff --git a/content/child/web_url_loader_impl.cc b/content/child/web_url_loader_impl.cc
index 0bf318d..73e4dee6 100644
--- a/content/child/web_url_loader_impl.cc
+++ b/content/child/web_url_loader_impl.cc
@@ -1216,6 +1216,7 @@
   new_request.SetShouldResetAppCache(request.ShouldResetAppCache());
   new_request.SetFetchRequestMode(request.GetFetchRequestMode());
   new_request.SetFetchCredentialsMode(request.GetFetchCredentialsMode());
+  new_request.SetKeepalive(request.GetKeepalive());
 
   new_request.SetHTTPReferrer(WebString::FromUTF8(redirect_info.new_referrer),
                               NetReferrerPolicyToBlinkReferrerPolicy(
diff --git a/content/public/android/java/src/org/chromium/content/browser/SpeechRecognition.java b/content/public/android/java/src/org/chromium/content/browser/SpeechRecognition.java
index 7ddb2bc..90aeb60f 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SpeechRecognition.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SpeechRecognition.java
@@ -18,6 +18,7 @@
 import android.speech.SpeechRecognizer;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
 import org.chromium.base.PackageUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -33,6 +34,7 @@
  */
 @JNINamespace("content")
 public class SpeechRecognition {
+    private static final String TAG = "SpeechRecog";
 
     // Constants describing the speech recognition provider we depend on.
     private static final String PROVIDER_PACKAGE_NAME = "com.google.android.googlequicksearchbox";
@@ -239,7 +241,13 @@
             nativeOnRecognitionError(mNativeSpeechRecognizerImplAndroid, error);
         }
 
-        mRecognizer.destroy();
+        try {
+            mRecognizer.destroy();
+        } catch (IllegalArgumentException e) {
+            // Intentionally swallow exception. This incorrectly throws exception on some samsung
+            // devices, causing crashes.
+            Log.w(TAG, "Destroy threw exception " + mRecognizer, e);
+        }
         mRecognizer = null;
         nativeOnRecognitionEnd(mNativeSpeechRecognizerImplAndroid);
         mNativeSpeechRecognizerImplAndroid = 0;
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 04aafe1..6e68d6f 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -252,6 +252,7 @@
   deps = [
     "//base:i18n",
     "//components/cast_certificate",
+    "//components/cast_channel",
     "//components/crx_file",
     "//components/guest_view/browser",
     "//components/keyed_service/content",
@@ -396,6 +397,7 @@
     "api/bluetooth/bluetooth_event_router_unittest.cc",
     "api/cast_channel/cast_auth_util_unittest.cc",
     "api/cast_channel/cast_channel_api_unittest.cc",
+    "api/cast_channel/cast_channel_enum_util_unittest.cc",
     "api/cast_channel/cast_framer_unittest.cc",
     "api/cast_channel/cast_socket_service_unittest.cc",
     "api/cast_channel/cast_socket_unittest.cc",
diff --git a/extensions/browser/api/cast_channel/BUILD.gn b/extensions/browser/api/cast_channel/BUILD.gn
index 40ff859..6ec14193 100644
--- a/extensions/browser/api/cast_channel/BUILD.gn
+++ b/extensions/browser/api/cast_channel/BUILD.gn
@@ -8,6 +8,8 @@
     "cast_auth_util.h",
     "cast_channel_api.cc",
     "cast_channel_api.h",
+    "cast_channel_enum_util.cc",
+    "cast_channel_enum_util.h",
     "cast_framer.cc",
     "cast_framer.h",
     "cast_message_util.cc",
@@ -28,6 +30,7 @@
 
   deps = [
     "//base",
+    "//components/cast_channel",
     "//extensions/common/api",
     "//extensions/common/api/cast_channel:cast_channel_proto",
     "//net",
diff --git a/extensions/browser/api/cast_channel/DEPS b/extensions/browser/api/cast_channel/DEPS
index 23ace86e..44ac277a 100644
--- a/extensions/browser/api/cast_channel/DEPS
+++ b/extensions/browser/api/cast_channel/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+components/cast_certificate",
+  "+components/cast_channel",
   "+third_party/zlib",
 ]
diff --git a/extensions/browser/api/cast_channel/cast_channel_api.cc b/extensions/browser/api/cast_channel/cast_channel_api.cc
index cb5078d5a..b645aed 100644
--- a/extensions/browser/api/cast_channel/cast_channel_api.cc
+++ b/extensions/browser/api/cast_channel/cast_channel_api.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/cast_channel/cast_channel_enum_util.h"
 #include "extensions/browser/api/cast_channel/cast_message_util.h"
 #include "extensions/browser/api/cast_channel/cast_socket.h"
 #include "extensions/browser/api/cast_channel/cast_socket_service.h"
@@ -43,18 +44,19 @@
 namespace OnMessage = cast_channel::OnMessage;
 namespace Open = cast_channel::Open;
 namespace Send = cast_channel::Send;
+using ::cast_channel::ChannelError;
 using cast_channel::CastDeviceCapability;
 using cast_channel::CastMessage;
 using cast_channel::CastSocket;
-using cast_channel::ChannelAuthType;
-using cast_channel::ChannelError;
+using cast_channel::CastSocketImpl;
+using cast_channel::CastTransport;
 using cast_channel::ChannelInfo;
 using cast_channel::ConnectInfo;
 using cast_channel::ErrorInfo;
+using cast_channel::KeepAliveDelegate;
 using cast_channel::LastErrors;
 using cast_channel::Logger;
 using cast_channel::MessageInfo;
-using cast_channel::ReadyState;
 using content::BrowserThread;
 
 namespace {
@@ -75,15 +77,17 @@
   const net::IPEndPoint& ip_endpoint = socket.ip_endpoint();
   channel_info->connect_info.ip_address = ip_endpoint.ToStringWithoutPort();
   channel_info->connect_info.port = ip_endpoint.port();
-  channel_info->connect_info.auth = socket.channel_auth();
-  channel_info->ready_state = socket.ready_state();
-  channel_info->error_state = socket.error_state();
+  channel_info->connect_info.auth =
+      cast_channel::ToChannelAuthType(socket.channel_auth());
+  channel_info->ready_state = cast_channel::ToReadyState(socket.ready_state());
+  channel_info->error_state =
+      cast_channel::ToChannelError(socket.error_state());
   channel_info->keep_alive = socket.keep_alive();
   channel_info->audio_only = socket.audio_only();
 }
 
 // Fills |error_info| from |error_state| and |last_errors|.
-void FillErrorInfo(ChannelError error_state,
+void FillErrorInfo(cast_channel::ChannelError error_state,
                    const LastErrors& last_errors,
                    ErrorInfo* error_info) {
   error_info->error_state = error_state;
@@ -209,15 +213,17 @@
     const CastSocket& socket) {
   ChannelInfo channel_info;
   FillChannelInfo(socket, &channel_info);
-  ChannelError error = socket.error_state();
+  cast_channel::ChannelError error =
+      cast_channel::ToChannelError(socket.error_state());
   if (error != cast_channel::CHANNEL_ERROR_NONE) {
     SetError("Channel socket error = " + base::IntToString(error));
   }
   SetResultFromChannelInfo(channel_info);
 }
 
-void CastChannelAsyncApiFunction::SetResultFromError(int channel_id,
-                                                     ChannelError error) {
+void CastChannelAsyncApiFunction::SetResultFromError(
+    int channel_id,
+    cast_channel::ChannelError error) {
   ChannelInfo channel_info;
   channel_info.channel_id = channel_id;
   channel_info.ready_state = cast_channel::READY_STATE_CLOSED;
@@ -294,7 +300,7 @@
     return false;
   }
 
-  channel_auth_ = connect_info.auth;
+  channel_auth_ = cast_channel::ToChannelAuthTypeInternal(connect_info.auth);
   ip_endpoint_.reset(ParseConnectInfo(connect_info));
   return true;
 }
@@ -308,7 +314,7 @@
   if (test_socket.get()) {
     socket = test_socket.release();
   } else {
-    socket = new cast_channel::CastSocketImpl(
+    socket = new CastSocketImpl(
         extension_->id(), *ip_endpoint_, channel_auth_,
         ExtensionsBrowserClient::Get()->GetNetLog(),
         base::TimeDelta::FromMilliseconds(connect_info.timeout.get()
@@ -321,17 +327,16 @@
   new_channel_id_ = AddSocket(base::WrapUnique(socket));
 
   // Construct read delegates.
-  std::unique_ptr<api::cast_channel::CastTransport::Delegate> delegate(
+  std::unique_ptr<CastTransport::Delegate> delegate(
       base::MakeUnique<CastMessageHandler>(
           base::Bind(&CastChannelAPI::SendEvent, api_->AsWeakPtr(),
                      extension_->id()),
           socket, api_->GetLogger()));
   if (socket->keep_alive()) {
     // Wrap read delegate in a KeepAliveDelegate for timeout handling.
-    api::cast_channel::KeepAliveDelegate* keep_alive =
-        new api::cast_channel::KeepAliveDelegate(
-            socket, api_->GetLogger(), std::move(delegate), ping_interval_,
-            liveness_timeout_);
+    KeepAliveDelegate* keep_alive =
+        new KeepAliveDelegate(socket, api_->GetLogger(), std::move(delegate),
+                              ping_interval_, liveness_timeout_);
     std::unique_ptr<base::Timer> injected_timer =
         api_->GetInjectedTimeoutTimerForTest();
     if (injected_timer) {
@@ -345,19 +350,19 @@
                   base::Bind(&CastChannelOpenFunction::OnOpen, this));
 }
 
-void CastChannelOpenFunction::OnOpen(cast_channel::ChannelError result) {
+void CastChannelOpenFunction::OnOpen(ChannelError result) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   VLOG(1) << "Connect finished, OnOpen invoked.";
   // TODO: If we failed to open the CastSocket, we may want to clean up here,
   // rather than relying on the extension to call close(). This can be done by
   // calling RemoveSocket() and api_->GetLogger()->ClearLastErrors(channel_id).
-  if (result != cast_channel::CHANNEL_ERROR_UNKNOWN) {
+  if (result != ChannelError::UNKNOWN) {
     CastSocket* socket = GetSocket(new_channel_id_);
     CHECK(socket);
     SetResultFromSocket(*socket);
   } else {
     // The socket is being destroyed.
-    SetResultFromError(new_channel_id_, result);
+    SetResultFromError(new_channel_id_, cast_channel::CHANNEL_ERROR_UNKNOWN);
   }
 
   AsyncWorkCompleted();
@@ -470,7 +475,7 @@
 
 CastChannelOpenFunction::CastMessageHandler::CastMessageHandler(
     const EventDispatchCallback& ui_dispatch_cb,
-    cast_channel::CastSocket* socket,
+    CastSocket* socket,
     scoped_refptr<Logger> logger)
     : ui_dispatch_cb_(ui_dispatch_cb), socket_(socket), logger_(logger) {
   DCHECK(socket_);
@@ -481,14 +486,14 @@
 }
 
 void CastChannelOpenFunction::CastMessageHandler::OnError(
-    cast_channel::ChannelError error_state) {
+    ChannelError error_state) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   ChannelInfo channel_info;
   FillChannelInfo(*socket_, &channel_info);
-  channel_info.error_state = error_state;
+  channel_info.error_state = cast_channel::ToChannelError(error_state);
   ErrorInfo error_info;
-  FillErrorInfo(error_state, logger_->GetLastErrors(socket_->id()),
+  FillErrorInfo(channel_info.error_state, logger_->GetLastErrors(socket_->id()),
                 &error_info);
 
   std::unique_ptr<base::ListValue> results =
@@ -505,7 +510,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   MessageInfo message_info;
-  cast_channel::CastMessageToMessageInfo(message, &message_info);
+  CastMessageToMessageInfo(message, &message_info);
   ChannelInfo channel_info;
   FillChannelInfo(*socket_, &channel_info);
   VLOG(1) << "Received message " << ParamToString(message_info)
diff --git a/extensions/browser/api/cast_channel/cast_channel_api.h b/extensions/browser/api/cast_channel/cast_channel_api.h
index d396a5d..b97faea1 100644
--- a/extensions/browser/api/cast_channel/cast_channel_api.h
+++ b/extensions/browser/api/cast_channel/cast_channel_api.h
@@ -12,6 +12,8 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
+#include "components/cast_channel/cast_channel_enum.h"
+#include "extensions/browser/api/api_resource_manager.h"
 #include "extensions/browser/api/async_api_function.h"
 #include "extensions/browser/api/cast_channel/cast_socket.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
@@ -171,7 +173,7 @@
     ~CastMessageHandler() override;
 
     // CastTransport::Delegate implementation.
-    void OnError(cast_channel::ChannelError error_state) override;
+    void OnError(::cast_channel::ChannelError error_state) override;
     void OnMessage(const cast_channel::CastMessage& message) override;
     void Start() override;
 
@@ -192,14 +194,14 @@
   static net::IPEndPoint* ParseConnectInfo(
       const cast_channel::ConnectInfo& connect_info);
 
-  void OnOpen(cast_channel::ChannelError result);
+  void OnOpen(::cast_channel::ChannelError result);
 
   std::unique_ptr<cast_channel::Open::Params> params_;
   // The id of the newly opened socket.
   int new_channel_id_;
   CastChannelAPI* api_;
   std::unique_ptr<net::IPEndPoint> ip_endpoint_;
-  cast_channel::ChannelAuthType channel_auth_;
+  ::cast_channel::ChannelAuthType channel_auth_;
   base::TimeDelta liveness_timeout_;
   base::TimeDelta ping_interval_;
 
diff --git a/extensions/browser/api/cast_channel/cast_channel_apitest.cc b/extensions/browser/api/cast_channel/cast_channel_apitest.cc
index 0c5a886..13ad9133 100644
--- a/extensions/browser/api/cast_channel/cast_channel_apitest.cc
+++ b/extensions/browser/api/cast_channel/cast_channel_apitest.cc
@@ -33,20 +33,20 @@
 // TODO(mfoltz): Mock out the ApiResourceManager to resolve threading issues
 // (crbug.com/398242) and simulate unloading of the extension.
 
-namespace cast_channel = extensions::api::cast_channel;
-using cast_channel::CastMessage;
-using cast_channel::CastSocket;
-using cast_channel::CastTransport;
-using cast_channel::ChannelAuthType;
-using cast_channel::ChannelError;
-using cast_channel::CreateIPEndPointForTest;
-using cast_channel::ErrorInfo;
-using cast_channel::LastErrors;
-using cast_channel::Logger;
-using cast_channel::MessageInfo;
-using cast_channel::MockCastSocket;
-using cast_channel::MockCastTransport;
-using cast_channel::ReadyState;
+using ::cast_channel::ChannelAuthType;
+using ::cast_channel::ChannelError;
+using ::cast_channel::ReadyState;
+
+using extensions::api::cast_channel::CastMessage;
+using extensions::api::cast_channel::CastSocket;
+using extensions::api::cast_channel::CastTransport;
+using extensions::api::cast_channel::CreateIPEndPointForTest;
+using extensions::api::cast_channel::ErrorInfo;
+using extensions::api::cast_channel::LastErrors;
+using extensions::api::cast_channel::Logger;
+using extensions::api::cast_channel::MessageInfo;
+using extensions::api::cast_channel::MockCastSocket;
+using extensions::api::cast_channel::MockCastTransport;
 using extensions::Extension;
 
 namespace utils = extension_function_test_utils;
@@ -89,7 +89,7 @@
     ExtensionApiTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(
         extensions::switches::kWhitelistedExtensionID,
-        cast_channel::kTestExtensionId);
+        extensions::api::cast_channel::kTestExtensionId);
   }
 
   void SetUpMockCastSocket() {
@@ -108,49 +108,47 @@
     ON_CALL(*mock_cast_socket_, ip_endpoint())
         .WillByDefault(ReturnRef(ip_endpoint_));
     ON_CALL(*mock_cast_socket_, channel_auth())
-        .WillByDefault(Return(cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED));
+        .WillByDefault(Return(ChannelAuthType::SSL_VERIFIED));
     ON_CALL(*mock_cast_socket_, keep_alive()).WillByDefault(Return(false));
   }
 
   void SetUpOpenSendClose() {
     SetUpMockCastSocket();
     EXPECT_CALL(*mock_cast_socket_, error_state())
-        .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+        .WillRepeatedly(Return(ChannelError::NONE));
     {
       InSequence sequence;
       EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _))
-          .WillOnce(
-              InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE));
+          .WillOnce(InvokeCompletionCallback<1>(ChannelError::NONE));
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(cast_channel::READY_STATE_OPEN));
+          .WillOnce(Return(ReadyState::OPEN));
       EXPECT_CALL(*mock_cast_socket_->mock_transport(),
                   SendMessage(A<const CastMessage&>(), _))
           .WillOnce(InvokeCompletionCallback<1>(net::OK));
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(cast_channel::READY_STATE_OPEN));
+          .WillOnce(Return(ReadyState::OPEN));
       EXPECT_CALL(*mock_cast_socket_, Close(_))
           .WillOnce(InvokeCompletionCallback<0>(net::OK));
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+          .WillOnce(Return(ReadyState::CLOSED));
     }
   }
 
   void SetUpOpenPingTimeout() {
     SetUpMockCastSocket();
     EXPECT_CALL(*mock_cast_socket_, error_state())
-        .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+        .WillRepeatedly(Return(ChannelError::NONE));
     EXPECT_CALL(*mock_cast_socket_, keep_alive()).WillRepeatedly(Return(true));
     {
       InSequence sequence;
       EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(_, _))
-          .WillOnce(DoAll(
-              SaveArg<0>(&message_delegate_),
-              InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE)));
+          .WillOnce(DoAll(SaveArg<0>(&message_delegate_),
+                          InvokeCompletionCallback<1>(ChannelError::NONE)));
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(cast_channel::READY_STATE_OPEN))
+          .WillOnce(Return(ReadyState::OPEN))
           .RetiresOnSaturation();
       EXPECT_CALL(*mock_cast_socket_, ready_state())
-          .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+          .WillOnce(Return(ReadyState::CLOSED));
     }
   }
 
@@ -160,10 +158,10 @@
 
   // Logs some bogus error details and calls the OnError handler.
   void DoCallOnError(extensions::CastChannelAPI* api) {
-    api->GetLogger()->LogSocketEventWithRv(mock_cast_socket_->id(),
-                                           cast_channel::proto::SOCKET_WRITE,
-                                           net::ERR_FAILED);
-    message_delegate_->OnError(cast_channel::CHANNEL_ERROR_CONNECT_ERROR);
+    api->GetLogger()->LogSocketEventWithRv(
+        mock_cast_socket_->id(),
+        extensions::api::cast_channel::proto::SOCKET_WRITE, net::ERR_FAILED);
+    message_delegate_->OnError(ChannelError::CONNECT_ERROR);
   }
 
  protected:
@@ -187,7 +185,7 @@
     CHECK(message_delegate_);
     content::BrowserThread::PostTask(
         content::BrowserThread::IO, FROM_HERE,
-        base::Bind(&cast_channel::CastTransport::Delegate::Start,
+        base::Bind(&CastTransport::Delegate::Start,
                    base::Unretained(message_delegate_)));
   }
 
@@ -303,21 +301,20 @@
 IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenReceiveClose) {
   SetUpMockCastSocket();
   EXPECT_CALL(*mock_cast_socket_, error_state())
-      .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
+      .WillRepeatedly(Return(ChannelError::NONE));
 
   {
     InSequence sequence;
     EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _))
-        .WillOnce(DoAll(
-            SaveArg<0>(&message_delegate_),
-            InvokeCompletionCallback<1>(cast_channel::CHANNEL_ERROR_NONE)));
+        .WillOnce(DoAll(SaveArg<0>(&message_delegate_),
+                        InvokeCompletionCallback<1>(ChannelError::NONE)));
     EXPECT_CALL(*mock_cast_socket_, ready_state())
         .Times(3)
-        .WillRepeatedly(Return(cast_channel::READY_STATE_OPEN));
+        .WillRepeatedly(Return(ReadyState::OPEN));
     EXPECT_CALL(*mock_cast_socket_, Close(_))
         .WillOnce(InvokeCompletionCallback<0>(net::OK));
     EXPECT_CALL(*mock_cast_socket_, ready_state())
-        .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
+        .WillOnce(Return(ReadyState::CLOSED));
   }
 
   EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
@@ -341,14 +338,13 @@
   SetUpMockCastSocket();
 
   EXPECT_CALL(*mock_cast_socket_, ConnectRawPtr(NotNull(), _))
-      .WillOnce(DoAll(SaveArg<0>(&message_delegate_),
-                      InvokeDelegateOnError(this, GetApi()),
-                      InvokeCompletionCallback<1>(
-                          cast_channel::CHANNEL_ERROR_CONNECT_ERROR)));
+      .WillOnce(DoAll(
+          SaveArg<0>(&message_delegate_), InvokeDelegateOnError(this, GetApi()),
+          InvokeCompletionCallback<1>(ChannelError::CONNECT_ERROR)));
   EXPECT_CALL(*mock_cast_socket_, error_state())
-      .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_CONNECT_ERROR));
+      .WillRepeatedly(Return(ChannelError::CONNECT_ERROR));
   EXPECT_CALL(*mock_cast_socket_, ready_state())
-      .WillRepeatedly(Return(cast_channel::READY_STATE_CLOSED));
+      .WillRepeatedly(Return(ReadyState::CLOSED));
   EXPECT_CALL(*mock_cast_socket_, Close(_))
       .WillOnce(InvokeCompletionCallback<0>(net::OK));
 
diff --git a/extensions/browser/api/cast_channel/cast_channel_enum_util.cc b/extensions/browser/api/cast_channel/cast_channel_enum_util.cc
new file mode 100644
index 0000000..e3e7022
--- /dev/null
+++ b/extensions/browser/api/cast_channel/cast_channel_enum_util.cc
@@ -0,0 +1,87 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_channel_enum_util.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+api::cast_channel::ReadyState ToReadyState(
+    ::cast_channel::ReadyState ready_state) {
+  switch (ready_state) {
+    case ::cast_channel::ReadyState::NONE:
+      return READY_STATE_NONE;
+    case ::cast_channel::ReadyState::CONNECTING:
+      return READY_STATE_CONNECTING;
+    case ::cast_channel::ReadyState::OPEN:
+      return READY_STATE_OPEN;
+    case ::cast_channel::ReadyState::CLOSING:
+      return READY_STATE_CLOSING;
+    case ::cast_channel::ReadyState::CLOSED:
+      return READY_STATE_CLOSED;
+  }
+  NOTREACHED() << "Unknown ready_state " << ReadyStateToString(ready_state);
+  return READY_STATE_NONE;
+}
+
+api::cast_channel::ChannelError ToChannelError(
+    ::cast_channel::ChannelError channel_error) {
+  switch (channel_error) {
+    case ::cast_channel::ChannelError::NONE:
+      return CHANNEL_ERROR_NONE;
+    case ::cast_channel::ChannelError::CHANNEL_NOT_OPEN:
+      return CHANNEL_ERROR_CHANNEL_NOT_OPEN;
+    case ::cast_channel::ChannelError::AUTHENTICATION_ERROR:
+      return CHANNEL_ERROR_AUTHENTICATION_ERROR;
+    case ::cast_channel::ChannelError::CONNECT_ERROR:
+      return CHANNEL_ERROR_CONNECT_ERROR;
+    case ::cast_channel::ChannelError::CAST_SOCKET_ERROR:
+      return CHANNEL_ERROR_SOCKET_ERROR;
+    case ::cast_channel::ChannelError::TRANSPORT_ERROR:
+      return CHANNEL_ERROR_TRANSPORT_ERROR;
+    case ::cast_channel::ChannelError::INVALID_MESSAGE:
+      return CHANNEL_ERROR_INVALID_MESSAGE;
+    case ::cast_channel::ChannelError::INVALID_CHANNEL_ID:
+      return CHANNEL_ERROR_INVALID_CHANNEL_ID;
+    case ::cast_channel::ChannelError::CONNECT_TIMEOUT:
+      return CHANNEL_ERROR_CONNECT_TIMEOUT;
+    case ::cast_channel::ChannelError::PING_TIMEOUT:
+      return CHANNEL_ERROR_PING_TIMEOUT;
+    case ::cast_channel::ChannelError::UNKNOWN:
+      return CHANNEL_ERROR_UNKNOWN;
+  }
+  NOTREACHED() << "Unknown channel_error "
+               << ChannelErrorToString(channel_error);
+  return CHANNEL_ERROR_NONE;
+}
+
+api::cast_channel::ChannelAuthType ToChannelAuthType(
+    ::cast_channel::ChannelAuthType channel_auth) {
+  switch (channel_auth) {
+    case ::cast_channel::ChannelAuthType::NONE:
+      return CHANNEL_AUTH_TYPE_NONE;
+    case ::cast_channel::ChannelAuthType::SSL_VERIFIED:
+      return CHANNEL_AUTH_TYPE_SSL_VERIFIED;
+  }
+  NOTREACHED() << "Unknown channel_auth "
+               << ChannelAuthTypeToString(channel_auth);
+  return CHANNEL_AUTH_TYPE_NONE;
+}
+
+::cast_channel::ChannelAuthType ToChannelAuthTypeInternal(
+    api::cast_channel::ChannelAuthType channel_auth) {
+  switch (channel_auth) {
+    case CHANNEL_AUTH_TYPE_NONE:
+      return ::cast_channel::ChannelAuthType::NONE;
+    case CHANNEL_AUTH_TYPE_SSL_VERIFIED:
+      return ::cast_channel::ChannelAuthType::SSL_VERIFIED;
+  }
+  NOTREACHED() << "Unknown channel_auth " << channel_auth;
+  return ::cast_channel::ChannelAuthType::NONE;
+}
+
+}  // namespace cast_channel
+}  // namespace api
+}  // namespace extensions
diff --git a/extensions/browser/api/cast_channel/cast_channel_enum_util.h b/extensions/browser/api/cast_channel/cast_channel_enum_util.h
new file mode 100644
index 0000000..0fbe066
--- /dev/null
+++ b/extensions/browser/api/cast_channel/cast_channel_enum_util.h
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_TYPE_UTIL_H_
+#define EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_TYPE_UTIL_H_
+
+#include "components/cast_channel/cast_channel_enum.h"
+#include "extensions/common/api/cast_channel.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+
+api::cast_channel::ReadyState ToReadyState(
+    ::cast_channel::ReadyState ready_state);
+api::cast_channel::ChannelError ToChannelError(
+    ::cast_channel::ChannelError channel_error);
+api::cast_channel::ChannelAuthType ToChannelAuthType(
+    ::cast_channel::ChannelAuthType channel_auth);
+::cast_channel::ChannelAuthType ToChannelAuthTypeInternal(
+    api::cast_channel::ChannelAuthType channel_auth);
+
+}  // namespace cast_channel
+}  // namespace api
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_CAST_CHANNEL_CAST_CHANNEL_TYPE_UTIL_H_
diff --git a/extensions/browser/api/cast_channel/cast_channel_enum_util_unittest.cc b/extensions/browser/api/cast_channel/cast_channel_enum_util_unittest.cc
new file mode 100644
index 0000000..a25c6898
--- /dev/null
+++ b/extensions/browser/api/cast_channel/cast_channel_enum_util_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/cast_channel/cast_channel_enum_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace api {
+namespace cast_channel {
+namespace {
+
+TEST(CastChannelTypeUtilTest, TestToReadyState) {
+  EXPECT_EQ(READY_STATE_NONE, ToReadyState(::cast_channel::ReadyState::NONE));
+  EXPECT_EQ(READY_STATE_CONNECTING,
+            ToReadyState(::cast_channel::ReadyState::CONNECTING));
+  EXPECT_EQ(READY_STATE_OPEN, ToReadyState(::cast_channel::ReadyState::OPEN));
+  EXPECT_EQ(READY_STATE_CLOSING,
+            ToReadyState(::cast_channel::ReadyState::CLOSING));
+  EXPECT_EQ(READY_STATE_CLOSED,
+            ToReadyState(::cast_channel::ReadyState::CLOSED));
+}
+
+TEST(CastChannelTypeUtilTest, TestToChannelError) {
+  EXPECT_EQ(CHANNEL_ERROR_NONE,
+            ToChannelError(::cast_channel::ChannelError::NONE));
+  EXPECT_EQ(CHANNEL_ERROR_CHANNEL_NOT_OPEN,
+            ToChannelError(::cast_channel::ChannelError::CHANNEL_NOT_OPEN));
+  EXPECT_EQ(CHANNEL_ERROR_AUTHENTICATION_ERROR,
+            ToChannelError(::cast_channel::ChannelError::AUTHENTICATION_ERROR));
+  EXPECT_EQ(CHANNEL_ERROR_CONNECT_ERROR,
+            ToChannelError(::cast_channel::ChannelError::CONNECT_ERROR));
+  EXPECT_EQ(CHANNEL_ERROR_SOCKET_ERROR,
+            ToChannelError(::cast_channel::ChannelError::CAST_SOCKET_ERROR));
+  EXPECT_EQ(CHANNEL_ERROR_TRANSPORT_ERROR,
+            ToChannelError(::cast_channel::ChannelError::TRANSPORT_ERROR));
+  EXPECT_EQ(CHANNEL_ERROR_INVALID_MESSAGE,
+            ToChannelError(::cast_channel::ChannelError::INVALID_MESSAGE));
+  EXPECT_EQ(CHANNEL_ERROR_INVALID_CHANNEL_ID,
+            ToChannelError(::cast_channel::ChannelError::INVALID_CHANNEL_ID));
+  EXPECT_EQ(CHANNEL_ERROR_CONNECT_TIMEOUT,
+            ToChannelError(::cast_channel::ChannelError::CONNECT_TIMEOUT));
+  EXPECT_EQ(CHANNEL_ERROR_PING_TIMEOUT,
+            ToChannelError(::cast_channel::ChannelError::PING_TIMEOUT));
+  EXPECT_EQ(CHANNEL_ERROR_UNKNOWN,
+            ToChannelError(::cast_channel::ChannelError::UNKNOWN));
+}
+
+TEST(CastChannelTypeUtilTest, TestToChannelAuthType) {
+  EXPECT_EQ(CHANNEL_AUTH_TYPE_NONE,
+            ToChannelAuthType(::cast_channel::ChannelAuthType::NONE));
+  EXPECT_EQ(CHANNEL_AUTH_TYPE_SSL_VERIFIED,
+            ToChannelAuthType(::cast_channel::ChannelAuthType::SSL_VERIFIED));
+}
+
+TEST(CastChannelTypeUtilTest, TestToChannelAuthTypeInternal) {
+  EXPECT_EQ(::cast_channel::ChannelAuthType::NONE,
+            ToChannelAuthTypeInternal(CHANNEL_AUTH_TYPE_NONE));
+  EXPECT_EQ(::cast_channel::ChannelAuthType::SSL_VERIFIED,
+            ToChannelAuthTypeInternal(CHANNEL_AUTH_TYPE_SSL_VERIFIED));
+}
+
+}  // namespace
+}  // namespace cast_channel
+}  // namespace api
+}  // namespace extensions
diff --git a/extensions/browser/api/cast_channel/cast_framer.cc b/extensions/browser/api/cast_channel/cast_framer.cc
index e453415..0bd744fa 100644
--- a/extensions/browser/api/cast_channel/cast_framer.cc
+++ b/extensions/browser/api/cast_channel/cast_framer.cc
@@ -119,7 +119,7 @@
   DCHECK(error);
   DCHECK(message_length);
   if (error_) {
-    *error = CHANNEL_ERROR_INVALID_MESSAGE;
+    *error = ChannelError::INVALID_MESSAGE;
     return nullptr;
   }
 
@@ -127,7 +127,7 @@
             input_buffer_->offset());
   CHECK_LE(num_bytes, BytesRequested());
   message_bytes_received_ += num_bytes;
-  *error = CHANNEL_ERROR_NONE;
+  *error = ChannelError::NONE;
   *message_length = 0;
   switch (current_element_) {
     case HEADER:
@@ -136,7 +136,7 @@
         MessageHeader::Deserialize(input_buffer_->StartOfBuffer(), &header);
         if (header.message_size > MessageHeader::max_message_size()) {
           VLOG(1) << "Error parsing header (message size too large).";
-          *error = CHANNEL_ERROR_INVALID_MESSAGE;
+          *error = ChannelError::INVALID_MESSAGE;
           error_ = true;
           return nullptr;
         }
@@ -151,7 +151,7 @@
                 input_buffer_->StartOfBuffer() + MessageHeader::header_size(),
                 body_size_)) {
           VLOG(1) << "Error parsing packet body.";
-          *error = CHANNEL_ERROR_INVALID_MESSAGE;
+          *error = ChannelError::INVALID_MESSAGE;
           error_ = true;
           return nullptr;
         }
diff --git a/extensions/browser/api/cast_channel/cast_framer.h b/extensions/browser/api/cast_channel/cast_framer.h
index ec8fcfe6..8a5b6b5 100644
--- a/extensions/browser/api/cast_channel/cast_framer.h
+++ b/extensions/browser/api/cast_channel/cast_framer.h
@@ -12,7 +12,7 @@
 #include <string>
 
 #include "base/macros.h"
-#include "extensions/common/api/cast_channel.h"
+#include "components/cast_channel/cast_channel_enum.h"
 #include "net/base/io_buffer.h"
 
 namespace extensions {
@@ -23,6 +23,8 @@
 // Class for constructing and parsing CastMessage packet data.
 class MessageFramer {
  public:
+  using ChannelError = ::cast_channel::ChannelError;
+
   // |input_buffer|: The input buffer used by all socket read operations that
   //                 feed data into the framer.
   explicit MessageFramer(scoped_refptr<net::GrowableIOBuffer> input_buffer);
diff --git a/extensions/browser/api/cast_channel/cast_framer_unittest.cc b/extensions/browser/api/cast_channel/cast_framer_unittest.cc
index 4000e1a..87c76e4 100644
--- a/extensions/browser/api/cast_channel/cast_framer_unittest.cc
+++ b/extensions/browser/api/cast_channel/cast_framer_unittest.cc
@@ -15,6 +15,9 @@
 namespace extensions {
 namespace api {
 namespace cast_channel {
+
+using ::cast_channel::ChannelError;
+
 class CastFramerTest : public testing::Test {
  public:
   CastFramerTest() {}
@@ -54,13 +57,13 @@
   // Receive 1 byte of the header, framer demands 3 more bytes.
   EXPECT_EQ(4u, framer_->BytesRequested());
   EXPECT_EQ(nullptr, framer_->Ingest(1, &message_length, &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+  EXPECT_EQ(ChannelError::NONE, error);
   EXPECT_EQ(3u, framer_->BytesRequested());
 
   // Ingest remaining 3, expect that the framer has moved on to requesting the
   // body contents.
   EXPECT_EQ(nullptr, framer_->Ingest(3, &message_length, &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+  EXPECT_EQ(ChannelError::NONE, error);
   EXPECT_EQ(
       cast_message_str_.size() - MessageFramer::MessageHeader::header_size(),
       framer_->BytesRequested());
@@ -69,7 +72,7 @@
   std::unique_ptr<CastMessage> message;
   message = framer_->Ingest(framer_->BytesRequested(), &message_length, &error);
   EXPECT_NE(static_cast<CastMessage*>(nullptr), message.get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+  EXPECT_EQ(ChannelError::NONE, error);
   EXPECT_EQ(message->SerializeAsString(), cast_message_.SerializeAsString());
   EXPECT_EQ(4u, framer_->BytesRequested());
   EXPECT_EQ(message->SerializeAsString().size(), message_length);
@@ -97,14 +100,14 @@
   ChannelError error;
   EXPECT_EQ(4u, framer_->BytesRequested());
   EXPECT_EQ(nullptr, framer_->Ingest(4, &bytes_ingested, &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+  EXPECT_EQ(ChannelError::INVALID_MESSAGE, error);
   EXPECT_EQ(0u, framer_->BytesRequested());
 
   // Test that the parser enters a terminal error state.
   WriteToBuffer(cast_message_str_);
   EXPECT_EQ(0u, framer_->BytesRequested());
   EXPECT_EQ(nullptr, framer_->Ingest(4, &bytes_ingested, &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+  EXPECT_EQ(ChannelError::INVALID_MESSAGE, error);
   EXPECT_EQ(0u, framer_->BytesRequested());
 }
 
@@ -126,14 +129,14 @@
   ChannelError error;
   EXPECT_EQ(4u, framer_->BytesRequested());
   EXPECT_EQ(nullptr, framer_->Ingest(4, &message_length, &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, error);
+  EXPECT_EQ(ChannelError::NONE, error);
   EXPECT_EQ(cast_message_str_.size() - 4, framer_->BytesRequested());
 
   // Send body, expect an error.
   std::unique_ptr<CastMessage> message;
   EXPECT_EQ(nullptr, framer_->Ingest(framer_->BytesRequested(), &message_length,
                                      &error).get());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_INVALID_MESSAGE, error);
+  EXPECT_EQ(ChannelError::INVALID_MESSAGE, error);
 }
 }  // namespace cast_channel
 }  // namespace api
diff --git a/extensions/browser/api/cast_channel/cast_socket.cc b/extensions/browser/api/cast_channel/cast_socket.cc
index be7292e..cb258625 100644
--- a/extensions/browser/api/cast_channel/cast_socket.cc
+++ b/extensions/browser/api/cast_channel/cast_socket.cc
@@ -50,8 +50,10 @@
 // Helper for logging data with remote host IP and authentication state.
 // Assumes |ip_endpoint_| of type net::IPEndPoint and |channel_auth_| of enum
 // type ChannelAuthType are available in the current scope.
-#define CONNECTION_INFO() \
-  "[" << ip_endpoint_.ToString() << ", auth=" << channel_auth_ << "] "
+#define CONNECTION_INFO()                                                    \
+  "[" << ip_endpoint_.ToString()                                             \
+      << ", auth=" << ::cast_channel::ChannelAuthTypeToString(channel_auth_) \
+      << "] "
 #define VLOG_WITH_CONNECTION(level) VLOG(level) << CONNECTION_INFO()
 #define LOG_WITH_CONNECTION(level) LOG(level) << CONNECTION_INFO()
 
@@ -85,6 +87,10 @@
 
 }  // namespace
 
+using ChannelError = ::cast_channel::ChannelError;
+using ChannelAuthType = ::cast_channel::ChannelAuthType;
+using ReadyState = ::cast_channel::ReadyState;
+
 CastSocketImpl::CastSocketImpl(const std::string& owner_extension_id,
                                const net::IPEndPoint& ip_endpoint,
                                ChannelAuthType channel_auth,
@@ -125,8 +131,8 @@
       device_capabilities_(device_capabilities),
       audio_only_(false),
       connect_state_(proto::CONN_STATE_START_CONNECT),
-      error_state_(CHANNEL_ERROR_NONE),
-      ready_state_(READY_STATE_NONE),
+      error_state_(ChannelError::NONE),
+      ready_state_(ReadyState::NONE),
       auth_delegate_(nullptr) {
   DCHECK(net_log_);
   net_log_source_.type = net::NetLogSourceType::SOCKET;
@@ -139,7 +145,7 @@
   CloseInternal();
 
   if (!connect_callback_.is_null())
-    base::ResetAndReturn(&connect_callback_).Run(CHANNEL_ERROR_UNKNOWN);
+    base::ResetAndReturn(&connect_callback_).Run(ChannelError::UNKNOWN);
 }
 
 ReadyState CastSocketImpl::ready_state() const {
@@ -249,18 +255,19 @@
 void CastSocketImpl::Connect(std::unique_ptr<CastTransport::Delegate> delegate,
                              base::Callback<void(ChannelError)> callback) {
   DCHECK(CalledOnValidThread());
-  VLOG_WITH_CONNECTION(1) << "Connect readyState = " << ready_state_;
+  VLOG_WITH_CONNECTION(1) << "Connect readyState = "
+                          << ::cast_channel::ReadyStateToString(ready_state_);
   DCHECK_EQ(proto::CONN_STATE_START_CONNECT, connect_state_);
 
   delegate_ = std::move(delegate);
 
-  if (ready_state_ != READY_STATE_NONE) {
-    callback.Run(CHANNEL_ERROR_CONNECT_ERROR);
+  if (ready_state_ != ReadyState::NONE) {
+    callback.Run(ChannelError::CONNECT_ERROR);
     return;
   }
 
   connect_callback_ = callback;
-  SetReadyState(READY_STATE_CONNECTING);
+  SetReadyState(ReadyState::CONNECTING);
   SetConnectState(proto::CONN_STATE_TCP_CONNECT);
 
   // Set up connection timeout.
@@ -285,7 +292,7 @@
   // Stop all pending connection setup tasks and report back to the client.
   is_canceled_ = true;
   VLOG_WITH_CONNECTION(1) << "Timeout while establishing a connection.";
-  SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+  SetErrorState(ChannelError::CONNECT_TIMEOUT);
   DoConnectCallback();
 }
 
@@ -349,7 +356,7 @@
       default:
         NOTREACHED() << "Unknown state in connect flow: " << state;
         SetConnectState(proto::CONN_STATE_FINISHED);
-        SetErrorState(CHANNEL_ERROR_UNKNOWN);
+        SetErrorState(ChannelError::UNKNOWN);
         DoConnectCallback();
         return;
     }
@@ -386,10 +393,10 @@
     SetConnectState(proto::CONN_STATE_SSL_CONNECT);
   } else if (connect_result == net::ERR_CONNECTION_TIMED_OUT) {
     SetConnectState(proto::CONN_STATE_FINISHED);
-    SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+    SetErrorState(ChannelError::CONNECT_TIMEOUT);
   } else {
     SetConnectState(proto::CONN_STATE_FINISHED);
-    SetErrorState(CHANNEL_ERROR_CONNECT_ERROR);
+    SetErrorState(ChannelError::CONNECT_ERROR);
   }
   return connect_result;
 }
@@ -416,7 +423,7 @@
     if (!peer_cert_) {
       LOG_WITH_CONNECTION(WARNING) << "Could not extract peer cert.";
       SetConnectState(proto::CONN_STATE_FINISHED);
-      SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+      SetErrorState(ChannelError::AUTHENTICATION_ERROR);
       return net::ERR_CERT_INVALID;
     }
 
@@ -433,10 +440,10 @@
     SetConnectState(proto::CONN_STATE_AUTH_CHALLENGE_SEND);
   } else if (result == net::ERR_CONNECTION_TIMED_OUT) {
     SetConnectState(proto::CONN_STATE_FINISHED);
-    SetErrorState(CHANNEL_ERROR_CONNECT_TIMEOUT);
+    SetErrorState(ChannelError::CONNECT_TIMEOUT);
   } else {
     SetConnectState(proto::CONN_STATE_FINISHED);
-    SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+    SetErrorState(ChannelError::AUTHENTICATION_ERROR);
   }
   return result;
 }
@@ -461,7 +468,7 @@
   VLOG_WITH_CONNECTION(1) << "DoAuthChallengeSendComplete: " << result;
   if (result < 0) {
     SetConnectState(proto::CONN_STATE_ERROR);
-    SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+    SetErrorState(ChannelError::CAST_SOCKET_ERROR);
     logger_->LogSocketEventWithRv(channel_id_,
                                   proto::SEND_AUTH_CHALLENGE_FAILED, result);
     return result;
@@ -473,7 +480,7 @@
 
 CastSocketImpl::AuthTransportDelegate::AuthTransportDelegate(
     CastSocketImpl* socket)
-    : socket_(socket), error_state_(CHANNEL_ERROR_NONE) {
+    : socket_(socket), error_state_(ChannelError::NONE) {
   DCHECK(socket);
 }
 
@@ -493,7 +500,7 @@
 void CastSocketImpl::AuthTransportDelegate::OnMessage(
     const CastMessage& message) {
   if (!IsAuthMessage(message)) {
-    error_state_ = CHANNEL_ERROR_TRANSPORT_ERROR;
+    error_state_ = ChannelError::TRANSPORT_ERROR;
     socket_->PostTaskToStartConnectLoop(net::ERR_INVALID_RESPONSE);
   } else {
     socket_->challenge_reply_.reset(new CastMessage(message));
@@ -507,7 +514,7 @@
 int CastSocketImpl::DoAuthChallengeReplyComplete(int result) {
   VLOG_WITH_CONNECTION(1) << "DoAuthChallengeReplyComplete: " << result;
 
-  if (auth_delegate_->error_state() != CHANNEL_ERROR_NONE) {
+  if (auth_delegate_->error_state() != ChannelError::NONE) {
     SetErrorState(auth_delegate_->error_state());
     SetConnectState(proto::CONN_STATE_ERROR);
     return net::ERR_CONNECTION_FAILED;
@@ -520,7 +527,7 @@
   }
 
   if (!VerifyChallengeReply()) {
-    SetErrorState(CHANNEL_ERROR_AUTHENTICATION_ERROR);
+    SetErrorState(ChannelError::AUTHENTICATION_ERROR);
     SetConnectState(proto::CONN_STATE_ERROR);
     return net::ERR_CONNECTION_FAILED;
   }
@@ -531,14 +538,15 @@
 }
 
 void CastSocketImpl::DoConnectCallback() {
-  VLOG(1) << "DoConnectCallback (error_state = " << error_state_ << ")";
+  VLOG(1) << "DoConnectCallback (error_state = "
+          << ::cast_channel::ChannelErrorToString(error_state_) << ")";
   if (connect_callback_.is_null()) {
     DLOG(FATAL) << "Connection callback invoked multiple times.";
     return;
   }
 
-  if (error_state_ == CHANNEL_ERROR_NONE) {
-    SetReadyState(READY_STATE_OPEN);
+  if (error_state_ == ChannelError::NONE) {
+    SetReadyState(ReadyState::OPEN);
     transport_->SetReadDelegate(std::move(delegate_));
   } else {
     CloseInternal();
@@ -558,11 +566,12 @@
   // TODO(mfoltz): Enforce this when CastChannelAPITest is rewritten to create
   // and free sockets on the same thread.  crbug.com/398242
   DCHECK(CalledOnValidThread());
-  if (ready_state_ == READY_STATE_CLOSED) {
+  if (ready_state_ == ReadyState::CLOSED) {
     return;
   }
 
-  VLOG_WITH_CONNECTION(1) << "Close ReadyState = " << ready_state_;
+  VLOG_WITH_CONNECTION(1) << "Close ReadyState = "
+                          << ::cast_channel::ReadyStateToString(ready_state_);
   transport_.reset();
   tcp_socket_.reset();
   socket_.reset();
@@ -575,7 +584,7 @@
   // loops.
   connect_loop_callback_.Cancel();
   connect_timeout_callback_.Cancel();
-  SetReadyState(READY_STATE_CLOSED);
+  SetReadyState(ReadyState::CLOSED);
 }
 
 bool CastSocketImpl::CalledOnValidThread() const {
@@ -598,8 +607,9 @@
 }
 
 void CastSocketImpl::SetErrorState(ChannelError error_state) {
-  VLOG_WITH_CONNECTION(1) << "SetErrorState " << error_state;
-  DCHECK_EQ(CHANNEL_ERROR_NONE, error_state_);
+  VLOG_WITH_CONNECTION(1) << "SetErrorState "
+                          << ::cast_channel::ChannelErrorToString(error_state);
+  DCHECK_EQ(ChannelError::NONE, error_state_);
   error_state_ = error_state;
   delegate_->OnError(error_state_);
 }
diff --git a/extensions/browser/api/cast_channel/cast_socket.h b/extensions/browser/api/cast_channel/cast_socket.h
index 4ee3683..242508f 100644
--- a/extensions/browser/api/cast_channel/cast_socket.h
+++ b/extensions/browser/api/cast_channel/cast_socket.h
@@ -16,10 +16,12 @@
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
 #include "base/timer/timer.h"
+#include "components/cast_channel/cast_channel_enum.h"
+#include "extensions/browser/api/api_resource.h"
+#include "extensions/browser/api/api_resource_manager.h"
 #include "extensions/browser/api/cast_channel/cast_auth_util.h"
 #include "extensions/browser/api/cast_channel/cast_socket.h"
 #include "extensions/browser/api/cast_channel/cast_transport.h"
-#include "extensions/common/api/cast_channel.h"
 #include "extensions/common/api/cast_channel/logging.pb.h"
 #include "net/base/completion_callback.h"
 #include "net/base/io_buffer.h"
@@ -58,8 +60,15 @@
 // Public interface of the CastSocket class.
 class CastSocket {
  public:
+  using ChannelError = ::cast_channel::ChannelError;
+  using ChannelAuthType = ::cast_channel::ChannelAuthType;
+  using ReadyState = ::cast_channel::ReadyState;
+
   virtual ~CastSocket() {}
 
+  // Used by BrowserContextKeyedAPIFactory.
+  static const char* service_name() { return "CastSocketImplManager"; }
+
   // Connects the channel to the peer. If successful, the channel will be in
   // READY_STATE_OPEN.  DO NOT delete the CastSocket object in |callback|.
   // Instead use Close().
diff --git a/extensions/browser/api/cast_channel/cast_socket_unittest.cc b/extensions/browser/api/cast_channel/cast_socket_unittest.cc
index 29fbf778..bbd4767 100644
--- a/extensions/browser/api/cast_channel/cast_socket_unittest.cc
+++ b/extensions/browser/api/cast_channel/cast_socket_unittest.cc
@@ -42,6 +42,9 @@
 
 const int64_t kDistantTimeoutMillis = 100000;  // 100 seconds (never hit).
 
+using ::cast_channel::ChannelError;
+using ::cast_channel::ChannelAuthType;
+using ::cast_channel::ReadyState;
 using ::testing::_;
 using ::testing::A;
 using ::testing::DoAll;
@@ -177,7 +180,7 @@
       Logger* logger,
       uint64_t device_capabilities = cast_channel::CastDeviceCapability::NONE) {
     return std::unique_ptr<TestCastSocket>(new TestCastSocket(
-        CreateIPEndPointForTest(), CHANNEL_AUTH_TYPE_SSL_VERIFIED,
+        CreateIPEndPointForTest(), ChannelAuthType::SSL_VERIFIED,
         kDistantTimeoutMillis, logger, device_capabilities));
   }
 
@@ -372,7 +375,7 @@
                 SendMessage(EqualsProto(challenge_proto), _))
         .WillOnce(PostCompletionCallbackTask<1>(net::OK));
     EXPECT_CALL(*socket_->GetMockTransport(), Start());
-    EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+    EXPECT_CALL(handler_, OnConnectComplete(ChannelError::NONE));
     socket_->Connect(std::move(delegate_),
                      base::Bind(&CompleteHandler::OnConnectComplete,
                                 base::Unretained(&handler_)));
@@ -413,8 +416,8 @@
 
   HandleAuthHandshake();
 
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 }
 
 // Tests that the following connection flow works:
@@ -431,8 +434,8 @@
 
   HandleAuthHandshake();
 
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 }
 
 // Test that an AuthMessage with a mangled namespace triggers cancelation
@@ -449,7 +452,7 @@
               SendMessage(EqualsProto(challenge_proto), _))
       .WillOnce(PostCompletionCallbackTask<1>(net::OK));
   EXPECT_CALL(*socket_->GetMockTransport(), Start());
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_TRANSPORT_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::TRANSPORT_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
@@ -461,9 +464,8 @@
       mangled_auth_reply);
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_TRANSPORT_ERROR,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::TRANSPORT_ERROR, socket_->error_state());
 
   // Verifies that the CastSocket's resources were torn down during channel
   // close. (see http://crbug.com/504078)
@@ -476,14 +478,14 @@
 
   socket_->SetupTcpConnect(net::ASYNC, net::ERR_FAILED);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_ERROR, socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_ERROR, socket_->error_state());
 }
 
 // Test connection error - TCP connect fails (sync)
@@ -492,51 +494,49 @@
 
   socket_->SetupTcpConnect(net::SYNCHRONOUS, net::ERR_FAILED);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_ERROR, socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_ERROR, socket_->error_state());
 }
 
 // Test connection error - timeout
 TEST_F(CastSocketTest, TestConnectTcpTimeoutError) {
   CreateCastSocketSecure();
   socket_->SetupTcpConnectUnresponsive();
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_CONNECT_TIMEOUT));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_TIMEOUT));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CONNECT_TIMEOUT));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CONNECTING, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::CONNECTING, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
   socket_->TriggerTimeout();
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_TIMEOUT, socket_->error_state());
 }
 
 // Test connection error - TCP socket returns timeout
 TEST_F(CastSocketTest, TestConnectTcpSocketTimeoutError) {
   CreateCastSocketSecure();
   socket_->SetupTcpConnect(net::SYNCHRONOUS, net::ERR_CONNECTION_TIMED_OUT);
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_CONNECT_TIMEOUT));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_TIMEOUT));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CONNECT_TIMEOUT));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_TIMEOUT, socket_->error_state());
   EXPECT_EQ(net::ERR_CONNECTION_TIMED_OUT,
             logger_->GetLastErrors(socket_->id()).net_return_value);
 }
@@ -548,15 +548,14 @@
   socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
   socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_FAILED);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::AUTHENTICATION_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::AUTHENTICATION_ERROR, socket_->error_state());
 }
 
 // Test connection error - SSL connect fails (sync)
@@ -566,15 +565,14 @@
   socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
   socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_FAILED);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::AUTHENTICATION_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::AUTHENTICATION_ERROR, socket_->error_state());
   EXPECT_EQ(net::ERR_FAILED,
             logger_->GetLastErrors(socket_->id()).net_return_value);
 }
@@ -586,15 +584,14 @@
   socket_->SetupTcpConnect(net::SYNCHRONOUS, net::OK);
   socket_->SetupSslConnect(net::SYNCHRONOUS, net::ERR_CONNECTION_TIMED_OUT);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_TIMEOUT));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_TIMEOUT, socket_->error_state());
   EXPECT_EQ(net::ERR_CONNECTION_TIMED_OUT,
             logger_->GetLastErrors(socket_->id()).net_return_value);
 }
@@ -606,15 +603,14 @@
   socket_->SetupTcpConnect(net::ASYNC, net::OK);
   socket_->SetupSslConnect(net::ASYNC, net::ERR_CONNECTION_TIMED_OUT);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_CONNECT_TIMEOUT));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CONNECT_TIMEOUT));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_CONNECT_TIMEOUT,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CONNECT_TIMEOUT, socket_->error_state());
 }
 
 // Test connection error - challenge send fails
@@ -628,14 +624,14 @@
               SendMessage(EqualsProto(CreateAuthChallenge()), _))
       .WillOnce(PostCompletionCallbackTask<1>(net::ERR_CONNECTION_RESET));
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CAST_SOCKET_ERROR));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_SOCKET_ERROR, socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CAST_SOCKET_ERROR, socket_->error_state());
 }
 
 // Test connection error - connection is destroyed after the challenge is
@@ -666,19 +662,19 @@
               SendMessage(EqualsProto(CreateAuthChallenge()), _))
       .WillOnce(PostCompletionCallbackTask<1>(net::OK));
   socket_->AddReadResult(net::SYNCHRONOUS, net::ERR_FAILED);
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::CAST_SOCKET_ERROR));
   EXPECT_CALL(*socket_->GetMockTransport(), Start());
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
   socket_->GetMockTransport()->current_delegate()->OnError(
-      CHANNEL_ERROR_SOCKET_ERROR);
+      ChannelError::CAST_SOCKET_ERROR);
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_SOCKET_ERROR, socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::CAST_SOCKET_ERROR, socket_->error_state());
 }
 
 TEST_F(CastSocketTest, TestConnectChallengeVerificationFails) {
@@ -688,12 +684,12 @@
   socket_->SetupSslConnect(net::ASYNC, net::OK);
   socket_->SetVerifyChallengeResult(false);
 
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::AUTHENTICATION_ERROR));
   CastMessage challenge_proto = CreateAuthChallenge();
   EXPECT_CALL(*socket_->GetMockTransport(),
               SendMessage(EqualsProto(challenge_proto), _))
       .WillOnce(PostCompletionCallbackTask<1>(net::OK));
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_AUTHENTICATION_ERROR));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::AUTHENTICATION_ERROR));
   EXPECT_CALL(*socket_->GetMockTransport(), Start());
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
@@ -702,9 +698,8 @@
   socket_->GetMockTransport()->current_delegate()->OnMessage(CreateAuthReply());
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_CLOSED, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_AUTHENTICATION_ERROR,
-            socket_->error_state());
+  EXPECT_EQ(ReadyState::CLOSED, socket_->ready_state());
+  EXPECT_EQ(ChannelError::AUTHENTICATION_ERROR, socket_->error_state());
 }
 
 // Sends message data through an actual non-mocked CastTransport object,
@@ -732,13 +727,13 @@
   EXPECT_TRUE(MessageFramer::Serialize(test_message, &test_message_str));
   socket_->AddWriteResultForData(net::ASYNC, test_message_str);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::NONE));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 
   // Send the test message through a real transport object.
   EXPECT_CALL(handler_, OnWriteComplete(net::OK));
@@ -747,8 +742,8 @@
                                base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 }
 
 // Same as TestConnectEndToEndWithRealTransportAsync, except synchronous.
@@ -775,13 +770,13 @@
   EXPECT_TRUE(MessageFramer::Serialize(test_message, &test_message_str));
   socket_->AddWriteResultForData(net::SYNCHRONOUS, test_message_str);
 
-  EXPECT_CALL(handler_, OnConnectComplete(CHANNEL_ERROR_NONE));
+  EXPECT_CALL(handler_, OnConnectComplete(ChannelError::NONE));
   socket_->Connect(std::move(delegate_),
                    base::Bind(&CompleteHandler::OnConnectComplete,
                               base::Unretained(&handler_)));
   RunPendingTasks();
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 
   // Send the test message through a real transport object.
   EXPECT_CALL(handler_, OnWriteComplete(net::OK));
@@ -790,8 +785,8 @@
                                base::Unretained(&handler_)));
   RunPendingTasks();
 
-  EXPECT_EQ(cast_channel::READY_STATE_OPEN, socket_->ready_state());
-  EXPECT_EQ(cast_channel::CHANNEL_ERROR_NONE, socket_->error_state());
+  EXPECT_EQ(ReadyState::OPEN, socket_->ready_state());
+  EXPECT_EQ(ChannelError::NONE, socket_->error_state());
 }
 
 }  // namespace cast_channel
diff --git a/extensions/browser/api/cast_channel/cast_transport.cc b/extensions/browser/api/cast_channel/cast_transport.cc
index c8805bc..12168f4 100644
--- a/extensions/browser/api/cast_channel/cast_transport.cc
+++ b/extensions/browser/api/cast_channel/cast_transport.cc
@@ -24,8 +24,9 @@
 #include "net/base/net_errors.h"
 #include "net/socket/socket.h"
 
-#define VLOG_WITH_CONNECTION(level)                                           \
-  VLOG(level) << "[" << ip_endpoint_.ToString() << ", auth=" << channel_auth_ \
+#define VLOG_WITH_CONNECTION(level)                                     \
+  VLOG(level) << "[" << ip_endpoint_.ToString() << ", auth="            \
+              << ::cast_channel::ChannelAuthTypeToString(channel_auth_) \
               << "] "
 
 namespace extensions {
@@ -41,7 +42,7 @@
       socket_(socket),
       write_state_(WRITE_STATE_IDLE),
       read_state_(READ_STATE_READ),
-      error_state_(CHANNEL_ERROR_NONE),
+      error_state_(ChannelError::NONE),
       channel_id_(channel_id),
       ip_endpoint_(ip_endpoint),
       channel_auth_(channel_auth),
@@ -119,25 +120,25 @@
 // static
 proto::ErrorState CastTransportImpl::ErrorStateToProto(ChannelError state) {
   switch (state) {
-    case CHANNEL_ERROR_NONE:
+    case ChannelError::NONE:
       return proto::CHANNEL_ERROR_NONE;
-    case CHANNEL_ERROR_CHANNEL_NOT_OPEN:
+    case ChannelError::CHANNEL_NOT_OPEN:
       return proto::CHANNEL_ERROR_CHANNEL_NOT_OPEN;
-    case CHANNEL_ERROR_AUTHENTICATION_ERROR:
+    case ChannelError::AUTHENTICATION_ERROR:
       return proto::CHANNEL_ERROR_AUTHENTICATION_ERROR;
-    case CHANNEL_ERROR_CONNECT_ERROR:
+    case ChannelError::CONNECT_ERROR:
       return proto::CHANNEL_ERROR_CONNECT_ERROR;
-    case CHANNEL_ERROR_SOCKET_ERROR:
+    case ChannelError::CAST_SOCKET_ERROR:
       return proto::CHANNEL_ERROR_SOCKET_ERROR;
-    case CHANNEL_ERROR_TRANSPORT_ERROR:
+    case ChannelError::TRANSPORT_ERROR:
       return proto::CHANNEL_ERROR_TRANSPORT_ERROR;
-    case CHANNEL_ERROR_INVALID_MESSAGE:
+    case ChannelError::INVALID_MESSAGE:
       return proto::CHANNEL_ERROR_INVALID_MESSAGE;
-    case CHANNEL_ERROR_INVALID_CHANNEL_ID:
+    case ChannelError::INVALID_CHANNEL_ID:
       return proto::CHANNEL_ERROR_INVALID_CHANNEL_ID;
-    case CHANNEL_ERROR_CONNECT_TIMEOUT:
+    case ChannelError::CONNECT_TIMEOUT:
       return proto::CHANNEL_ERROR_CONNECT_TIMEOUT;
-    case CHANNEL_ERROR_UNKNOWN:
+    case ChannelError::UNKNOWN:
       return proto::CHANNEL_ERROR_UNKNOWN;
     default:
       NOTREACHED();
@@ -209,7 +210,8 @@
 }
 
 void CastTransportImpl::SetErrorState(ChannelError error_state) {
-  VLOG_WITH_CONNECTION(2) << "SetErrorState: " << error_state;
+  VLOG_WITH_CONNECTION(2) << "SetErrorState: "
+                          << ::cast_channel::ChannelErrorToString(error_state);
   error_state_ = error_state;
 }
 
@@ -250,7 +252,7 @@
       default:
         NOTREACHED() << "Unknown state in write state machine: " << state;
         SetWriteState(WRITE_STATE_ERROR);
-        SetErrorState(CHANNEL_ERROR_UNKNOWN);
+        SetErrorState(ChannelError::UNKNOWN);
         rv = net::ERR_FAILED;
         break;
     }
@@ -258,7 +260,7 @@
 
   if (write_state_ == WRITE_STATE_ERROR) {
     FlushWriteQueue();
-    DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+    DCHECK_NE(ChannelError::NONE, error_state_);
     VLOG_WITH_CONNECTION(2) << "Sending OnError().";
     delegate_->OnError(error_state_);
   }
@@ -285,7 +287,7 @@
   DCHECK(!write_queue_.empty());
   if (result <= 0) {  // NOTE that 0 also indicates an error
     logger_->LogSocketEventWithRv(channel_id_, proto::SOCKET_WRITE, result);
-    SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+    SetErrorState(ChannelError::CAST_SOCKET_ERROR);
     SetWriteState(WRITE_STATE_HANDLE_ERROR);
     return result == 0 ? net::ERR_FAILED : result;
   }
@@ -323,7 +325,7 @@
 
 int CastTransportImpl::DoWriteHandleError(int result) {
   VLOG_WITH_CONNECTION(2) << "DoWriteHandleError result=" << result;
-  DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+  DCHECK_NE(ChannelError::NONE, error_state_);
   DCHECK_LT(result, 0);
   SetWriteState(WRITE_STATE_ERROR);
   return net::ERR_FAILED;
@@ -372,7 +374,7 @@
       default:
         NOTREACHED() << "Unknown state in read state machine: " << state;
         SetReadState(READ_STATE_ERROR);
-        SetErrorState(CHANNEL_ERROR_UNKNOWN);
+        SetErrorState(ChannelError::UNKNOWN);
         rv = net::ERR_FAILED;
         break;
     }
@@ -404,7 +406,7 @@
   if (result <= 0) {
     logger_->LogSocketEventWithRv(channel_id_, proto::SOCKET_READ, result);
     VLOG_WITH_CONNECTION(1) << "Read error, peer closed the socket.";
-    SetErrorState(CHANNEL_ERROR_SOCKET_ERROR);
+    SetErrorState(ChannelError::CAST_SOCKET_ERROR);
     SetReadState(READ_STATE_HANDLE_ERROR);
     return result == 0 ? net::ERR_FAILED : result;
   }
@@ -413,12 +415,12 @@
   DCHECK(!current_message_);
   ChannelError framing_error;
   current_message_ = framer_->Ingest(result, &message_size, &framing_error);
-  if (current_message_.get() && (framing_error == CHANNEL_ERROR_NONE)) {
+  if (current_message_.get() && (framing_error == ChannelError::NONE)) {
     DCHECK_GT(message_size, static_cast<size_t>(0));
     SetReadState(READ_STATE_DO_CALLBACK);
-  } else if (framing_error != CHANNEL_ERROR_NONE) {
+  } else if (framing_error != ChannelError::NONE) {
     DCHECK(!current_message_);
-    SetErrorState(CHANNEL_ERROR_INVALID_MESSAGE);
+    SetErrorState(ChannelError::INVALID_MESSAGE);
     SetReadState(READ_STATE_HANDLE_ERROR);
   } else {
     DCHECK(!current_message_);
@@ -431,7 +433,7 @@
   VLOG_WITH_CONNECTION(2) << "DoReadCallback";
   if (!IsCastMessageValid(*current_message_)) {
     SetReadState(READ_STATE_HANDLE_ERROR);
-    SetErrorState(CHANNEL_ERROR_INVALID_MESSAGE);
+    SetErrorState(ChannelError::INVALID_MESSAGE);
     return net::ERR_INVALID_RESPONSE;
   }
   SetReadState(READ_STATE_READ);
@@ -442,7 +444,7 @@
 
 int CastTransportImpl::DoReadHandleError(int result) {
   VLOG_WITH_CONNECTION(2) << "DoReadHandleError";
-  DCHECK_NE(CHANNEL_ERROR_NONE, error_state_);
+  DCHECK_NE(ChannelError::NONE, error_state_);
   DCHECK_LE(result, 0);
   SetReadState(READ_STATE_ERROR);
   return net::ERR_FAILED;
diff --git a/extensions/browser/api/cast_channel/cast_transport.h b/extensions/browser/api/cast_channel/cast_transport.h
index ba67ee8f..68769e1 100644
--- a/extensions/browser/api/cast_channel/cast_transport.h
+++ b/extensions/browser/api/cast_channel/cast_transport.h
@@ -12,8 +12,8 @@
 #include "base/memory/ref_counted.h"
 #include "base/sequence_checker.h"
 #include "base/threading/thread_checker.h"
+#include "components/cast_channel/cast_channel_enum.h"
 #include "extensions/browser/api/cast_channel/logger.h"
-#include "extensions/common/api/cast_channel.h"
 #include "extensions/common/api/cast_channel/logging.pb.h"
 #include "net/base/completion_callback.h"
 #include "net/base/ip_endpoint.h"
@@ -38,6 +38,8 @@
   // Object to be informed of incoming messages and read errors.
   class Delegate {
    public:
+    using ChannelError = ::cast_channel::ChannelError;
+
     virtual ~Delegate() {}
 
     // Called once Transport is successfully initialized and started.
@@ -74,6 +76,9 @@
 // Manager class for reading and writing messages to/from a socket.
 class CastTransportImpl : public CastTransport {
  public:
+  using ChannelAuthType = ::cast_channel::ChannelAuthType;
+  using ChannelError = ::cast_channel::ChannelError;
+
   // Adds a CastMessage read/write layer to a socket.
   // Message read events are propagated to the owner via |read_delegate|.
   // |vlog_prefix| sets the prefix used for all VLOGged output.
diff --git a/extensions/browser/api/cast_channel/cast_transport_unittest.cc b/extensions/browser/api/cast_channel/cast_transport_unittest.cc
index aad1a9c1..90374d4 100644
--- a/extensions/browser/api/cast_channel/cast_transport_unittest.cc
+++ b/extensions/browser/api/cast_channel/cast_transport_unittest.cc
@@ -146,6 +146,9 @@
 
 class CastTransportTest : public testing::Test {
  public:
+  using ChannelError = ::cast_channel::ChannelError;
+  using ChannelAuthType = ::cast_channel::ChannelAuthType;
+
   CastTransportTest() : logger_(new Logger()) {
     delegate_ = new MockCastTransportDelegate;
     transport_.reset(new CastTransportImpl(&mock_socket_, kChannelId,
@@ -241,7 +244,7 @@
   EXPECT_CALL(mock_socket_, Write(NotNull(), _, _)).WillOnce(
       DoAll(EnqueueCallback<2>(&socket_cbs), Return(net::ERR_IO_PENDING)));
   EXPECT_CALL(write_handler, Complete(net::ERR_FAILED));
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   transport_->SendMessage(
       message,
       base::Bind(&CompleteHandler::Complete, base::Unretained(&write_handler)));
@@ -422,7 +425,7 @@
                       Return(net::ERR_IO_PENDING)))
       .RetiresOnSaturation();
 
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   transport_->Start();
   // Header read failure.
   socket_cbs.Pop(net::ERR_CONNECTION_RESET);
@@ -460,7 +463,7 @@
                       EnqueueCallback<2>(&socket_cbs),
                       Return(net::ERR_IO_PENDING)))
       .RetiresOnSaturation();
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   transport_->Start();
   // Header read is OK.
   socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
@@ -507,7 +510,7 @@
                       Return(net::ERR_IO_PENDING)))
       .RetiresOnSaturation();
 
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::INVALID_MESSAGE));
   transport_->Start();
   socket_cbs.Pop(MessageFramer::MessageHeader::header_size());
   socket_cbs.Pop(serialized_message.size() -
@@ -606,7 +609,7 @@
       .WillOnce(DoAll(FillBufferFromString<0>(serialized_message),
                       Return(net::ERR_CONNECTION_RESET)))
       .RetiresOnSaturation();
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   transport_->Start();
 }
 
@@ -635,7 +638,7 @@
                               MessageFramer::MessageHeader::header_size() - 1)),
                       Return(net::ERR_CONNECTION_RESET)))
       .RetiresOnSaturation();
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   transport_->Start();
   EXPECT_EQ(proto::SOCKET_READ, logger_->GetLastErrors(kChannelId).event_type);
   EXPECT_EQ(net::ERR_CONNECTION_RESET,
@@ -676,7 +679,7 @@
                       Return(serialized_message.size() -
                              MessageFramer::MessageHeader::header_size())))
       .RetiresOnSaturation();
-  EXPECT_CALL(*delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE));
+  EXPECT_CALL(*delegate_, OnError(ChannelError::INVALID_MESSAGE));
   transport_->Start();
 }
 }  // namespace cast_channel
diff --git a/extensions/browser/api/cast_channel/keep_alive_delegate.cc b/extensions/browser/api/cast_channel/keep_alive_delegate.cc
index 71adb89b..c6803eb 100644
--- a/extensions/browser/api/cast_channel/keep_alive_delegate.cc
+++ b/extensions/browser/api/cast_channel/keep_alive_delegate.cc
@@ -48,6 +48,8 @@
 // static
 const char KeepAliveDelegate::kHeartbeatPongType[] = "PONG";
 
+using ::cast_channel::ChannelError;
+
 // static
 CastMessage KeepAliveDelegate::CreateKeepAliveMessage(
     const char* message_type) {
@@ -146,19 +148,20 @@
     // An error occurred while sending the ping response.
     VLOG(1) << "Error sending " << message_type;
     logger_->LogSocketEventWithRv(socket_->id(), proto::PING_WRITE_ERROR, rv);
-    OnError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR);
+    OnError(ChannelError::CAST_SOCKET_ERROR);
   }
 }
 
 void KeepAliveDelegate::LivenessTimeout() {
-  OnError(cast_channel::CHANNEL_ERROR_PING_TIMEOUT);
+  OnError(ChannelError::PING_TIMEOUT);
   Stop();
 }
 
 // CastTransport::Delegate interface.
 void KeepAliveDelegate::OnError(ChannelError error_state) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  VLOG(1) << "KeepAlive::OnError: " << error_state;
+  VLOG(1) << "KeepAlive::OnError: "
+          << ::cast_channel::ChannelErrorToString(error_state);
   inner_delegate_->OnError(error_state);
   Stop();
 }
diff --git a/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc b/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc
index d9e946b..cc46dd5d 100644
--- a/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc
+++ b/extensions/browser/api/cast_channel/keep_alive_delegate_unittest.cc
@@ -49,6 +49,8 @@
 
 class KeepAliveDelegateTest : public testing::Test {
  public:
+  using ChannelError = ::cast_channel::ChannelError;
+
   KeepAliveDelegateTest() {}
   ~KeepAliveDelegateTest() override {}
 
@@ -87,7 +89,7 @@
 };
 
 TEST_F(KeepAliveDelegateTest, TestErrorHandledBeforeStarting) {
-  keep_alive_->OnError(CHANNEL_ERROR_CONNECT_ERROR);
+  keep_alive_->OnError(ChannelError::CONNECT_ERROR);
 }
 
 TEST_F(KeepAliveDelegateTest, TestPing) {
@@ -115,7 +117,7 @@
                           _))
       .WillOnce(PostCompletionCallbackTask<1>(net::ERR_CONNECTION_RESET));
   EXPECT_CALL(*inner_delegate_, Start());
-  EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_SOCKET_ERROR));
+  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
   EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
   EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1);
   EXPECT_CALL(*liveness_timer_, Stop());
@@ -136,7 +138,7 @@
                               KeepAliveDelegate::kHeartbeatPingType)),
                           _))
       .WillOnce(PostCompletionCallbackTask<1>(net::OK));
-  EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_PING_TIMEOUT));
+  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::PING_TIMEOUT));
   EXPECT_CALL(*inner_delegate_, Start());
   EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
   EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1);
@@ -182,7 +184,7 @@
       .Times(1)
       .InSequence(message_and_error_sequence)
       .RetiresOnSaturation();
-  EXPECT_CALL(*inner_delegate_, OnError(CHANNEL_ERROR_INVALID_MESSAGE))
+  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::INVALID_MESSAGE))
       .Times(1)
       .InSequence(message_and_error_sequence);
   EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(message_after_error)))
@@ -199,7 +201,7 @@
   keep_alive_->Start();
   keep_alive_->OnMessage(message);
   RunPendingTasks();
-  keep_alive_->OnError(CHANNEL_ERROR_INVALID_MESSAGE);
+  keep_alive_->OnError(ChannelError::INVALID_MESSAGE);
   RunPendingTasks();
 
   // Process a non-PING/PONG message and expect it to pass through.
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 15aa3aa3..77629e4 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -2401,13 +2401,13 @@
     },
     {
       "id": 224,
-      "description": "VPx decoding isn't supported before Windows 10 anniversary update.",
-      "cr_bugs": [616318],
+      "description": "VPx decoding isn't supported well before Windows 10 creators update.",
+      "cr_bugs": [616318, 667532],
       "os": {
         "type": "win",
         "version": {
           "op": "<",
-          "value": "10.0.14393"
+          "value": "10.0.15063"
         }
       },
       "features": [
diff --git a/ios/chrome/browser/ui/autofill/autofill_ui_type.h b/ios/chrome/browser/ui/autofill/autofill_ui_type.h
index 9a408bb..72768b28 100644
--- a/ios/chrome/browser/ui/autofill/autofill_ui_type.h
+++ b/ios/chrome/browser/ui/autofill/autofill_ui_type.h
@@ -15,6 +15,7 @@
   AutofillUITypeCreditCardExpMonth,
   AutofillUITypeCreditCardExpYear,
   AutofillUITypeCreditCardBillingAddress,
+  AutofillUITypeCreditCardSaveToChrome,
   AutofillUITypeProfileFullName,
   AutofillUITypeProfileCompanyName,
   AutofillUITypeProfileHomeAddressStreet,
diff --git a/ios/chrome/browser/ui/payments/BUILD.gn b/ios/chrome/browser/ui/payments/BUILD.gn
index c5dac5aa..f37688f 100644
--- a/ios/chrome/browser/ui/payments/BUILD.gn
+++ b/ios/chrome/browser/ui/payments/BUILD.gn
@@ -11,8 +11,6 @@
     "address_edit_coordinator.mm",
     "address_edit_mediator.h",
     "address_edit_mediator.mm",
-    "address_edit_view_controller.h",
-    "address_edit_view_controller.mm",
     "country_selection_coordinator.h",
     "country_selection_coordinator.mm",
     "credit_card_edit_coordinator.h",
@@ -91,15 +89,11 @@
 source_set("payments_ui") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "credit_card_edit_view_controller.h",
-    "credit_card_edit_view_controller.mm",
-    "credit_card_edit_view_controller_data_source.h",
     "payment_items_display_view_controller.h",
     "payment_items_display_view_controller.mm",
     "payment_items_display_view_controller_actions.h",
     "payment_items_display_view_controller_data_source.h",
     "payment_request_edit_consumer.h",
-    "payment_request_edit_view_controller+internal.h",
     "payment_request_edit_view_controller.h",
     "payment_request_edit_view_controller.mm",
     "payment_request_edit_view_controller_actions.h",
@@ -143,7 +137,6 @@
     "address_edit_coordinator_unittest.mm",
     "country_selection_coordinator_unittest.mm",
     "credit_card_edit_coordinator_unittest.mm",
-    "credit_card_edit_view_controller_unittest.mm",
     "payment_items_display_coordinator_unittest.mm",
     "payment_items_display_view_controller_unittest.mm",
     "payment_method_selection_coordinator_unittest.mm",
diff --git a/ios/chrome/browser/ui/payments/address_edit_coordinator.h b/ios/chrome/browser/ui/payments/address_edit_coordinator.h
index 799fb224..f6f8a3b6 100644
--- a/ios/chrome/browser/ui/payments/address_edit_coordinator.h
+++ b/ios/chrome/browser/ui/payments/address_edit_coordinator.h
@@ -6,7 +6,6 @@
 #define IOS_CHROME_BROWSER_UI_PAYMENTS_ADDRESS_EDIT_COORDINATOR_H_
 
 #import "ios/chrome/browser/chrome_coordinator.h"
-#import "ios/chrome/browser/ui/payments/address_edit_view_controller.h"
 #import "ios/chrome/browser/ui/payments/country_selection_coordinator.h"
 #import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
 
@@ -38,7 +37,7 @@
 // controller. This view controller will be presented by the view controller
 // provided in the initializer.
 @interface AddressEditCoordinator
-    : ChromeCoordinator<AddressEditViewControllerDelegate,
+    : ChromeCoordinator<PaymentRequestEditViewControllerDelegate,
                         PaymentRequestEditViewControllerValidator,
                         CountrySelectionCoordinatorDelegate>
 
diff --git a/ios/chrome/browser/ui/payments/address_edit_coordinator.mm b/ios/chrome/browser/ui/payments/address_edit_coordinator.mm
index 4d4c78e1..8e06e94 100644
--- a/ios/chrome/browser/ui/payments/address_edit_coordinator.mm
+++ b/ios/chrome/browser/ui/payments/address_edit_coordinator.mm
@@ -31,7 +31,7 @@
 @property(nonatomic, strong)
     CountrySelectionCoordinator* countrySelectionCoordinator;
 
-@property(nonatomic, strong) AddressEditViewController* viewController;
+@property(nonatomic, strong) PaymentRequestEditViewController* viewController;
 
 @property(nonatomic, strong) AddressEditMediator* mediator;
 
@@ -47,7 +47,7 @@
 @synthesize mediator = _mediator;
 
 - (void)start {
-  self.viewController = [[AddressEditViewController alloc] init];
+  self.viewController = [[PaymentRequestEditViewController alloc] init];
   // TODO(crbug.com/602666): Title varies depending on what field is missing.
   // e.g., Add Email vs. Add Phone Number.
   NSString* title = self.address
@@ -88,7 +88,7 @@
   return nil;
 }
 
-#pragma mark - AddressEditViewControllerDelegate
+#pragma mark - PaymentRequestEditViewControllerDelegate
 
 - (void)paymentRequestEditViewController:
             (PaymentRequestEditViewController*)controller
@@ -104,8 +104,9 @@
   }
 }
 
-- (void)addressEditViewController:(AddressEditViewController*)controller
-           didFinishEditingFields:(NSArray<EditorField*>*)fields {
+- (void)paymentRequestEditViewController:
+            (PaymentRequestEditViewController*)controller
+                  didFinishEditingFields:(NSArray<EditorField*>*)fields {
   // Create an empty autofill profile. If an address is being edited, copy over
   // the information.
   autofill::AutofillProfile address =
@@ -136,8 +137,8 @@
                 didFinishEditingAddress:self.address];
 }
 
-- (void)addressEditViewControllerDidCancel:
-    (AddressEditViewController*)controller {
+- (void)paymentRequestEditViewControllerDidCancel:
+    (PaymentRequestEditViewController*)controller {
   [self.delegate addressEditCoordinatorDidCancel:self];
 }
 
diff --git a/ios/chrome/browser/ui/payments/address_edit_coordinator_unittest.mm b/ios/chrome/browser/ui/payments/address_edit_coordinator_unittest.mm
index bad6de67..30fa649 100644
--- a/ios/chrome/browser/ui/payments/address_edit_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/payments/address_edit_coordinator_unittest.mm
@@ -16,7 +16,7 @@
 #include "components/prefs/pref_service.h"
 #include "ios/chrome/browser/payments/payment_request_test_util.h"
 #include "ios/chrome/browser/payments/test_payment_request.h"
-#import "ios/chrome/browser/ui/payments/address_edit_view_controller.h"
+#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
 #import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -178,11 +178,11 @@
   EXPECT_CALL(personal_data_manager_, UpdateProfile(_)).Times(0);
 
   // Call the controller delegate method.
-  AddressEditViewController* view_controller =
-      base::mac::ObjCCastStrict<AddressEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator addressEditViewController:view_controller
-                  didFinishEditingFields:GetEditorFields()];
+  [coordinator paymentRequestEditViewController:view_controller
+                         didFinishEditingFields:GetEditorFields()];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
@@ -233,11 +233,11 @@
       .Times(1);
 
   // Call the controller delegate method.
-  AddressEditViewController* view_controller =
-      base::mac::ObjCCastStrict<AddressEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator addressEditViewController:view_controller
-                  didFinishEditingFields:GetEditorFields()];
+  [coordinator paymentRequestEditViewController:view_controller
+                         didFinishEditingFields:GetEditorFields()];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
@@ -269,10 +269,10 @@
   EXPECT_EQ(2u, navigation_controller.viewControllers.count);
 
   // Call the controller delegate method.
-  AddressEditViewController* view_controller =
-      base::mac::ObjCCastStrict<AddressEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator addressEditViewControllerDidCancel:view_controller];
+  [coordinator paymentRequestEditViewControllerDidCancel:view_controller];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
diff --git a/ios/chrome/browser/ui/payments/address_edit_mediator.mm b/ios/chrome/browser/ui/payments/address_edit_mediator.mm
index 3f872b2..9b7b3fc 100644
--- a/ios/chrome/browser/ui/payments/address_edit_mediator.mm
+++ b/ios/chrome/browser/ui/payments/address_edit_mediator.mm
@@ -118,6 +118,10 @@
   return NO;
 }
 
+- (UIImage*)iconIdentifyingEditorField:(EditorField*)field {
+  return nil;
+}
+
 #pragma mark - RegionDataLoaderConsumer
 
 - (void)regionDataLoaderDidSucceedWithRegions:
diff --git a/ios/chrome/browser/ui/payments/address_edit_view_controller.h b/ios/chrome/browser/ui/payments/address_edit_view_controller.h
deleted file mode 100644
index be84f49..0000000
--- a/ios/chrome/browser/ui/payments/address_edit_view_controller.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_ADDRESS_EDIT_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_PAYMENTS_ADDRESS_EDIT_VIEW_CONTROLLER_H_
-
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
-
-@class AddressEditViewController;
-@class EditorField;
-
-// Delegate protocol for AddressEditViewController.
-@protocol
-    AddressEditViewControllerDelegate<PaymentRequestEditViewControllerDelegate>
-
-// Notifies the delegate that the user has finished editing the address editor
-// fields.
-- (void)addressEditViewController:(AddressEditViewController*)controller
-           didFinishEditingFields:(NSArray<EditorField*>*)fields;
-
-// Notifies the delegate that the user has chosen to discard entries in the
-// address editor fields and return to the previous screen.
-- (void)addressEditViewControllerDidCancel:
-    (AddressEditViewController*)controller;
-
-@end
-
-// View controller responsible for presenting an address edit form. The form
-// features text fields for the field definitions passed to the initializer.
-@interface AddressEditViewController : PaymentRequestEditViewController
-
-// The delegate to be notified when the user returns or finishes editing the
-// address editor fields.
-@property(nonatomic, weak) id<AddressEditViewControllerDelegate> delegate;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_ADDRESS_EDIT_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/payments/address_edit_view_controller.mm b/ios/chrome/browser/ui/payments/address_edit_view_controller.mm
deleted file mode 100644
index d06eb3b..0000000
--- a/ios/chrome/browser/ui/payments/address_edit_view_controller.mm
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/payments/address_edit_view_controller.h"
-
-#include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h"
-#import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
-#include "ui/base/l10n/l10n_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-NSString* const kAddressEditCollectionViewAccessibilityID =
-    @"kAddressEditCollectionViewAccessibilityID";
-
-}  // namespace
-
-@interface AddressEditViewController ()
-
-// The list of field definitions for the editor.
-@property(nonatomic, weak) NSArray<EditorField*>* fields;
-
-@end
-
-@implementation AddressEditViewController
-
-@synthesize delegate = _delegate;
-@synthesize fields = _fields;
-
-#pragma mark - Setters
-
-- (void)setDelegate:(id<AddressEditViewControllerDelegate>)delegate {
-  [super setDelegate:delegate];
-  _delegate = delegate;
-}
-
-#pragma mark - CollectionViewController methods
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-
-  self.collectionView.accessibilityIdentifier =
-      kAddressEditCollectionViewAccessibilityID;
-}
-
-#pragma mark - PaymentRequestEditViewControllerActions methods
-
-- (void)onCancel {
-  [super onCancel];
-
-  [self.delegate addressEditViewControllerDidCancel:self];
-}
-
-- (void)onDone {
-  [super onDone];
-
-  if (![self validateForm])
-    return;
-
-  [self.delegate addressEditViewController:self
-                    didFinishEditingFields:self.fields];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.h b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.h
index da3037df..0a4fdd57 100644
--- a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.h
+++ b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.h
@@ -6,7 +6,6 @@
 #define IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_COORDINATOR_H_
 
 #import "ios/chrome/browser/chrome_coordinator.h"
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h"
 #import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
 
 namespace autofill {
@@ -38,7 +37,7 @@
 // controller. This view controller will be presented by the view controller
 // provided in the initializer.
 @interface CreditCardEditCoordinator
-    : ChromeCoordinator<CreditCardEditViewControllerDelegate,
+    : ChromeCoordinator<PaymentRequestEditViewControllerDelegate,
                         PaymentRequestEditViewControllerValidator>
 
 // The credit card to be edited, if any. This pointer is not owned by this class
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.mm b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.mm
index 693cc92..68f03b68 100644
--- a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.mm
+++ b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator.mm
@@ -65,7 +65,7 @@
 }  // namespace
 
 @interface CreditCardEditCoordinator () {
-  CreditCardEditViewController* _viewController;
+  PaymentRequestEditViewController* _viewController;
 
   CreditCardEditViewControllerMediator* _mediator;
 }
@@ -79,7 +79,7 @@
 @synthesize delegate = _delegate;
 
 - (void)start {
-  _viewController = [[CreditCardEditViewController alloc] init];
+  _viewController = [[PaymentRequestEditViewController alloc] init];
   // TODO(crbug.com/602666): Title varies depending on the missing fields.
   NSString* title = _creditCard
                         ? l10n_util::GetNSString(IDS_PAYMENTS_EDIT_CARD)
@@ -132,7 +132,7 @@
   return nil;
 }
 
-#pragma mark - CreditCardEditViewControllerDelegate
+#pragma mark - PaymentRequestEditViewControllerDelegate
 
 - (void)paymentRequestEditViewController:
             (PaymentRequestEditViewController*)controller
@@ -142,9 +142,10 @@
   }
 }
 
-- (void)creditCardEditViewController:(CreditCardEditViewController*)controller
-              didFinishEditingFields:(NSArray<EditorField*>*)fields
-                      saveCreditCard:(BOOL)saveCreditCard {
+- (void)paymentRequestEditViewController:
+            (PaymentRequestEditViewController*)controller
+                  didFinishEditingFields:(NSArray<EditorField*>*)fields {
+  BOOL saveCreditCard = NO;
   // Create an empty credit card. If a credit card is being edited, copy over
   // the information.
   autofill::CreditCard creditCard =
@@ -153,7 +154,9 @@
                                          autofill::kSettingsOrigin);
 
   for (EditorField* field in fields) {
-    if (field.autofillUIType == AutofillUITypeCreditCardBillingAddress) {
+    if (field.autofillUIType == AutofillUITypeCreditCardSaveToChrome) {
+      saveCreditCard = [field.value boolValue];
+    } else if (field.autofillUIType == AutofillUITypeCreditCardBillingAddress) {
       creditCard.set_billing_address_id(base::SysNSStringToUTF8(field.value));
     } else {
       creditCard.SetRawInfo(
@@ -188,8 +191,8 @@
             didFinishEditingCreditCard:_creditCard];
 }
 
-- (void)creditCardEditViewControllerDidCancel:
-    (CreditCardEditViewController*)controller {
+- (void)paymentRequestEditViewControllerDidCancel:
+    (PaymentRequestEditViewController*)controller {
   [_delegate creditCardEditCoordinatorDidCancel:self];
 }
 
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator_unittest.mm b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator_unittest.mm
index 8a7eaa1..ff8e42f 100644
--- a/ios/chrome/browser/ui/payments/credit_card_edit_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/payments/credit_card_edit_coordinator_unittest.mm
@@ -14,7 +14,6 @@
 #include "ios/chrome/browser/payments/payment_request.h"
 #include "ios/chrome/browser/payments/payment_request_test_util.h"
 #import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h"
 #import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -61,7 +60,7 @@
          arg.billing_address_id() == billing_address_id;
 }
 
-NSArray<EditorField*>* GetEditorFields() {
+NSArray<EditorField*>* GetEditorFields(bool save_card) {
   return @[
     [[EditorField alloc] initWithAutofillUIType:AutofillUITypeCreditCardNumber
                                       fieldType:EditorFieldTypeTextField
@@ -90,6 +89,12 @@
                          label:@"Billing Address"
                          value:@"12345"
                       required:YES],
+    [[EditorField alloc]
+        initWithAutofillUIType:AutofillUITypeCreditCardSaveToChrome
+                     fieldType:EditorFieldTypeSwitch
+                         label:@"Save Card"
+                         value:save_card ? @"YES" : @"NO"
+                      required:YES],
   ];
 }
 
@@ -178,12 +183,11 @@
   EXPECT_CALL(personal_data_manager_, UpdateCreditCard(_)).Times(0);
 
   // Call the controller delegate method.
-  CreditCardEditViewController* view_controller =
-      base::mac::ObjCCastStrict<CreditCardEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator creditCardEditViewController:view_controller
-                     didFinishEditingFields:GetEditorFields()
-                             saveCreditCard:YES];
+  [coordinator paymentRequestEditViewController:view_controller
+                         didFinishEditingFields:GetEditorFields(true)];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
@@ -230,12 +234,11 @@
   EXPECT_CALL(personal_data_manager_, UpdateCreditCard(_)).Times(0);
 
   // Call the controller delegate method.
-  CreditCardEditViewController* view_controller =
-      base::mac::ObjCCastStrict<CreditCardEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator creditCardEditViewController:view_controller
-                     didFinishEditingFields:GetEditorFields()
-                             saveCreditCard:NO];
+  [coordinator paymentRequestEditViewController:view_controller
+                         didFinishEditingFields:GetEditorFields(false)];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
@@ -285,12 +288,11 @@
       .Times(1);
 
   // Call the controller delegate method.
-  CreditCardEditViewController* view_controller =
-      base::mac::ObjCCastStrict<CreditCardEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator creditCardEditViewController:view_controller
-                     didFinishEditingFields:GetEditorFields()
-                             saveCreditCard:YES];
+  [coordinator paymentRequestEditViewController:view_controller
+                         didFinishEditingFields:GetEditorFields(true)];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
@@ -322,10 +324,10 @@
   EXPECT_EQ(2u, navigation_controller.viewControllers.count);
 
   // Call the controller delegate method.
-  CreditCardEditViewController* view_controller =
-      base::mac::ObjCCastStrict<CreditCardEditViewController>(
+  PaymentRequestEditViewController* view_controller =
+      base::mac::ObjCCastStrict<PaymentRequestEditViewController>(
           navigation_controller.visibleViewController);
-  [coordinator creditCardEditViewControllerDidCancel:view_controller];
+  [coordinator paymentRequestEditViewControllerDidCancel:view_controller];
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_mediator.h b/ios/chrome/browser/ui/payments/credit_card_edit_mediator.h
index 9a19d4f..c020d121 100644
--- a/ios/chrome/browser/ui/payments/credit_card_edit_mediator.h
+++ b/ios/chrome/browser/ui/payments/credit_card_edit_mediator.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_MEDIATOR_H_
 #define IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_MEDIATOR_H_
 
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller_data_source.h"
+#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h"
 
 class PaymentRequest;
 @protocol PaymentRequestEditConsumer;
@@ -16,7 +16,7 @@
 
 // Serves as data source for CreditCardEditViewController.
 @interface CreditCardEditViewControllerMediator
-    : NSObject<CreditCardEditViewControllerDataSource>
+    : NSObject<PaymentRequestEditViewControllerDataSource>
 
 // The consumer for this object. This can change during the lifetime of this
 // object and may be nil.
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_mediator.mm b/ios/chrome/browser/ui/payments/credit_card_edit_mediator.mm
index 13e75e4..2b71da3 100644
--- a/ios/chrome/browser/ui/payments/credit_card_edit_mediator.mm
+++ b/ios/chrome/browser/ui/payments/credit_card_edit_mediator.mm
@@ -15,7 +15,6 @@
 #include "ios/chrome/browser/payments/payment_request.h"
 #import "ios/chrome/browser/payments/payment_request_util.h"
 #import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
-#import "ios/chrome/browser/ui/autofill/autofill_ui_type_util.h"
 #import "ios/chrome/browser/ui/payments/cells/accepted_payment_methods_item.h"
 #import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
 #import "ios/chrome/browser/ui/payments/payment_request_edit_consumer.h"
@@ -28,7 +27,6 @@
 #endif
 
 namespace {
-using ::AutofillUITypeFromAutofillType;
 using ::autofill::data_util::GetIssuerNetworkForBasicCardIssuerNetwork;
 using ::autofill::data_util::GetPaymentRequestData;
 using ::payment_request_util::GetBillingAddressLabelFromAutofillProfile;
@@ -73,7 +71,7 @@
   [self.consumer setEditorFields:[self createEditorFields]];
 }
 
-#pragma mark - CreditCardEditViewControllerDataSource
+#pragma mark - PaymentRequestEditViewControllerDataSource
 
 - (CollectionViewItem*)headerItem {
   if (_creditCard && !autofill::IsCreditCardLocal(*_creditCard)) {
@@ -118,9 +116,13 @@
   return !_creditCard || autofill::IsCreditCardLocal(*_creditCard);
 }
 
-- (UIImage*)cardTypeIconFromCardNumber:(NSString*)cardNumber {
+- (UIImage*)iconIdentifyingEditorField:(EditorField*)field {
+  // Early return if the field is not the credit card number field.
+  if (field.autofillUIType != AutofillUITypeCreditCardNumber)
+    return nil;
+
   const char* issuerNetwork = autofill::CreditCard::GetCardNetwork(
-      base::SysNSStringToUTF16(cardNumber));
+      base::SysNSStringToUTF16(field.value));
   // This should not happen in Payment Request.
   if (issuerNetwork == autofill::kGenericCard)
     return nil;
@@ -186,40 +188,40 @@
           ? [NSString stringWithFormat:@"%04d", _creditCard->expiration_year()]
           : nil;
 
-  NSMutableArray* editorFields = [[NSMutableArray alloc] init];
-  [editorFields addObjectsFromArray:@[
+  return @[
     [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeFromAutofillType(
-                                   autofill::CREDIT_CARD_NUMBER)
+        initWithAutofillUIType:AutofillUITypeCreditCardNumber
                      fieldType:EditorFieldTypeTextField
                          label:l10n_util::GetNSString(IDS_PAYMENTS_CARD_NUMBER)
                          value:creditCardNumber
                       required:YES],
     [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeFromAutofillType(
-                                   autofill::CREDIT_CARD_NAME_FULL)
+        initWithAutofillUIType:AutofillUITypeCreditCardHolderFullName
                      fieldType:EditorFieldTypeTextField
                          label:l10n_util::GetNSString(IDS_PAYMENTS_NAME_ON_CARD)
                          value:creditCardName
                       required:YES],
     [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeFromAutofillType(
-                                   autofill::CREDIT_CARD_EXP_MONTH)
+        initWithAutofillUIType:AutofillUITypeCreditCardExpMonth
                      fieldType:EditorFieldTypeTextField
                          label:l10n_util::GetNSString(IDS_PAYMENTS_EXP_MONTH)
                          value:creditCardExpMonth
                       required:YES],
     [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeFromAutofillType(
-                                   autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR)
+        initWithAutofillUIType:AutofillUITypeCreditCardExpYear
                      fieldType:EditorFieldTypeTextField
                          label:l10n_util::GetNSString(IDS_PAYMENTS_EXP_YEAR)
                          value:creditCardExpYear
-                      required:YES]
-  ]];
-  // The billing address field goes at the end.
-  [editorFields addObject:billingAddressEditorField];
-  return editorFields;
+                      required:YES],
+    billingAddressEditorField,
+    [[EditorField alloc]
+        initWithAutofillUIType:AutofillUITypeCreditCardSaveToChrome
+                     fieldType:EditorFieldTypeSwitch
+                         label:l10n_util::GetNSString(
+                                   IDS_PAYMENTS_SAVE_CARD_TO_DEVICE_CHECKBOX)
+                         value:@"YES"
+                      required:YES],
+  ];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h b/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h
deleted file mode 100644
index 0073807bd..0000000
--- a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_H_
-
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller_data_source.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
-
-@class CreditCardEditViewController;
-
-// Delegate protocol for CreditCardEditViewController.
-@protocol CreditCardEditViewControllerDelegate<
-    PaymentRequestEditViewControllerDelegate>
-
-// Notifies the delegate that the user has finished editing the credit card
-// editor fields.
-- (void)creditCardEditViewController:(CreditCardEditViewController*)controller
-              didFinishEditingFields:(NSArray<EditorField*>*)fields
-                      saveCreditCard:(BOOL)saveCard;
-
-// Notifies the delegate that the user has chosen to discard entries in the
-// credit card editor fields and return to the previous screen.
-- (void)creditCardEditViewControllerDidCancel:
-    (CreditCardEditViewController*)controller;
-
-@end
-
-// View controller responsible for presenting a credit card edit form. The form
-// features text fields for the field definitions passed to the initializer in
-// addition to an item displaying the billing address associated with the credit
-// card, if any.
-@interface CreditCardEditViewController : PaymentRequestEditViewController
-
-// The delegate to be notified when the user returns or finishes editing the
-// credit card editor fields.
-@property(nonatomic, weak) id<CreditCardEditViewControllerDelegate> delegate;
-
-// The data source for this view controller.
-@property(nonatomic, weak) id<CreditCardEditViewControllerDataSource>
-    dataSource;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.mm b/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.mm
deleted file mode 100644
index 79b8ce4..0000000
--- a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller.mm
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h"
-
-#import "base/mac/foundation_util.h"
-#include "base/memory/ptr_util.h"
-#include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
-#import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
-#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
-#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collection_view_controller.h"
-#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
-#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h"
-#import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
-#import "ios/chrome/browser/ui/uikit_ui_util.h"
-#include "ui/base/l10n/l10n_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-NSString* const kCreditCardEditCollectionViewId =
-    @"kCreditCardEditCollectionViewId";
-
-typedef NS_ENUM(NSInteger, SectionIdentifier) {
-  SectionIdentifierSaveCard = kSectionIdentifierEnumStart,
-};
-
-typedef NS_ENUM(NSInteger, ItemType) {
-  ItemTypeSaveCard = kItemTypeEnumStart,
-};
-
-}  // namespace
-
-@interface CreditCardEditViewController () {
-  // Indicates whether the credit card being created should be saved locally.
-  BOOL _saveCreditCard;
-}
-
-// The list of field definitions for the editor.
-@property(nonatomic, strong) NSArray<EditorField*>* fields;
-
-@end
-
-@implementation CreditCardEditViewController
-
-@synthesize delegate = _delegate;
-@synthesize dataSource = _dataSource;
-@synthesize fields = _fields;
-
-#pragma mark - Setters
-
-- (void)setDelegate:(id<CreditCardEditViewControllerDelegate>)delegate {
-  [super setDelegate:delegate];
-  _delegate = delegate;
-}
-
-- (void)setDataSource:(id<CreditCardEditViewControllerDataSource>)dataSource {
-  [super setDataSource:dataSource];
-  _dataSource = dataSource;
-}
-
-#pragma mark - PaymentRequestEditViewControllerActions methods
-
-- (void)onCancel {
-  [super onCancel];
-
-  [_delegate creditCardEditViewControllerDidCancel:self];
-}
-
-- (void)onDone {
-  [super onDone];
-
-  if (![self validateForm])
-    return;
-
-  [_delegate creditCardEditViewController:self
-                   didFinishEditingFields:_fields
-                           saveCreditCard:_saveCreditCard];
-}
-
-#pragma mark - CollectionViewController methods
-
-- (void)loadModel {
-  [super loadModel];
-
-  // If editing a credit card, set the card type icon (e.g. "Visa").
-  if (_dataSource.state == EditViewControllerStateEdit) {
-    for (EditorField* field in _fields) {
-      if (field.autofillUIType == AutofillUITypeCreditCardNumber) {
-        AutofillEditItem* item =
-            base::mac::ObjCCastStrict<AutofillEditItem>(field.item);
-        item.identifyingIcon =
-            [_dataSource cardTypeIconFromCardNumber:item.textFieldValue];
-      }
-    }
-  }
-}
-
-- (void)loadFooterItems {
-  CollectionViewModel* model = self.collectionViewModel;
-
-  // "Save card" section. Visible only when creating a card.
-  if (_dataSource.state == EditViewControllerStateCreate) {
-    [model addSectionWithIdentifier:SectionIdentifierSaveCard];
-    CollectionViewSwitchItem* saveCardItem =
-        [[CollectionViewSwitchItem alloc] initWithType:ItemTypeSaveCard];
-    saveCardItem.text =
-        l10n_util::GetNSString(IDS_PAYMENTS_SAVE_CARD_TO_DEVICE_CHECKBOX);
-    saveCardItem.on = YES;
-    [model addItem:saveCardItem
-        toSectionWithIdentifier:SectionIdentifierSaveCard];
-  }
-
-  [super loadFooterItems];
-}
-
-#pragma mark - UITextFieldDelegate
-
-// This method is called as the text is being typed in, pasted, or deleted. Asks
-// the delegate if the text should be changed. Should always return YES. During
-// typing/pasting text, |newText| contains one or more new characters. When user
-// deletes text, |newText| is empty. |range| is the range of characters to be
-// replaced.
-- (BOOL)textField:(UITextField*)textField
-    shouldChangeCharactersInRange:(NSRange)range
-                replacementString:(NSString*)newText {
-  NSIndexPath* indexPath = [self indexPathForCurrentTextField];
-  AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>(
-      [self.collectionViewModel itemAtIndexPath:indexPath]);
-
-  // If the user is typing in the credit card number field, update the card type
-  // icon (e.g. "Visa") to reflect the number being typed.
-  if (item.autofillUIType == AutofillUITypeCreditCardNumber) {
-    // Obtain the text being typed.
-    NSString* updatedText =
-        [textField.text stringByReplacingCharactersInRange:range
-                                                withString:newText];
-    item.identifyingIcon = [_dataSource cardTypeIconFromCardNumber:updatedText];
-
-    // Update the cell.
-    [self reconfigureCellsForItems:@[ item ]];
-  }
-
-  return YES;
-}
-
-#pragma mark - UICollectionViewDataSource
-
-- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
-                 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
-  UICollectionViewCell* cell =
-      [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
-  CollectionViewItem* item =
-      [self.collectionViewModel itemAtIndexPath:indexPath];
-
-  switch (item.type) {
-    case ItemTypeSaveCard: {
-      CollectionViewSwitchCell* switchCell =
-          base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
-      [switchCell.switchView addTarget:self
-                                action:@selector(saveCardSwitchToggled:)
-                      forControlEvents:UIControlEventValueChanged];
-      break;
-    }
-    default:
-      break;
-  }
-
-  return cell;
-}
-
-#pragma mark MDCCollectionViewStylingDelegate
-
-- (CGFloat)collectionView:(UICollectionView*)collectionView
-    cellHeightAtIndexPath:(NSIndexPath*)indexPath {
-  CollectionViewItem* item =
-      [self.collectionViewModel itemAtIndexPath:indexPath];
-  switch (item.type) {
-    case ItemTypeSaveCard:
-      return [MDCCollectionViewCell
-          cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
-                             forItem:item];
-    default:
-      return
-          [super collectionView:collectionView cellHeightAtIndexPath:indexPath];
-  }
-}
-
-- (BOOL)collectionView:(UICollectionView*)collectionView
-    hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
-  NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
-  switch (type) {
-    case ItemTypeSaveCard:
-      return YES;
-    default:
-      return [super collectionView:collectionView
-           hidesInkViewAtIndexPath:indexPath];
-  }
-}
-
-#pragma mark Switch Actions
-
-- (void)saveCardSwitchToggled:(UISwitch*)sender {
-  _saveCreditCard = sender.isOn;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_data_source.h b/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_data_source.h
deleted file mode 100644
index 406aca340..0000000
--- a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_data_source.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
-#define IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h"
-
-// Data source protocol for CreditCardEditViewController.
-@protocol CreditCardEditViewControllerDataSource<
-    PaymentRequestEditViewControllerDataSource>
-
-// Returns the credit card type icon corresponding to |cardNumber|.
-- (UIImage*)cardTypeIconFromCardNumber:(NSString*)cardNumber;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_CREDIT_CARD_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
diff --git a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_unittest.mm b/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_unittest.mm
deleted file mode 100644
index 12f04c8..0000000
--- a/ios/chrome/browser/ui/payments/credit_card_edit_view_controller_unittest.mm
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/payments/credit_card_edit_view_controller.h"
-
-#include "base/mac/foundation_util.h"
-#include "base/memory/ptr_util.h"
-#include "components/autofill/core/browser/field_types.h"
-#include "ios/chrome/browser/payments/payment_request_test_util.h"
-#import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
-#import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
-#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
-#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
-#import "ios/chrome/browser/ui/collection_view/cells/test_utils.h"
-#import "ios/chrome/browser/ui/collection_view/collection_view_controller_test.h"
-#import "ios/chrome/browser/ui/payments/cells/accepted_payment_methods_item.h"
-#import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
-#import "ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_consumer.h"
-#import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
-#include "ios/web/public/payments/payment_request.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface TestCreditCardEditViewControllerMediator
-    : NSObject<CreditCardEditViewControllerDataSource>
-
-@property(nonatomic, weak) id<PaymentRequestEditConsumer> consumer;
-
-@end
-
-@implementation TestCreditCardEditViewControllerMediator
-
-@synthesize state = _state;
-@synthesize consumer = _consumer;
-
-- (CollectionViewItem*)headerItem {
-  return [[PaymentMethodItem alloc] init];
-}
-
-- (BOOL)shouldHideBackgroundForHeaderItem {
-  return NO;
-}
-
-- (void)setConsumer:(id<PaymentRequestEditConsumer>)consumer {
-  _consumer = consumer;
-  [self.consumer setEditorFields:@[
-    [[EditorField alloc] initWithAutofillUIType:AutofillUITypeCreditCardNumber
-                                      fieldType:EditorFieldTypeTextField
-                                          label:@"Credit Card Number"
-                                          value:@"4111111111111111" /* Visa */
-                                       required:YES],
-    [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeCreditCardHolderFullName
-                     fieldType:EditorFieldTypeTextField
-                         label:@"Cardholder Name"
-                         value:@"John Doe"
-                      required:YES],
-    [[EditorField alloc] initWithAutofillUIType:AutofillUITypeCreditCardExpMonth
-                                      fieldType:EditorFieldTypeTextField
-                                          label:@"Expiration Month"
-                                          value:@"12"
-                                       required:YES],
-    [[EditorField alloc] initWithAutofillUIType:AutofillUITypeCreditCardExpYear
-                                      fieldType:EditorFieldTypeTextField
-                                          label:@"Expiration Year"
-                                          value:@"2090"
-                                       required:YES],
-    [[EditorField alloc]
-        initWithAutofillUIType:AutofillUITypeCreditCardBillingAddress
-                     fieldType:EditorFieldTypeSelector
-                         label:@"Billing Address"
-                         value:@"12345"
-                      required:YES],
-  ]];
-}
-
-- (UIImage*)cardTypeIconFromCardNumber:(NSString*)cardNumber {
-  return nil;
-}
-
-@end
-
-class PaymentRequestCreditCardEditViewControllerTest
-    : public CollectionViewControllerTest {
- protected:
-  CollectionViewController* InstantiateController() override {
-    CreditCardEditViewController* viewController =
-        [[CreditCardEditViewController alloc] init];
-    mediator_ = [[TestCreditCardEditViewControllerMediator alloc] init];
-    [mediator_ setConsumer:viewController];
-    [viewController setDataSource:mediator_];
-    return viewController;
-  }
-
-  CreditCardEditViewController* GetCreditCardEditViewController() {
-    return base::mac::ObjCCastStrict<CreditCardEditViewController>(
-        controller());
-  }
-
-  TestCreditCardEditViewControllerMediator* mediator_ = nil;
-};
-
-// Tests that the correct number of items are displayed after loading the model.
-TEST_F(PaymentRequestCreditCardEditViewControllerTest, TestModel) {
-  CreateController();
-  CheckController();
-
-  [mediator_ setState:EditViewControllerStateEdit];
-  [GetCreditCardEditViewController() loadModel];
-
-  // There is one section containing the credit card type icons for the accepted
-  // payment methods. In addition to that, there is one section for every field
-  // (there are five form fields in total), and finally one for the footer.
-  ASSERT_EQ(7, NumberOfSections());
-
-  // The server card summary section is the first section and has one item of
-  // the type PaymentMethodItem.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(0)));
-  id item = GetCollectionViewItem(0, 0);
-  EXPECT_TRUE([item isMemberOfClass:[PaymentMethodItem class]]);
-
-  // The next four sections have only one item of the type AutofillEditItem.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
-  item = GetCollectionViewItem(1, 0);
-  EXPECT_TRUE([item isMemberOfClass:[AutofillEditItem class]]);
-
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(2)));
-  item = GetCollectionViewItem(2, 0);
-  EXPECT_TRUE([item isMemberOfClass:[AutofillEditItem class]]);
-
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
-  item = GetCollectionViewItem(3, 0);
-  EXPECT_TRUE([item isMemberOfClass:[AutofillEditItem class]]);
-
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(4)));
-  item = GetCollectionViewItem(4, 0);
-  EXPECT_TRUE([item isMemberOfClass:[AutofillEditItem class]]);
-
-  // The billing address section contains one item which is of the type
-  // PaymentsSelectorEditItem.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(5)));
-  item = GetCollectionViewItem(5, 0);
-  EXPECT_TRUE([item isMemberOfClass:[PaymentsSelectorEditItem class]]);
-  PaymentsSelectorEditItem* billing_address_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
-            billing_address_item.accessoryType);
-
-  // The footer section contains one item which is of the type
-  // CollectionViewFooterItem.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(6)));
-  item = GetCollectionViewItem(6, 0);
-  EXPECT_TRUE([item isMemberOfClass:[CollectionViewFooterItem class]]);
-}
-
-// Tests that the correct number of items are displayed after loading the model,
-// when creating a new credit card.
-TEST_F(PaymentRequestCreditCardEditViewControllerTest,
-       TestModelCreateNewCreditCard) {
-  CreateController();
-  CheckController();
-
-  [mediator_ setState:EditViewControllerStateCreate];
-  [GetCreditCardEditViewController() loadModel];
-
-  // There is an extra section containing a switch that allows the user to save
-  // the credit card locally.
-  ASSERT_EQ(8, NumberOfSections());
-
-  // The switch section is the last section before the footer and has one item
-  // of the type CollectionViewSwitchItem. The switch is on by defualt.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(6)));
-  id item = GetCollectionViewItem(6, 0);
-  EXPECT_TRUE([item isMemberOfClass:[CollectionViewSwitchItem class]]);
-  CollectionViewSwitchItem* switch_item = item;
-  EXPECT_EQ(YES, [switch_item isOn]);
-}
diff --git a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h
deleted file mode 100644
index beed7be..0000000
--- a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_INTERNAL_H_
-#define IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_INTERNAL_H_
-
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller_actions.h"
-
-// Use these as the starting values for section identifier and item type enums
-// in subclasses. These values are chosen to prevent overlapping with the
-// section identifier and item type enums of this class.
-const NSInteger kSectionIdentifierEnumStart = kSectionIdentifierEnumZero + 20;
-const NSInteger kItemTypeEnumStart = kItemTypeEnumZero + 100;
-
-// Internal API for subclasses of PaymentRequestEditViewController.
-@interface PaymentRequestEditViewController (
-    Internal)<PaymentRequestEditViewControllerActions>
-
-// Validates each field. If there is a validation error, displays an error
-// message item in the same section as the field and returns NO. Otherwise
-// removes the error message item in that section if one exists and sets the
-// value on the field. Returns YES if all the fields are validated successfully.
-- (BOOL)validateForm;
-
-// Called after the editor field items have been added to the the collection
-// view model. Subclasses override this method to add items after the editor
-// fields.
-- (void)loadFooterItems;
-
-// Returns the index path for the cell associated with the currently focused
-// text field.
-- (NSIndexPath*)indexPathForCurrentTextField;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_INTERNAL_H_
diff --git a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h
index bc1c271..4352f8e 100644
--- a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h
+++ b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.h
@@ -26,6 +26,16 @@
             (PaymentRequestEditViewController*)controller
                           didSelectField:(EditorField*)field;
 
+// Notifies the delegate that the user has finished editing the editor fields.
+- (void)paymentRequestEditViewController:
+            (PaymentRequestEditViewController*)controller
+                  didFinishEditingFields:(NSArray<EditorField*>*)fields;
+
+// Notifies the delegate that the user has chosen to discard entries in the
+// editor fields and return to the previous screen.
+- (void)paymentRequestEditViewControllerDidCancel:
+    (PaymentRequestEditViewController*)controller;
+
 @end
 
 // Validator protocol for PaymentRequestEditViewController.
diff --git a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.mm b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.mm
index 0e58eb9..9539bc97 100644
--- a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller.mm
@@ -13,10 +13,11 @@
 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collection_view_controller.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
 #import "ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.h"
 #import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
-#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller+internal.h"
+#import "ios/chrome/browser/ui/payments/payment_request_edit_view_controller_actions.h"
 #import "ios/chrome/browser/ui/payments/payment_request_editor_field.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_theme_resources.h"
@@ -52,6 +53,20 @@
   return nil;
 }
 
+CollectionViewSwitchCell* CollectionViewSwitchCellForSwitchField(
+    UISwitch* switchField) {
+  for (UIView* view = switchField; view; view = [view superview]) {
+    CollectionViewSwitchCell* cell =
+        base::mac::ObjCCast<CollectionViewSwitchCell>(view);
+    if (cell)
+      return cell;
+  }
+
+  // There should be a cell associated with this switch field.
+  NOTREACHED();
+  return nil;
+}
+
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierHeader = kSectionIdentifierEnumZero,
   SectionIdentifierFooter,
@@ -63,22 +78,25 @@
   ItemTypeFooter,
   ItemTypeTextField,      // This is a repeated item type.
   ItemTypeSelectorField,  // This is a repeated item type.
+  ItemTypeSwitchField,    // This is a repeated item type.
   ItemTypeErrorMessage,   // This is a repeated item type.
 };
 
 }  // namespace
 
-@interface PaymentRequestEditViewController ()<AutofillEditAccessoryDelegate,
-                                               UITextFieldDelegate,
-                                               UIPickerViewDataSource,
-                                               UIPickerViewDelegate> {
+@interface PaymentRequestEditViewController ()<
+    AutofillEditAccessoryDelegate,
+    PaymentRequestEditViewControllerActions,
+    UIPickerViewDataSource,
+    UIPickerViewDelegate,
+    UITextFieldDelegate> {
   // The currently focused cell. May be nil.
   __weak AutofillEditCell* _currentEditingCell;
 
   AutofillEditAccessoryView* _accessoryView;
 }
 
-// The map of autofill types to the fields definitions for the editor.
+// The map of section identifiers to the fields definitions for the editor.
 @property(nonatomic, strong)
     NSMutableDictionary<NSNumber*, EditorField*>* fieldsMap;
 
@@ -112,6 +130,16 @@
 - (void)addOrRemoveErrorMessage:(NSString*)errorMessage
         inSectionWithIdentifier:(NSInteger)sectionIdentifier;
 
+// Validates each field. If there is a validation error, displays an error
+// message item in the same section as the field and returns NO. Otherwise
+// removes the error message item in that section if one exists and sets the
+// value on the field. Returns YES if all the fields are validated successfully.
+- (BOOL)validateForm;
+
+// Returns the index path for the cell associated with the currently focused
+// text field.
+- (NSIndexPath*)indexPathForCurrentTextField;
+
 @end
 
 @implementation PaymentRequestEditViewController
@@ -200,6 +228,9 @@
     [model addItem:headerItem toSectionWithIdentifier:SectionIdentifierHeader];
   }
 
+  self.fieldsMap =
+      [[NSMutableDictionary alloc] initWithCapacity:self.fields.count];
+
   // Iterate over the fields and add the respective sections and items.
   [self.fields enumerateObjectsUsingBlock:^(EditorField* field,
                                             NSUInteger index, BOOL* stop) {
@@ -214,6 +245,7 @@
         item.textFieldValue = field.value;
         item.required = field.isRequired;
         item.autofillUIType = field.autofillUIType;
+        item.identifyingIcon = [_dataSource iconIdentifyingEditorField:field];
         [model addItem:item toSectionWithIdentifier:sectionIdentifier];
         field.item = item;
 
@@ -231,14 +263,29 @@
         field.item = item;
         break;
       }
+      case EditorFieldTypeSwitch: {
+        CollectionViewSwitchItem* item =
+            [[CollectionViewSwitchItem alloc] initWithType:ItemTypeSwitchField];
+        item.text = field.label;
+        item.on = [field.value boolValue];
+        [model addItem:item toSectionWithIdentifier:sectionIdentifier];
+        field.item = item;
+        break;
+      }
       default:
         NOTREACHED();
     }
 
     field.sectionIdentifier = sectionIdentifier;
+    NSNumber* key = [NSNumber numberWithInt:sectionIdentifier];
+    [self.fieldsMap setObject:field forKey:key];
   }];
 
-  [self loadFooterItems];
+  [model addSectionWithIdentifier:SectionIdentifierFooter];
+  CollectionViewFooterItem* footerItem =
+      [[CollectionViewFooterItem alloc] initWithType:ItemTypeFooter];
+  footerItem.text = l10n_util::GetNSString(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE);
+  [model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter];
 }
 
 - (void)viewDidLoad {
@@ -257,13 +304,6 @@
 
 - (void)setEditorFields:(NSArray<EditorField*>*)fields {
   self.fields = fields;
-  self.fieldsMap = [[NSMutableDictionary alloc] initWithCapacity:fields.count];
-  // Iterate over the fields and populate the map.
-  [self.fields enumerateObjectsUsingBlock:^(EditorField* field,
-                                            NSUInteger index, BOOL* stop) {
-    NSNumber* key = [NSNumber numberWithInt:field.autofillUIType];
-    [self.fieldsMap setObject:field forKey:key];
-  }];
 }
 
 - (void)setOptions:(NSArray<NSString*>*)options
@@ -304,22 +344,18 @@
 - (void)textFieldDidEndEditing:(UITextField*)textField {
   DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField));
 
-  CollectionViewModel* model = self.collectionViewModel;
-
   NSIndexPath* indexPath = [self indexPathForCurrentTextField];
-  AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>(
-      [model itemAtIndexPath:indexPath]);
+  NSInteger sectionIdentifier = [self.collectionViewModel
+      sectionIdentifierForSection:[indexPath section]];
 
-  // Find and validate the respective editor field.
-  NSNumber* key = [NSNumber numberWithInt:item.autofillUIType];
+  // Find the respective editor field, update its value, and validate it.
+  NSNumber* key = [NSNumber numberWithInt:sectionIdentifier];
   EditorField* field = self.fieldsMap[key];
   DCHECK(field);
   field.value = textField.text;
   NSString* errorMessage =
       [_validatorDelegate paymentRequestEditViewController:self
                                              validateField:field];
-  NSInteger sectionIdentifier =
-      [model sectionIdentifierForSection:[indexPath section]];
   [self addOrRemoveErrorMessage:errorMessage
         inSectionWithIdentifier:sectionIdentifier];
 
@@ -338,6 +374,44 @@
   return NO;
 }
 
+// This method is called as the text is being typed in, pasted, or deleted. Asks
+// the delegate if the text should be changed. Should always return YES. During
+// typing/pasting text, |newText| contains one or more new characters. When user
+// deletes text, |newText| is empty. |range| is the range of characters to be
+// replaced.
+- (BOOL)textField:(UITextField*)textField
+    shouldChangeCharactersInRange:(NSRange)range
+                replacementString:(NSString*)newText {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField));
+
+  NSIndexPath* indexPath = [self indexPathForCurrentTextField];
+  NSInteger sectionIdentifier =
+      [model sectionIdentifierForSection:[indexPath section]];
+  AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>(
+      [model itemAtIndexPath:indexPath]);
+
+  // Find the respective editor field and update its value.
+  NSNumber* key = [NSNumber numberWithInt:sectionIdentifier];
+  EditorField* field = self.fieldsMap[key];
+  DCHECK(field);
+  // Obtain the text being typed.
+  NSString* updatedText =
+      [textField.text stringByReplacingCharactersInRange:range
+                                              withString:newText];
+  field.value = updatedText;
+
+  // Get the icon that identifies the field value and reload the cell if the
+  // icon changes.
+  UIImage* oldIcon = item.identifyingIcon;
+  item.identifyingIcon = [_dataSource iconIdentifyingEditorField:field];
+  if (item.identifyingIcon != oldIcon)
+    [self reconfigureCellsForItems:@[ item ]];
+
+  return YES;
+}
+
 #pragma mark - AutofillEditAccessoryDelegate
 
 - (void)nextPressed {
@@ -414,6 +488,14 @@
           [[MDCPalette cr_bluePalette] tint600];
       break;
     }
+    case ItemTypeSwitchField: {
+      CollectionViewSwitchCell* switchCell =
+          base::mac::ObjCCastStrict<CollectionViewSwitchCell>(cell);
+      [switchCell.switchView addTarget:self
+                                action:@selector(switchToggled:)
+                      forControlEvents:UIControlEventValueChanged];
+      break;
+    }
     case ItemTypeErrorMessage: {
       PaymentsTextCell* errorMessageCell =
           base::mac::ObjCCastStrict<PaymentsTextCell>(cell);
@@ -471,6 +553,7 @@
     case ItemTypeHeader:
     case ItemTypeFooter:
     case ItemTypeTextField:
+    case ItemTypeSwitchField:
     case ItemTypeErrorMessage:
       return [MDCCollectionViewCell
           cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
@@ -490,6 +573,8 @@
     case ItemTypeHeader:
     case ItemTypeFooter:
     case ItemTypeErrorMessage:
+    case ItemTypeTextField:
+    case ItemTypeSwitchField:
       return YES;
     default:
       return NO;
@@ -582,20 +667,6 @@
   }
 }
 
-#pragma mark - Keyboard handling
-
-- (void)keyboardDidShow {
-  [self.collectionView
-      scrollToItemAtIndexPath:[self.collectionView
-                                  indexPathForCell:_currentEditingCell]
-             atScrollPosition:UICollectionViewScrollPositionCenteredVertically
-                     animated:YES];
-}
-
-@end
-
-@implementation PaymentRequestEditViewController (Internal)
-
 - (BOOL)validateForm {
   for (EditorField* field in self.fields) {
     NSString* errorMessage =
@@ -609,16 +680,6 @@
   return YES;
 }
 
-- (void)loadFooterItems {
-  CollectionViewModel* model = self.collectionViewModel;
-
-  [model addSectionWithIdentifier:SectionIdentifierFooter];
-  CollectionViewFooterItem* footerItem =
-      [[CollectionViewFooterItem alloc] initWithType:ItemTypeFooter];
-  footerItem.text = l10n_util::GetNSString(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE);
-  [model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter];
-}
-
 - (NSIndexPath*)indexPathForCurrentTextField {
   DCHECK(_currentEditingCell);
   NSIndexPath* indexPath =
@@ -627,13 +688,48 @@
   return indexPath;
 }
 
+#pragma mark - Keyboard handling
+
+- (void)keyboardDidShow {
+  [self.collectionView
+      scrollToItemAtIndexPath:[self.collectionView
+                                  indexPathForCell:_currentEditingCell]
+             atScrollPosition:UICollectionViewScrollPositionCenteredVertically
+                     animated:YES];
+}
+
+#pragma mark Switch Actions
+
+- (void)switchToggled:(UISwitch*)sender {
+  CollectionViewSwitchCell* switchCell =
+      CollectionViewSwitchCellForSwitchField(sender);
+  NSIndexPath* indexPath = [[self collectionView] indexPathForCell:switchCell];
+  DCHECK(indexPath);
+
+  NSInteger sectionIdentifier = [self.collectionViewModel
+      sectionIdentifierForSection:[indexPath section]];
+
+  // Update editor field's value.
+  NSNumber* key = [NSNumber numberWithInt:sectionIdentifier];
+  EditorField* field = self.fieldsMap[key];
+  DCHECK(field);
+  field.value = [sender isOn] ? @"YES" : @"NO";
+}
+
 #pragma mark - PaymentRequestEditViewControllerActions methods
 
 - (void)onCancel {
+  [self.delegate paymentRequestEditViewControllerDidCancel:self];
 }
 
 - (void)onDone {
   [_currentEditingCell.textField resignFirstResponder];
+
+  if (![self validateForm])
+    return;
+
+  [self.delegate paymentRequestEditViewController:self
+                           didFinishEditingFields:self.fields];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h
index 36ffd10..d3bc084 100644
--- a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h
+++ b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_data_source.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
 #define IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
 
-#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 @class EditorField;
 @class CollectionViewItem;
@@ -30,6 +30,9 @@
 // Returns whether the header item should hide its background.
 - (BOOL)shouldHideBackgroundForHeaderItem;
 
+// Returns an icon that identifies |field| or its current value. May be nil.
+- (UIImage*)iconIdentifyingEditorField:(EditorField*)field;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_EDIT_VIEW_CONTROLLER_DATA_SOURCE_H_
diff --git a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_unittest.mm b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_unittest.mm
index f35e3df8..21524ff4 100644
--- a/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_edit_view_controller_unittest.mm
@@ -9,6 +9,7 @@
 #import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
 #import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_switch_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller_test.h"
 #import "ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.h"
@@ -41,6 +42,10 @@
   return NO;
 }
 
+- (UIImage*)iconIdentifyingEditorField:(EditorField*)field {
+  return nil;
+}
+
 - (void)setConsumer:(id<PaymentRequestEditConsumer>)consumer {
   _consumer = consumer;
   [self.consumer setEditorFields:@[
@@ -54,6 +59,12 @@
                                           label:@""
                                           value:@""
                                        required:YES],
+    [[EditorField alloc]
+        initWithAutofillUIType:AutofillUITypeCreditCardSaveToChrome
+                     fieldType:EditorFieldTypeSwitch
+                         label:@""
+                         value:@"YES"
+                      required:YES],
   ]];
 }
 
@@ -88,9 +99,9 @@
   [GetPaymentRequestEditViewController() loadModel];
 
   // There is one section containing the header item, In addition to that, there
-  // is one section for every form field (there are two fields in total) and one
-  // for the footer.
-  ASSERT_EQ(4, NumberOfSections());
+  // is one section for every form field (there are three fields in total) and
+  // one for the footer.
+  ASSERT_EQ(5, NumberOfSections());
 
   // The header section is the first section and has one item of the type
   // CollectionViewTextItem.
@@ -108,9 +119,14 @@
   item = GetCollectionViewItem(2, 0);
   EXPECT_TRUE([item isMemberOfClass:[PaymentsSelectorEditItem class]]);
 
+  // The next section has one item of the type CollectionViewSwitchItem.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
+  item = GetCollectionViewItem(3, 0);
+  EXPECT_TRUE([item isMemberOfClass:[CollectionViewSwitchItem class]]);
+
   // The footer section contains one item which is of the type
   // CollectionViewFooterItem.
   ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
-  item = GetCollectionViewItem(3, 0);
+  item = GetCollectionViewItem(4, 0);
   EXPECT_TRUE([item isMemberOfClass:[CollectionViewFooterItem class]]);
 }
diff --git a/ios/chrome/browser/ui/payments/payment_request_editor_field.h b/ios/chrome/browser/ui/payments/payment_request_editor_field.h
index 8f54213..27c0c6a 100644
--- a/ios/chrome/browser/ui/payments/payment_request_editor_field.h
+++ b/ios/chrome/browser/ui/payments/payment_request_editor_field.h
@@ -15,6 +15,7 @@
 typedef NS_ENUM(NSInteger, EditorFieldType) {
   EditorFieldTypeTextField,
   EditorFieldTypeSelector,
+  EditorFieldTypeSwitch,
 };
 
 // Field definition for an editor field. Used for building the UI and
diff --git a/ios/showcase/payments/sc_payments_editor_coordinator.mm b/ios/showcase/payments/sc_payments_editor_coordinator.mm
index 1b5621e..7bd18082 100644
--- a/ios/showcase/payments/sc_payments_editor_coordinator.mm
+++ b/ios/showcase/payments/sc_payments_editor_coordinator.mm
@@ -102,6 +102,10 @@
   return NO;
 }
 
+- (UIImage*)iconIdentifyingEditorField:(EditorField*)field {
+  return nil;
+}
+
 #pragma mark - PaymentRequestEditViewControllerValidator
 
 - (NSString*)paymentRequestEditViewController:
diff --git a/media/cast/test/utility/standalone_cast_environment.cc b/media/cast/test/utility/standalone_cast_environment.cc
index 272f04d..9a2b4b2 100644
--- a/media/cast/test/utility/standalone_cast_environment.cc
+++ b/media/cast/test/utility/standalone_cast_environment.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_restrictions.h"
 #include "base/time/default_tick_clock.h"
 
 namespace media {
@@ -40,6 +41,9 @@
 
 void StandaloneCastEnvironment::Shutdown() {
   CHECK(CalledOnValidThread());
+
+  base::ThreadRestrictions::ScopedAllowIO
+      because_i_brought_you_into_this_world_and_i_am_gonna_take_you_out;
   main_thread_.Stop();
   audio_thread_.Stop();
   video_thread_.Stop();
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index 9ae2be7..9a04f5b 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -217,6 +217,8 @@
     if (enable_media_codec_video_decoder) {
       assert(mojo_media_host == "gpu", "MCVD requires the CDM")
       sources += [
+        "android/codec_wrapper.cc",
+        "android/codec_wrapper.h",
         "android/media_codec_video_decoder.cc",
         "android/media_codec_video_decoder.h",
       ]
@@ -460,7 +462,10 @@
       "surface_texture_gl_owner_unittest.cc",
     ]
     if (enable_media_codec_video_decoder) {
-      sources += [ "android/media_codec_video_decoder_unittest.cc" ]
+      sources += [
+        "android/codec_wrapper_unittest.cc",
+        "android/media_codec_video_decoder_unittest.cc",
+      ]
     }
     deps = [
       ":gpu",
diff --git a/media/gpu/android/codec_wrapper.cc b/media/gpu/android/codec_wrapper.cc
new file mode 100644
index 0000000..c738a72
--- /dev/null
+++ b/media/gpu/android/codec_wrapper.cc
@@ -0,0 +1,344 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/android/codec_wrapper.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "media/base/android/media_codec_util.h"
+
+namespace media {
+
+// CodecWrapperImpl is the implementation for CodecWrapper but is separate so
+// we can keep its refcounting as an implementation detail. CodecWrapper and
+// CodecOutputBuffer are the only two things that hold references to it.
+class CodecWrapperImpl : public base::RefCountedThreadSafe<CodecWrapperImpl> {
+ public:
+  CodecWrapperImpl(std::unique_ptr<MediaCodecBridge> codec);
+
+  std::unique_ptr<MediaCodecBridge> TakeCodec();
+  bool HasValidCodecOutputBuffers() const;
+  void DiscardCodecOutputBuffers();
+  bool SupportsFlush() const;
+  bool Flush();
+  MediaCodecStatus QueueInputBuffer(int index,
+                                    const uint8_t* data,
+                                    size_t data_size,
+                                    base::TimeDelta presentation_time);
+  MediaCodecStatus QueueSecureInputBuffer(
+      int index,
+      const uint8_t* data,
+      size_t data_size,
+      const std::string& key_id,
+      const std::string& iv,
+      const std::vector<SubsampleEntry>& subsamples,
+      const EncryptionScheme& encryption_scheme,
+      base::TimeDelta presentation_time);
+  void QueueEOS(int input_buffer_index);
+  MediaCodecStatus DequeueInputBuffer(base::TimeDelta timeout, int* index);
+  MediaCodecStatus DequeueOutputBuffer(
+      base::TimeDelta timeout,
+      base::TimeDelta* presentation_time,
+      bool* end_of_stream,
+      std::unique_ptr<CodecOutputBuffer>* codec_buffer);
+  bool SetSurface(jobject surface);
+
+  // Releases the codec buffer and optionally renders it. This is a noop if
+  // the codec buffer is not valid (i.e., there's no race between checking its
+  // validity and releasing it). Can be called on any thread. Returns true if
+  // the buffer was released.
+  bool ReleaseCodecOutputBuffer(int64_t id, bool render);
+
+ private:
+  friend base::RefCountedThreadSafe<CodecWrapperImpl>;
+  ~CodecWrapperImpl();
+
+  void DiscardCodecOutputBuffers_Locked();
+
+  // |lock_| protects access to all member variables.
+  mutable base::Lock lock_;
+  std::unique_ptr<MediaCodecBridge> codec_;
+  bool in_error_state_;
+
+  // Buffer ids are unique for a given CodecWrapper and map to MediaCodec buffer
+  // indices.
+  int64_t next_buffer_id_;
+  base::flat_map<int64_t, int> buffer_ids_;
+
+  // The current output size. Updated when DequeueOutputBuffer() reports
+  // OUTPUT_FORMAT_CHANGED.
+  gfx::Size size_;
+
+  DISALLOW_COPY_AND_ASSIGN(CodecWrapperImpl);
+};
+
+CodecOutputBuffer::CodecOutputBuffer(scoped_refptr<CodecWrapperImpl> codec,
+                                     int64_t id,
+                                     gfx::Size size)
+    : codec_(std::move(codec)), id_(id), size_(size) {}
+
+CodecOutputBuffer::~CodecOutputBuffer() {
+  codec_->ReleaseCodecOutputBuffer(id_, false);
+}
+
+bool CodecOutputBuffer::ReleaseToSurface() {
+  return codec_->ReleaseCodecOutputBuffer(id_, true);
+}
+
+CodecWrapperImpl::CodecWrapperImpl(std::unique_ptr<MediaCodecBridge> codec)
+    : codec_(std::move(codec)), in_error_state_(false), next_buffer_id_(0) {}
+
+CodecWrapperImpl::~CodecWrapperImpl() = default;
+
+std::unique_ptr<MediaCodecBridge> CodecWrapperImpl::TakeCodec() {
+  base::AutoLock l(lock_);
+  if (!codec_)
+    return nullptr;
+  DiscardCodecOutputBuffers_Locked();
+  return std::move(codec_);
+}
+
+bool CodecWrapperImpl::HasValidCodecOutputBuffers() const {
+  base::AutoLock l(lock_);
+  return !buffer_ids_.empty();
+}
+
+void CodecWrapperImpl::DiscardCodecOutputBuffers() {
+  base::AutoLock l(lock_);
+  DiscardCodecOutputBuffers_Locked();
+}
+
+void CodecWrapperImpl::DiscardCodecOutputBuffers_Locked() {
+  lock_.AssertAcquired();
+  for (auto& kv : buffer_ids_)
+    codec_->ReleaseOutputBuffer(kv.second, false);
+  buffer_ids_.clear();
+}
+
+bool CodecWrapperImpl::SupportsFlush() const {
+  base::AutoLock l(lock_);
+  return !MediaCodecUtil::CodecNeedsFlushWorkaround(codec_.get());
+}
+
+bool CodecWrapperImpl::Flush() {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+
+  // Dequeued output buffers are invalidated by flushing.
+  buffer_ids_.clear();
+  auto status = codec_->Flush();
+  if (status == MEDIA_CODEC_ERROR) {
+    in_error_state_ = true;
+    return false;
+  }
+  return true;
+}
+
+MediaCodecStatus CodecWrapperImpl::QueueInputBuffer(
+    int index,
+    const uint8_t* data,
+    size_t data_size,
+    base::TimeDelta presentation_time) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+
+  auto status =
+      codec_->QueueInputBuffer(index, data, data_size, presentation_time);
+  if (status == MEDIA_CODEC_ERROR)
+    in_error_state_ = true;
+  return status;
+}
+
+MediaCodecStatus CodecWrapperImpl::QueueSecureInputBuffer(
+    int index,
+    const uint8_t* data,
+    size_t data_size,
+    const std::string& key_id,
+    const std::string& iv,
+    const std::vector<SubsampleEntry>& subsamples,
+    const EncryptionScheme& encryption_scheme,
+    base::TimeDelta presentation_time) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+
+  auto status = codec_->QueueSecureInputBuffer(
+      index, data, data_size, key_id, iv, subsamples, encryption_scheme,
+      presentation_time);
+  if (status == MEDIA_CODEC_ERROR)
+    in_error_state_ = true;
+  return status;
+}
+
+void CodecWrapperImpl::QueueEOS(int input_buffer_index) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+  codec_->QueueEOS(input_buffer_index);
+}
+
+MediaCodecStatus CodecWrapperImpl::DequeueInputBuffer(base::TimeDelta timeout,
+                                                      int* index) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+  auto status = codec_->DequeueInputBuffer(timeout, index);
+  if (status == MEDIA_CODEC_ERROR)
+    in_error_state_ = true;
+  return status;
+}
+
+MediaCodecStatus CodecWrapperImpl::DequeueOutputBuffer(
+    base::TimeDelta timeout,
+    base::TimeDelta* presentation_time,
+    bool* end_of_stream,
+    std::unique_ptr<CodecOutputBuffer>* codec_buffer) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+  // If |*codec_buffer| were not null, deleting it may deadlock when it
+  // tries to release itself.
+  DCHECK(!*codec_buffer);
+
+  // Dequeue in a loop so we can avoid propagating the uninteresting
+  // OUTPUT_FORMAT_CHANGED and OUTPUT_BUFFERS_CHANGED statuses to our caller.
+  for (int attempt = 0; attempt < 3; ++attempt) {
+    int index = -1;
+    size_t unused_offset = 0;
+    size_t unused_size = 0;
+    bool* unused_key_frame = nullptr;
+    auto status = codec_->DequeueOutputBuffer(timeout, &index, &unused_offset,
+                                              &unused_size, presentation_time,
+                                              end_of_stream, unused_key_frame);
+    switch (status) {
+      case MEDIA_CODEC_OK: {
+        int64_t buffer_id = next_buffer_id_++;
+        buffer_ids_[buffer_id] = index;
+        *codec_buffer =
+            base::WrapUnique(new CodecOutputBuffer(this, buffer_id, size_));
+        return status;
+      }
+      case MEDIA_CODEC_ERROR: {
+        in_error_state_ = true;
+        return status;
+      }
+      case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: {
+        // An OUTPUT_FORMAT_CHANGED is not reported after Flush() if the frame
+        // size does not change.
+        if (codec_->GetOutputSize(&size_) == MEDIA_CODEC_ERROR) {
+          in_error_state_ = true;
+          return MEDIA_CODEC_ERROR;
+        }
+        continue;
+      }
+      case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
+        continue;
+      default:
+        return status;
+    }
+  }
+
+  in_error_state_ = true;
+  return MEDIA_CODEC_ERROR;
+}
+
+bool CodecWrapperImpl::SetSurface(jobject surface) {
+  base::AutoLock l(lock_);
+  DCHECK(codec_ && !in_error_state_);
+
+  bool status = codec_->SetSurface(surface);
+  if (!status)
+    in_error_state_ = true;
+  return status;
+}
+
+bool CodecWrapperImpl::ReleaseCodecOutputBuffer(int64_t id, bool render) {
+  base::AutoLock l(lock_);
+  if (!codec_ || in_error_state_)
+    return false;
+
+  auto it = buffer_ids_.find(id);
+  if (it == buffer_ids_.end())
+    return false;
+  int index = it->second;
+  buffer_ids_.erase(it);
+  codec_->ReleaseOutputBuffer(index, render);
+  return true;
+}
+
+CodecWrapper::CodecWrapper(std::unique_ptr<MediaCodecBridge> codec)
+    : impl_(new CodecWrapperImpl(std::move(codec))) {}
+
+CodecWrapper::~CodecWrapper() {
+  // The codec must have already been taken.
+  DCHECK(!impl_->TakeCodec());
+}
+
+std::unique_ptr<MediaCodecBridge> CodecWrapper::TakeCodec() {
+  return impl_->TakeCodec();
+}
+
+bool CodecWrapper::HasValidCodecOutputBuffers() const {
+  return impl_->HasValidCodecOutputBuffers();
+}
+
+void CodecWrapper::DiscardCodecOutputBuffers() {
+  impl_->DiscardCodecOutputBuffers();
+}
+
+bool CodecWrapper::SupportsFlush() const {
+  return impl_->SupportsFlush();
+}
+
+bool CodecWrapper::Flush() {
+  return impl_->Flush();
+}
+
+MediaCodecStatus CodecWrapper::QueueInputBuffer(
+    int index,
+    const uint8_t* data,
+    size_t data_size,
+    base::TimeDelta presentation_time) {
+  return impl_->QueueInputBuffer(index, data, data_size, presentation_time);
+}
+
+MediaCodecStatus CodecWrapper::QueueSecureInputBuffer(
+    int index,
+    const uint8_t* data,
+    size_t data_size,
+    const std::string& key_id,
+    const std::string& iv,
+    const std::vector<SubsampleEntry>& subsamples,
+    const EncryptionScheme& encryption_scheme,
+    base::TimeDelta presentation_time) {
+  return impl_->QueueSecureInputBuffer(index, data, data_size, key_id, iv,
+                                       subsamples, encryption_scheme,
+                                       presentation_time);
+}
+
+void CodecWrapper::QueueEOS(int input_buffer_index) {
+  impl_->QueueEOS(input_buffer_index);
+}
+
+MediaCodecStatus CodecWrapper::DequeueInputBuffer(base::TimeDelta timeout,
+                                                  int* index) {
+  return impl_->DequeueInputBuffer(timeout, index);
+}
+
+MediaCodecStatus CodecWrapper::DequeueOutputBuffer(
+    base::TimeDelta timeout,
+    base::TimeDelta* presentation_time,
+    bool* end_of_stream,
+    std::unique_ptr<CodecOutputBuffer>* codec_buffer) {
+  return impl_->DequeueOutputBuffer(timeout, presentation_time, end_of_stream,
+                                    codec_buffer);
+}
+
+bool CodecWrapper::SetSurface(jobject surface) {
+  return impl_->SetSurface(surface);
+}
+
+}  // namespace media
diff --git a/media/gpu/android/codec_wrapper.h b/media/gpu/android/codec_wrapper.h
new file mode 100644
index 0000000..f8a483c
--- /dev/null
+++ b/media/gpu/android/codec_wrapper.h
@@ -0,0 +1,118 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
+#define MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "media/base/android/media_codec_bridge.h"
+#include "media/gpu/media_gpu_export.h"
+#include "media/gpu/surface_texture_gl_owner.h"
+
+namespace media {
+class CodecWrapperImpl;
+
+// A MediaCodec output buffer that can be released on any thread.
+class MEDIA_GPU_EXPORT CodecOutputBuffer {
+ public:
+  // Releases the buffer without rendering it.
+  ~CodecOutputBuffer();
+
+  // Releases this buffer and renders it to the surface.
+  bool ReleaseToSurface();
+
+  // The size of the image.
+  gfx::Size size() const { return size_; }
+
+ private:
+  // Let CodecWrapperImpl call the constructor.
+  friend class CodecWrapperImpl;
+  CodecOutputBuffer(scoped_refptr<CodecWrapperImpl> codec,
+                    int64_t id,
+                    gfx::Size size);
+
+  scoped_refptr<CodecWrapperImpl> codec_;
+  int64_t id_;
+  gfx::Size size_;
+  DISALLOW_COPY_AND_ASSIGN(CodecOutputBuffer);
+};
+
+// This wraps a MediaCodecBridge and provides a pared down version of its
+// interface. It also adds the following features:
+// * It outputs CodecOutputBuffers from DequeueOutputBuffer() which can be
+//   safely rendered on any thread, and that will release their buffers on
+//   destruction. This lets us decode on one thread while rendering on another.
+// * It maintains codec specific state like whether an error has occurred.
+//
+// CodecWrapper is not threadsafe, but the CodecOutputBuffers it outputs
+// can be released on any thread.
+class MEDIA_GPU_EXPORT CodecWrapper {
+ public:
+  // |codec| should be in the state referred to by the MediaCodec docs as
+  // "Flushed", i.e., freshly configured or after a Flush() call.
+  CodecWrapper(std::unique_ptr<MediaCodecBridge> codec);
+  ~CodecWrapper();
+
+  // Takes the backing codec and discards all outstanding codec buffers. This
+  // lets you tear down the codec while there are still CodecOutputBuffers
+  // referencing |this|.
+  std::unique_ptr<MediaCodecBridge> TakeCodec();
+
+  // Whether there are any valid CodecOutputBuffers that have not been released.
+  bool HasValidCodecOutputBuffers() const;
+
+  // Releases currently dequeued codec buffers back to the codec without
+  // rendering.
+  void DiscardCodecOutputBuffers();
+
+  // Whether the codec supports Flush().
+  bool SupportsFlush() const;
+
+  // See MediaCodecBridge documentation for the following.
+  bool Flush();
+  MediaCodecStatus QueueInputBuffer(int index,
+                                    const uint8_t* data,
+                                    size_t data_size,
+                                    base::TimeDelta presentation_time);
+  MediaCodecStatus QueueSecureInputBuffer(
+      int index,
+      const uint8_t* data,
+      size_t data_size,
+      const std::string& key_id,
+      const std::string& iv,
+      const std::vector<SubsampleEntry>& subsamples,
+      const EncryptionScheme& encryption_scheme,
+      base::TimeDelta presentation_time);
+  void QueueEOS(int input_buffer_index);
+  MediaCodecStatus DequeueInputBuffer(base::TimeDelta timeout, int* index);
+
+  // Like MediaCodecBridge::DequeueOutputBuffer() but it outputs a
+  // CodecOutputBuffer instead of an index. And it's guaranteed to not return
+  // either of MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED or
+  // MEDIA_CODEC_OUTPUT_FORMAT_CHANGED. It will try to dequeue another
+  // buffer instead. |*codec_buffer| must be null.
+  MediaCodecStatus DequeueOutputBuffer(
+      base::TimeDelta timeout,
+      base::TimeDelta* presentation_time,
+      bool* end_of_stream,
+      std::unique_ptr<CodecOutputBuffer>* codec_buffer);
+  bool SetSurface(jobject surface);
+
+ private:
+  scoped_refptr<CodecWrapperImpl> impl_;
+  DISALLOW_COPY_AND_ASSIGN(CodecWrapper);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_GPU_ANDROID_CODEC_WRAPPER_H_
diff --git a/media/gpu/android/codec_wrapper_unittest.cc b/media/gpu/android/codec_wrapper_unittest.cc
new file mode 100644
index 0000000..06a9bb7
--- /dev/null
+++ b/media/gpu/android/codec_wrapper_unittest.cc
@@ -0,0 +1,204 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/android/codec_wrapper.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "media/base/android/media_codec_bridge.h"
+#include "media/base/android/mock_media_codec_bridge.h"
+#include "media/base/encryption_scheme.h"
+#include "media/base/subsample_entry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Invoke;
+using testing::Return;
+using testing::DoAll;
+using testing::SetArgPointee;
+using testing::NiceMock;
+using testing::_;
+
+namespace media {
+
+class CodecWrapperTest : public testing::Test {
+ public:
+  CodecWrapperTest() {
+    auto codec = base::MakeUnique<NiceMock<MockMediaCodecBridge>>();
+    codec_ = codec.get();
+    wrapper_ = base::MakeUnique<CodecWrapper>(std::move(codec));
+    ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+        .WillByDefault(Return(MEDIA_CODEC_OK));
+  }
+
+  ~CodecWrapperTest() override {
+    // ~CodecWrapper asserts that the codec was taken.
+    wrapper_->TakeCodec();
+  }
+
+  std::unique_ptr<CodecOutputBuffer> DequeueCodecOutputBuffer() {
+    std::unique_ptr<CodecOutputBuffer> codec_buffer;
+    wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr, nullptr,
+                                  &codec_buffer);
+    return codec_buffer;
+  }
+
+  NiceMock<MockMediaCodecBridge>* codec_;
+  std::unique_ptr<CodecWrapper> wrapper_;
+};
+
+TEST_F(CodecWrapperTest, TakeCodecReturnsTheCodecFirstAndNullLater) {
+  ASSERT_EQ(wrapper_->TakeCodec().get(), codec_);
+  ASSERT_EQ(wrapper_->TakeCodec(), nullptr);
+}
+
+TEST_F(CodecWrapperTest, NoCodecOutputBufferReturnedIfDequeueFails) {
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillOnce(Return(MEDIA_CODEC_ERROR));
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  ASSERT_EQ(codec_buffer, nullptr);
+}
+
+TEST_F(CodecWrapperTest, InitiallyThereAreNoValidCodecOutputBuffers) {
+  ASSERT_FALSE(wrapper_->HasValidCodecOutputBuffers());
+}
+
+TEST_F(CodecWrapperTest, FlushInvalidatesCodecOutputBuffers) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->Flush();
+  ASSERT_FALSE(codec_buffer->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, TakingTheCodecInvalidatesCodecOutputBuffers) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->TakeCodec();
+  ASSERT_FALSE(codec_buffer->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, SetSurfaceInvalidatesCodecOutputBuffers) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->SetSurface(0);
+  ASSERT_FALSE(codec_buffer->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersAreAllInvalidatedTogether) {
+  auto codec_buffer1 = DequeueCodecOutputBuffer();
+  auto codec_buffer2 = DequeueCodecOutputBuffer();
+  wrapper_->Flush();
+  ASSERT_FALSE(codec_buffer1->ReleaseToSurface());
+  ASSERT_FALSE(codec_buffer2->ReleaseToSurface());
+  ASSERT_FALSE(wrapper_->HasValidCodecOutputBuffers());
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersAfterFlushAreValid) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->Flush();
+  codec_buffer = DequeueCodecOutputBuffer();
+  ASSERT_TRUE(codec_buffer->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBufferReleaseUsesCorrectIndex) {
+  // The second arg is the buffer index pointer.
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(42), Return(MEDIA_CODEC_OK)));
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  EXPECT_CALL(*codec_, ReleaseOutputBuffer(42, true));
+  codec_buffer->ReleaseToSurface();
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersAreInvalidatedByRelease) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  codec_buffer->ReleaseToSurface();
+  ASSERT_FALSE(codec_buffer->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersReleaseOnDestruction) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, false));
+  codec_buffer = nullptr;
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersDoNotReleaseIfAlreadyReleased) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  codec_buffer->ReleaseToSurface();
+  EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, _)).Times(0);
+  codec_buffer = nullptr;
+}
+
+TEST_F(CodecWrapperTest, ReleasingCodecOutputBuffersAfterTheCodecIsSafe) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->TakeCodec();
+  codec_buffer->ReleaseToSurface();
+}
+
+TEST_F(CodecWrapperTest, DeletingCodecOutputBuffersAfterTheCodecIsSafe) {
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  wrapper_->TakeCodec();
+  // This test ensures the destructor doesn't crash.
+  codec_buffer = nullptr;
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBufferReleaseDoesNotInvalidateOthers) {
+  auto codec_buffer1 = DequeueCodecOutputBuffer();
+  auto codec_buffer2 = DequeueCodecOutputBuffer();
+  codec_buffer1->ReleaseToSurface();
+  ASSERT_TRUE(codec_buffer2->ReleaseToSurface());
+}
+
+TEST_F(CodecWrapperTest, FormatChangedStatusIsSwallowed) {
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
+      .WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
+  std::unique_ptr<CodecOutputBuffer> codec_buffer;
+  auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
+                                              nullptr, &codec_buffer);
+  ASSERT_EQ(status, MEDIA_CODEC_TRY_AGAIN_LATER);
+}
+
+TEST_F(CodecWrapperTest, BuffersChangedStatusIsSwallowed) {
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillOnce(Return(MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED))
+      .WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
+  std::unique_ptr<CodecOutputBuffer> codec_buffer;
+  auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
+                                              nullptr, &codec_buffer);
+  ASSERT_EQ(status, MEDIA_CODEC_TRY_AGAIN_LATER);
+}
+
+TEST_F(CodecWrapperTest, MultipleFormatChangedStatusesIsAnError) {
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillRepeatedly(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED));
+  std::unique_ptr<CodecOutputBuffer> codec_buffer;
+  auto status = wrapper_->DequeueOutputBuffer(base::TimeDelta(), nullptr,
+                                              nullptr, &codec_buffer);
+  ASSERT_EQ(status, MEDIA_CODEC_ERROR);
+}
+
+TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) {
+  EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
+      .WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
+      .WillOnce(Return(MEDIA_CODEC_OK));
+  EXPECT_CALL(*codec_, GetOutputSize(_))
+      .WillOnce(
+          DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(MEDIA_CODEC_OK)));
+  auto codec_buffer = DequeueCodecOutputBuffer();
+  ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
+}
+
+#if DCHECK_IS_ON()
+TEST_F(CodecWrapperTest, CallsDcheckAfterTakingTheCodec) {
+  wrapper_->TakeCodec();
+  ASSERT_DEATH(wrapper_->Flush(), "");
+}
+
+TEST_F(CodecWrapperTest, CallsDcheckAfterAnError) {
+  EXPECT_CALL(*codec_, Flush()).WillOnce(Return(MEDIA_CODEC_ERROR));
+  wrapper_->Flush();
+  ASSERT_DEATH(wrapper_->SetSurface(0), "");
+}
+#endif
+
+}  // namespace media
diff --git a/media/test/BUILD.gn b/media/test/BUILD.gn
index 5012118..c415cfe0 100644
--- a/media/test/BUILD.gn
+++ b/media/test/BUILD.gn
@@ -166,4 +166,6 @@
     # This is done to suppress tons of log messages generated by gmock asserts.
     "close_fd_mask=1",
   ]
+
+  seed_corpus = "media/test/data/"
 }
diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn
index a68cd44..8dd8cf8 100644
--- a/mojo/edk/system/BUILD.gn
+++ b/mojo/edk/system/BUILD.gn
@@ -42,8 +42,6 @@
     "handle_table.h",
     "mapping_table.cc",
     "mapping_table.h",
-    "message_for_transit.cc",
-    "message_for_transit.h",
     "message_pipe_dispatcher.cc",
     "message_pipe_dispatcher.h",
     "node_channel.cc",
@@ -53,12 +51,12 @@
     "options_validation.h",
     "platform_handle_dispatcher.cc",
     "platform_handle_dispatcher.h",
-    "ports_message.cc",
-    "ports_message.h",
     "request_context.cc",
     "request_context.h",
     "shared_buffer_dispatcher.cc",
     "shared_buffer_dispatcher.h",
+    "user_message_impl.cc",
+    "user_message_impl.h",
     "watch.cc",
     "watch.h",
     "watcher_dispatcher.cc",
diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc
index 5561baa5..dc61172 100644
--- a/mojo/edk/system/core.cc
+++ b/mojo/edk/system/core.cc
@@ -26,13 +26,14 @@
 #include "mojo/edk/system/data_pipe_consumer_dispatcher.h"
 #include "mojo/edk/system/data_pipe_producer_dispatcher.h"
 #include "mojo/edk/system/handle_signals_state.h"
-#include "mojo/edk/system/message_for_transit.h"
 #include "mojo/edk/system/message_pipe_dispatcher.h"
 #include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/name.h"
 #include "mojo/edk/system/ports/node.h"
 #include "mojo/edk/system/request_context.h"
 #include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/user_message_impl.h"
 #include "mojo/edk/system/watcher_dispatcher.h"
 
 namespace mojo {
@@ -435,17 +436,18 @@
                               const MojoHandle* handles,
                               uint32_t num_handles,
                               MojoAllocMessageFlags flags,
-                              MojoMessageHandle* message) {
-  if (!message)
+                              MojoMessageHandle* message_handle) {
+  if (!message_handle)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
   if (num_handles == 0) {  // Fast path: no handles.
-    std::unique_ptr<MessageForTransit> msg;
-    MojoResult rv = MessageForTransit::Create(&msg, num_bytes, nullptr, 0);
+    std::unique_ptr<ports::UserMessageEvent> message;
+    MojoResult rv = UserMessageImpl::CreateEventForNewSerializedMessage(
+        num_bytes, nullptr, 0, &message);
     if (rv != MOJO_RESULT_OK)
       return rv;
 
-    *message = reinterpret_cast<MojoMessageHandle>(msg.release());
+    *message_handle = reinterpret_cast<MojoMessageHandle>(message.release());
     return MOJO_RESULT_OK;
   }
 
@@ -470,15 +472,15 @@
   }
   DCHECK_EQ(num_handles, dispatchers.size());
 
-  std::unique_ptr<MessageForTransit> msg;
-  MojoResult rv = MessageForTransit::Create(
-      &msg, num_bytes, dispatchers.data(), num_handles);
+  std::unique_ptr<ports::UserMessageEvent> message;
+  MojoResult rv = UserMessageImpl::CreateEventForNewSerializedMessage(
+      num_bytes, dispatchers.data(), num_handles, &message);
 
   {
     base::AutoLock lock(handles_lock_);
     if (rv == MOJO_RESULT_OK) {
       handles_.CompleteTransitAndClose(dispatchers);
-      *message = reinterpret_cast<MojoMessageHandle>(msg.release());
+      *message_handle = reinterpret_cast<MojoMessageHandle>(message.release());
     } else {
       handles_.CancelTransit(dispatchers);
     }
@@ -487,22 +489,26 @@
   return rv;
 }
 
-MojoResult Core::FreeMessage(MojoMessageHandle message) {
-  if (!message)
+MojoResult Core::FreeMessage(MojoMessageHandle message_handle) {
+  if (!message_handle)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
   RequestContext request_context;
-  delete reinterpret_cast<MessageForTransit*>(message);
-
+  delete reinterpret_cast<ports::UserMessageEvent*>(message_handle);
   return MOJO_RESULT_OK;
 }
 
-MojoResult Core::GetMessageBuffer(MojoMessageHandle message, void** buffer) {
-  if (!message)
+MojoResult Core::GetMessageBuffer(MojoMessageHandle message_handle,
+                                  void** buffer) {
+  if (!message_handle)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
-  *buffer = reinterpret_cast<MessageForTransit*>(message)->mutable_bytes();
+  auto* message = reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+                      ->GetMessage<UserMessageImpl>();
+  if (!message->IsSerialized())
+    return MOJO_RESULT_NOT_FOUND;
 
+  *buffer = message->user_payload();
   return MOJO_RESULT_OK;
 }
 
@@ -525,8 +531,8 @@
   ports::PortRef port0, port1;
   GetNodeController()->node()->CreatePortPair(&port0, &port1);
 
-  CHECK(message_pipe_handle0);
-  CHECK(message_pipe_handle1);
+  DCHECK(message_pipe_handle0);
+  DCHECK(message_pipe_handle1);
 
   uint64_t pipe_id = base::RandUint64();
 
@@ -575,16 +581,15 @@
 }
 
 MojoResult Core::WriteMessageNew(MojoHandle message_pipe_handle,
-                                 MojoMessageHandle message,
+                                 MojoMessageHandle message_handle,
                                  MojoWriteMessageFlags flags) {
   RequestContext request_context;
-  std::unique_ptr<MessageForTransit> message_for_transit(
-      reinterpret_cast<MessageForTransit*>(message));
+  auto message = base::WrapUnique(
+      reinterpret_cast<ports::UserMessageEvent*>(message_handle));
   auto dispatcher = GetDispatcher(message_pipe_handle);
   if (!dispatcher)
     return MOJO_RESULT_INVALID_ARGUMENT;
-
-  return dispatcher->WriteMessage(std::move(message_for_transit), flags);
+  return dispatcher->WriteMessage(std::move(message), flags);
 }
 
 MojoResult Core::ReadMessage(MojoHandle message_pipe_handle,
@@ -593,44 +598,55 @@
                              MojoHandle* handles,
                              uint32_t* num_handles,
                              MojoReadMessageFlags flags) {
-  CHECK((!num_handles || !*num_handles || handles) &&
-        (!num_bytes || !*num_bytes || bytes));
+  DCHECK((!num_handles || !*num_handles || handles) &&
+         (!num_bytes || !*num_bytes || bytes));
   RequestContext request_context;
   auto dispatcher = GetDispatcher(message_pipe_handle);
   if (!dispatcher)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  std::unique_ptr<MessageForTransit> message;
+  std::unique_ptr<ports::UserMessageEvent> message_event;
   MojoResult rv =
-      dispatcher->ReadMessage(&message, num_bytes, handles, num_handles, flags,
-                              false /* ignore_num_bytes */);
+      dispatcher->ReadMessage(&message_event, num_bytes, handles, num_handles,
+                              flags, false /* ignore_num_bytes */);
   if (rv != MOJO_RESULT_OK)
     return rv;
 
-  if (message && message->num_bytes())
-    memcpy(bytes, message->bytes(), message->num_bytes());
+  // Some tests use fake message pipe dispatchers which return null events.
+  //
+  // TODO(rockot): Fix the tests, because this is weird.
+  if (!message_event)
+    return MOJO_RESULT_OK;
 
+  auto* message = message_event->GetMessage<UserMessageImpl>();
+  if (!message->IsSerialized())
+    return MOJO_RESULT_UNKNOWN;  // Maybe we need a better result code.
+
+  if (message->user_payload_size())
+    memcpy(bytes, message->user_payload(), message->user_payload_size());
   return MOJO_RESULT_OK;
 }
 
 MojoResult Core::ReadMessageNew(MojoHandle message_pipe_handle,
-                                MojoMessageHandle* message,
+                                MojoMessageHandle* message_handle,
                                 uint32_t* num_bytes,
                                 MojoHandle* handles,
                                 uint32_t* num_handles,
                                 MojoReadMessageFlags flags) {
-  CHECK(message);
-  CHECK(!num_handles || !*num_handles || handles);
+  DCHECK(message_handle);
+  DCHECK(!num_handles || !*num_handles || handles);
   RequestContext request_context;
   auto dispatcher = GetDispatcher(message_pipe_handle);
   if (!dispatcher)
     return MOJO_RESULT_INVALID_ARGUMENT;
-  std::unique_ptr<MessageForTransit> msg;
+  std::unique_ptr<ports::UserMessageEvent> message_event;
   MojoResult rv =
-      dispatcher->ReadMessage(&msg, num_bytes, handles, num_handles, flags,
-                              true /* ignore_num_bytes */);
+      dispatcher->ReadMessage(&message_event, num_bytes, handles, num_handles,
+                              flags, true /* ignore_num_bytes */);
   if (rv != MOJO_RESULT_OK)
     return rv;
-  *message = reinterpret_cast<MojoMessageHandle>(msg.release());
+
+  *message_handle =
+      reinterpret_cast<MojoMessageHandle>(message_event.release());
   return MOJO_RESULT_OK;
 }
 
@@ -669,15 +685,16 @@
   return MOJO_RESULT_OK;
 }
 
-MojoResult Core::NotifyBadMessage(MojoMessageHandle message,
+MojoResult Core::NotifyBadMessage(MojoMessageHandle message_handle,
                                   const char* error,
                                   size_t error_num_bytes) {
-  if (!message)
+  if (!message_handle)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
-  const PortsMessage& ports_message =
-      reinterpret_cast<MessageForTransit*>(message)->ports_message();
-  if (ports_message.source_node() == ports::kInvalidNodeName) {
+  auto* message_event =
+      reinterpret_cast<ports::UserMessageEvent*>(message_handle);
+  auto* message = message_event->GetMessage<UserMessageImpl>();
+  if (message->source_node() == ports::kInvalidNodeName) {
     DVLOG(1) << "Received invalid message from unknown node.";
     if (!default_process_error_callback_.is_null())
       default_process_error_callback_.Run(std::string(error, error_num_bytes));
@@ -685,7 +702,7 @@
   }
 
   GetNodeController()->NotifyBadMessageFrom(
-      ports_message.source_node(), std::string(error, error_num_bytes));
+      message->source_node(), std::string(error, error_num_bytes));
   return MOJO_RESULT_OK;
 }
 
@@ -991,12 +1008,12 @@
       static_cast<SharedBufferDispatcher*>(dispatcher.get());
   scoped_refptr<PlatformSharedBuffer> platform_shared_buffer =
       shm_dispatcher->PassPlatformSharedBuffer();
-  CHECK(platform_shared_buffer);
+  DCHECK(platform_shared_buffer);
 
-  CHECK(size);
+  DCHECK(size);
   *size = platform_shared_buffer->GetNumBytes();
 
-  CHECK(flags);
+  DCHECK(flags);
   *flags = MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE;
   if (platform_shared_buffer->IsReadOnly())
     *flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY;
diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h
index c2d0aa71..18c1167 100644
--- a/mojo/edk/system/core.h
+++ b/mojo/edk/system/core.h
@@ -175,9 +175,9 @@
                           const MojoHandle* handles,
                           uint32_t num_handles,
                           MojoAllocMessageFlags flags,
-                          MojoMessageHandle* message);
-  MojoResult FreeMessage(MojoMessageHandle message);
-  MojoResult GetMessageBuffer(MojoMessageHandle message, void** buffer);
+                          MojoMessageHandle* message_handle);
+  MojoResult FreeMessage(MojoMessageHandle message_handle);
+  MojoResult GetMessageBuffer(MojoMessageHandle message_handle, void** buffer);
   MojoResult GetProperty(MojoPropertyType type, void* value);
 
   // These methods correspond to the API functions defined in
@@ -193,7 +193,7 @@
                           uint32_t num_handles,
                           MojoWriteMessageFlags flags);
   MojoResult WriteMessageNew(MojoHandle message_pipe_handle,
-                             MojoMessageHandle message,
+                             MojoMessageHandle message_handle,
                              MojoWriteMessageFlags flags);
   MojoResult ReadMessage(MojoHandle message_pipe_handle,
                          void* bytes,
@@ -202,13 +202,13 @@
                          uint32_t* num_handles,
                          MojoReadMessageFlags flags);
   MojoResult ReadMessageNew(MojoHandle message_pipe_handle,
-                            MojoMessageHandle* message,
+                            MojoMessageHandle* message_handle,
                             uint32_t* num_bytes,
                             MojoHandle* handles,
                             uint32_t* num_handles,
                             MojoReadMessageFlags flags);
   MojoResult FuseMessagePipes(MojoHandle handle0, MojoHandle handle1);
-  MojoResult NotifyBadMessage(MojoMessageHandle message,
+  MojoResult NotifyBadMessage(MojoMessageHandle message_handle,
                               const char* error,
                               size_t error_num_bytes);
 
diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc
index 7751612e..e518f3d 100644
--- a/mojo/edk/system/core_test_base.cc
+++ b/mojo/edk/system/core_test_base.cc
@@ -16,7 +16,7 @@
 #include "mojo/edk/system/configuration.h"
 #include "mojo/edk/system/core.h"
 #include "mojo/edk/system/dispatcher.h"
-#include "mojo/edk/system/message_for_transit.h"
+#include "mojo/edk/system/user_message_impl.h"
 
 namespace mojo {
 namespace edk {
@@ -42,11 +42,12 @@
   }
 
   MojoResult WriteMessage(
-      std::unique_ptr<MessageForTransit> message,
+      std::unique_ptr<ports::UserMessageEvent> message_event,
       MojoWriteMessageFlags /*flags*/) override {
     info_->IncrementWriteMessageCallCount();
 
-    if (message->num_bytes() > GetConfiguration().max_message_num_bytes)
+    auto* message = message_event->GetMessage<UserMessageImpl>();
+    if (message->user_payload_size() > GetConfiguration().max_message_num_bytes)
       return MOJO_RESULT_RESOURCE_EXHAUSTED;
 
     if (message->num_handles())
@@ -55,12 +56,13 @@
     return MOJO_RESULT_OK;
   }
 
-  MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* message,
-                         uint32_t* num_bytes,
-                         MojoHandle* handle,
-                         uint32_t* num_handles,
-                         MojoReadMessageFlags /*flags*/,
-                         bool ignore_num_bytes) override {
+  MojoResult ReadMessage(
+      std::unique_ptr<ports::UserMessageEvent>* message_event,
+      uint32_t* num_bytes,
+      MojoHandle* handle,
+      uint32_t* num_handles,
+      MojoReadMessageFlags /*flags*/,
+      bool ignore_num_bytes) override {
     info_->IncrementReadMessageCallCount();
 
     if (num_handles)
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
index 7e4f084c..a0f6119 100644
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
@@ -20,8 +20,8 @@
 #include "mojo/edk/system/core.h"
 #include "mojo/edk/system/data_pipe_control_message.h"
 #include "mojo/edk/system/node_controller.h"
-#include "mojo/edk/system/ports_message.h"
 #include "mojo/edk/system/request_context.h"
+#include "mojo/edk/system/user_message_impl.h"
 #include "mojo/public/c/system/data_pipe.h"
 
 namespace mojo {
@@ -527,21 +527,21 @@
              << " [control_port=" << control_port_.name() << "]";
     peer_closed_ = true;
   } else if (rv == ports::OK && port_status.has_messages && !in_transit_) {
-    ports::ScopedMessage message;
+    std::unique_ptr<ports::UserMessageEvent> message_event;
     do {
-      int rv = node_controller_->node()->GetMessage(
-          control_port_, &message, nullptr);
+      int rv = node_controller_->node()->GetMessage(control_port_,
+                                                    &message_event, nullptr);
       if (rv != ports::OK)
         peer_closed_ = true;
-      if (message) {
-        if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) {
+      if (message_event) {
+        auto* message = message_event->GetMessage<UserMessageImpl>();
+        if (message->user_payload_size() < sizeof(DataPipeControlMessage)) {
           peer_closed_ = true;
           break;
         }
 
         const DataPipeControlMessage* m =
-            static_cast<const DataPipeControlMessage*>(
-                message->payload_bytes());
+            static_cast<const DataPipeControlMessage*>(message->user_payload());
 
         if (m->command != DataPipeCommand::DATA_WAS_WRITTEN) {
           DLOG(ERROR) << "Unexpected control message from producer.";
@@ -562,7 +562,7 @@
 
         bytes_available_ += m->num_bytes;
       }
-    } while (message);
+    } while (message_event);
   }
 
   bool has_new_data = bytes_available_ != previous_bytes_available;
diff --git a/mojo/edk/system/data_pipe_control_message.cc b/mojo/edk/system/data_pipe_control_message.cc
index 23873b82..22a32f8 100644
--- a/mojo/edk/system/data_pipe_control_message.cc
+++ b/mojo/edk/system/data_pipe_control_message.cc
@@ -6,7 +6,8 @@
 
 #include "mojo/edk/embedder/platform_handle_vector.h"
 #include "mojo/edk/system/node_controller.h"
-#include "mojo/edk/system/ports_message.h"
+#include "mojo/edk/system/ports/event.h"
+#include "mojo/edk/system/user_message_impl.h"
 
 namespace mojo {
 namespace edk {
@@ -15,16 +16,18 @@
                                 const ports::PortRef& port,
                                 DataPipeCommand command,
                                 uint32_t num_bytes) {
-  std::unique_ptr<PortsMessage> message =
-      PortsMessage::NewUserMessage(sizeof(DataPipeControlMessage), 0, 0);
-  CHECK(message);
+  std::unique_ptr<ports::UserMessageEvent> event;
+  MojoResult result = UserMessageImpl::CreateEventForNewSerializedMessage(
+      sizeof(DataPipeControlMessage), nullptr, 0, &event);
+  DCHECK_EQ(MOJO_RESULT_OK, result);
+  DCHECK(event);
 
-  DataPipeControlMessage* data =
-      static_cast<DataPipeControlMessage*>(message->mutable_payload_bytes());
+  DataPipeControlMessage* data = static_cast<DataPipeControlMessage*>(
+      event->GetMessage<UserMessageImpl>()->user_payload());
   data->command = command;
   data->num_bytes = num_bytes;
 
-  int rv = node_controller->SendMessage(port, std::move(message));
+  int rv = node_controller->SendUserMessage(port, std::move(event));
   if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) {
     DLOG(ERROR) << "Unexpected failure sending data pipe control message: "
                 << rv;
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc
index 126f56f..b40b621 100644
--- a/mojo/edk/system/data_pipe_producer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -19,8 +19,8 @@
 #include "mojo/edk/system/core.h"
 #include "mojo/edk/system/data_pipe_control_message.h"
 #include "mojo/edk/system/node_controller.h"
-#include "mojo/edk/system/ports_message.h"
 #include "mojo/edk/system/request_context.h"
+#include "mojo/edk/system/user_message_impl.h"
 #include "mojo/public/c/system/data_pipe.h"
 
 namespace mojo {
@@ -476,21 +476,21 @@
              << " [control_port=" << control_port_.name() << "]";
     peer_closed_ = true;
   } else if (rv == ports::OK && port_status.has_messages && !in_transit_) {
-    ports::ScopedMessage message;
+    std::unique_ptr<ports::UserMessageEvent> message_event;
     do {
-      int rv = node_controller_->node()->GetMessage(
-          control_port_, &message, nullptr);
+      int rv = node_controller_->node()->GetMessage(control_port_,
+                                                    &message_event, nullptr);
       if (rv != ports::OK)
         peer_closed_ = true;
-      if (message) {
-        if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) {
+      if (message_event) {
+        auto* message = message_event->GetMessage<UserMessageImpl>();
+        if (message->user_payload_size() < sizeof(DataPipeControlMessage)) {
           peer_closed_ = true;
           break;
         }
 
         const DataPipeControlMessage* m =
-            static_cast<const DataPipeControlMessage*>(
-                message->payload_bytes());
+            static_cast<const DataPipeControlMessage*>(message->user_payload());
 
         if (m->command != DataPipeCommand::DATA_WAS_READ) {
           DLOG(ERROR) << "Unexpected message from consumer.";
@@ -510,7 +510,7 @@
 
         available_capacity_ += m->num_bytes;
       }
-    } while (message);
+    } while (message_event);
   }
 
   if (peer_closed_ != was_peer_closed ||
diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc
index 7cdbe910..c144a00 100644
--- a/mojo/edk/system/dispatcher.cc
+++ b/mojo/edk/system/dispatcher.cc
@@ -10,6 +10,7 @@
 #include "mojo/edk/system/data_pipe_producer_dispatcher.h"
 #include "mojo/edk/system/message_pipe_dispatcher.h"
 #include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/shared_buffer_dispatcher.h"
 
 namespace mojo {
@@ -39,17 +40,19 @@
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
-MojoResult Dispatcher::WriteMessage(std::unique_ptr<MessageForTransit> message,
-                                    MojoWriteMessageFlags flags) {
+MojoResult Dispatcher::WriteMessage(
+    std::unique_ptr<ports::UserMessageEvent> message,
+    MojoWriteMessageFlags flags) {
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
-MojoResult Dispatcher::ReadMessage(std::unique_ptr<MessageForTransit>* message,
-                                   uint32_t* num_bytes,
-                                   MojoHandle* handles,
-                                   uint32_t* num_handles,
-                                   MojoReadMessageFlags flags,
-                                   bool read_any_size) {
+MojoResult Dispatcher::ReadMessage(
+    std::unique_ptr<ports::UserMessageEvent>* message,
+    uint32_t* num_bytes,
+    MojoHandle* handles,
+    uint32_t* num_handles,
+    MojoReadMessageFlags flags,
+    bool read_any_size) {
   return MOJO_RESULT_INVALID_ARGUMENT;
 }
 
diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h
index db1f1f1..0863da5 100644
--- a/mojo/edk/system/dispatcher.h
+++ b/mojo/edk/system/dispatcher.h
@@ -29,8 +29,11 @@
 namespace mojo {
 namespace edk {
 
+namespace ports {
+class UserMessageEvent;
+}
+
 class Dispatcher;
-class MessageForTransit;
 
 using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>;
 
@@ -78,15 +81,17 @@
 
   ///////////// Message pipe API /////////////
 
-  virtual MojoResult WriteMessage(std::unique_ptr<MessageForTransit> message,
-                                  MojoWriteMessageFlags flags);
+  virtual MojoResult WriteMessage(
+      std::unique_ptr<ports::UserMessageEvent> message,
+      MojoWriteMessageFlags flags);
 
-  virtual MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* message,
-                                 uint32_t* num_bytes,
-                                 MojoHandle* handles,
-                                 uint32_t* num_handles,
-                                 MojoReadMessageFlags flags,
-                                 bool read_any_size);
+  virtual MojoResult ReadMessage(
+      std::unique_ptr<ports::UserMessageEvent>* message,
+      uint32_t* num_bytes,
+      MojoHandle* handles,
+      uint32_t* num_handles,
+      MojoReadMessageFlags flags,
+      bool read_any_size);
 
   ///////////// Shared buffer API /////////////
 
diff --git a/mojo/edk/system/message_for_transit.cc b/mojo/edk/system/message_for_transit.cc
deleted file mode 100644
index 26658e16..0000000
--- a/mojo/edk/system/message_for_transit.cc
+++ /dev/null
@@ -1,136 +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 "mojo/edk/system/message_for_transit.h"
-
-#include <vector>
-
-#include "mojo/edk/embedder/platform_handle_vector.h"
-
-namespace mojo {
-namespace edk {
-
-namespace {
-
-static_assert(sizeof(MessageForTransit::MessageHeader) % 8 == 0,
-              "Invalid MessageHeader size.");
-static_assert(sizeof(MessageForTransit::DispatcherHeader) % 8 == 0,
-              "Invalid DispatcherHeader size.");
-
-}  // namespace
-
-MessageForTransit::~MessageForTransit() {}
-
-// static
-MojoResult MessageForTransit::Create(
-    std::unique_ptr<MessageForTransit>* message,
-    uint32_t num_bytes,
-    const Dispatcher::DispatcherInTransit* dispatchers,
-    uint32_t num_dispatchers) {
-  // A structure for retaining information about every Dispatcher that will be
-  // sent with this message.
-  struct DispatcherInfo {
-    uint32_t num_bytes;
-    uint32_t num_ports;
-    uint32_t num_handles;
-  };
-
-  // This is only the base header size. It will grow as we accumulate the
-  // size of serialized state for each dispatcher.
-  size_t header_size = sizeof(MessageHeader) +
-      num_dispatchers * sizeof(DispatcherHeader);
-  size_t num_ports = 0;
-  size_t num_handles = 0;
-
-  std::vector<DispatcherInfo> dispatcher_info(num_dispatchers);
-  for (size_t i = 0; i < num_dispatchers; ++i) {
-    Dispatcher* d = dispatchers[i].dispatcher.get();
-    d->StartSerialize(&dispatcher_info[i].num_bytes,
-                      &dispatcher_info[i].num_ports,
-                      &dispatcher_info[i].num_handles);
-    header_size += dispatcher_info[i].num_bytes;
-    num_ports += dispatcher_info[i].num_ports;
-    num_handles += dispatcher_info[i].num_handles;
-  }
-
-  // We now have enough information to fully allocate the message storage.
-  std::unique_ptr<PortsMessage> msg = PortsMessage::NewUserMessage(
-      header_size + num_bytes, num_ports, num_handles);
-  if (!msg)
-    return MOJO_RESULT_RESOURCE_EXHAUSTED;
-
-  // Populate the message header with information about serialized dispatchers.
-  //
-  // The front of the message is always a MessageHeader followed by a
-  // DispatcherHeader for each dispatcher to be sent.
-  MessageHeader* header =
-      static_cast<MessageHeader*>(msg->mutable_payload_bytes());
-  DispatcherHeader* dispatcher_headers =
-      reinterpret_cast<DispatcherHeader*>(header + 1);
-
-  // Serialized dispatcher state immediately follows the series of
-  // DispatcherHeaders.
-  char* dispatcher_data =
-      reinterpret_cast<char*>(dispatcher_headers + num_dispatchers);
-
-  header->num_dispatchers = num_dispatchers;
-
-  // |header_size| is the total number of bytes preceding the message payload,
-  // including all dispatcher headers and serialized dispatcher state.
-  DCHECK_LE(header_size, std::numeric_limits<uint32_t>::max());
-  header->header_size = static_cast<uint32_t>(header_size);
-
-  if (num_dispatchers > 0) {
-    ScopedPlatformHandleVectorPtr handles(
-        new PlatformHandleVector(num_handles));
-    size_t port_index = 0;
-    size_t handle_index = 0;
-    bool fail = false;
-    for (size_t i = 0; i < num_dispatchers; ++i) {
-      Dispatcher* d = dispatchers[i].dispatcher.get();
-      DispatcherHeader* dh = &dispatcher_headers[i];
-      const DispatcherInfo& info = dispatcher_info[i];
-
-      // Fill in the header for this dispatcher.
-      dh->type = static_cast<int32_t>(d->GetType());
-      dh->num_bytes = info.num_bytes;
-      dh->num_ports = info.num_ports;
-      dh->num_platform_handles = info.num_handles;
-
-      // Fill in serialized state, ports, and platform handles. We'll cancel
-      // the send if the dispatcher implementation rejects for some reason.
-      if (!d->EndSerialize(static_cast<void*>(dispatcher_data),
-                           msg->mutable_ports() + port_index,
-                           handles->data() + handle_index)) {
-        fail = true;
-        break;
-      }
-
-      dispatcher_data += info.num_bytes;
-      port_index += info.num_ports;
-      handle_index += info.num_handles;
-    }
-
-    if (fail) {
-      // Release any platform handles we've accumulated. Their dispatchers
-      // retain ownership when message creation fails, so these are not actually
-      // leaking.
-      handles->clear();
-      return MOJO_RESULT_INVALID_ARGUMENT;
-    }
-
-    // Take ownership of all the handles and move them into message storage.
-    msg->SetHandles(std::move(handles));
-  }
-
-  message->reset(new MessageForTransit(std::move(msg)));
-  return MOJO_RESULT_OK;
-}
-
-MessageForTransit::MessageForTransit(std::unique_ptr<PortsMessage> message)
-    : message_(std::move(message)) {
-}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/message_for_transit.h b/mojo/edk/system/message_for_transit.h
deleted file mode 100644
index 6103a771..0000000
--- a/mojo/edk/system/message_for_transit.h
+++ /dev/null
@@ -1,115 +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 MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_
-#define MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_
-
-#include <stdint.h>
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "mojo/edk/system/dispatcher.h"
-#include "mojo/edk/system/ports_message.h"
-#include "mojo/edk/system/system_impl_export.h"
-
-namespace mojo {
-namespace edk {
-
-// MessageForTransit holds onto a PortsMessage which may be sent via
-// |MojoWriteMessage()| or which may have been received on a pipe endpoint.
-// Instances of this class are exposed to Mojo system API consumers via the
-// opaque pointers used with |MojoCreateMessage()|, |MojoDestroyMessage()|,
-// |MojoWriteMessageNew()|, and |MojoReadMessageNew()|.
-class MOJO_SYSTEM_IMPL_EXPORT MessageForTransit {
- public:
-#pragma pack(push, 1)
-  // Header attached to every message.
-  struct MessageHeader {
-    // The number of serialized dispatchers included in this header.
-    uint32_t num_dispatchers;
-
-    // Total size of the header, including serialized dispatcher data.
-    uint32_t header_size;
-  };
-
-  // Header for each dispatcher in a message, immediately following the message
-  // header.
-  struct DispatcherHeader {
-    // The type of the dispatcher, correpsonding to the Dispatcher::Type enum.
-    int32_t type;
-
-    // The size of the serialized dispatcher, not including this header.
-    uint32_t num_bytes;
-
-    // The number of ports needed to deserialize this dispatcher.
-    uint32_t num_ports;
-
-    // The number of platform handles needed to deserialize this dispatcher.
-    uint32_t num_platform_handles;
-  };
-#pragma pack(pop)
-
-  ~MessageForTransit();
-
-  // A static constructor for building outbound messages.
-  static MojoResult Create(
-      std::unique_ptr<MessageForTransit>* message,
-      uint32_t num_bytes,
-      const Dispatcher::DispatcherInTransit* dispatchers,
-      uint32_t num_dispatchers);
-
-  // A static constructor for wrapping inbound messages.
-  static std::unique_ptr<MessageForTransit> WrapPortsMessage(
-      std::unique_ptr<PortsMessage> message) {
-    return base::WrapUnique(new MessageForTransit(std::move(message)));
-  }
-
-  const void* bytes() const {
-    DCHECK(message_);
-    return static_cast<const void*>(
-        static_cast<const char*>(message_->payload_bytes()) +
-            header()->header_size);
-  }
-
-  void* mutable_bytes() {
-    DCHECK(message_);
-    return static_cast<void*>(
-        static_cast<char*>(message_->mutable_payload_bytes()) +
-            header()->header_size);
-  }
-
-  size_t num_bytes() const {
-    size_t header_size = header()->header_size;
-    DCHECK_GE(message_->num_payload_bytes(), header_size);
-    return message_->num_payload_bytes() - header_size;
-  }
-
-  size_t num_handles() const { return header()->num_dispatchers; }
-
-  const PortsMessage& ports_message() const { return *message_; }
-
-  std::unique_ptr<PortsMessage> TakePortsMessage() {
-    return std::move(message_);
-  }
-
- private:
-  explicit MessageForTransit(std::unique_ptr<PortsMessage> message);
-
-  const MessageForTransit::MessageHeader* header() const {
-    DCHECK(message_);
-    return static_cast<const MessageForTransit::MessageHeader*>(
-        message_->payload_bytes());
-  }
-
-  std::unique_ptr<PortsMessage> message_;
-
-  DISALLOW_COPY_AND_ASSIGN(MessageForTransit);
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_
diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc
index 1db56c0..82d5f0f 100644
--- a/mojo/edk/system/message_pipe_dispatcher.cc
+++ b/mojo/edk/system/message_pipe_dispatcher.cc
@@ -12,20 +12,17 @@
 #include "base/memory/ref_counted.h"
 #include "mojo/edk/embedder/embedder_internal.h"
 #include "mojo/edk/system/core.h"
-#include "mojo/edk/system/message_for_transit.h"
 #include "mojo/edk/system/node_controller.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/message_filter.h"
-#include "mojo/edk/system/ports_message.h"
 #include "mojo/edk/system/request_context.h"
+#include "mojo/edk/system/user_message_impl.h"
 
 namespace mojo {
 namespace edk {
 
 namespace {
 
-using DispatcherHeader = MessageForTransit::DispatcherHeader;
-using MessageHeader = MessageForTransit::MessageHeader;
-
 #pragma pack(push, 1)
 
 struct SerializedState {
@@ -60,78 +57,6 @@
   DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
 };
 
-// A MessageFilter used by ReadMessage to determine whether a message should
-// actually be consumed yet.
-class ReadMessageFilter : public ports::MessageFilter {
- public:
-  // Creates a new ReadMessageFilter which captures and potentially modifies
-  // various (unowned) local state within MessagePipeDispatcher::ReadMessage.
-  ReadMessageFilter(bool read_any_size,
-                    bool may_discard,
-                    uint32_t* num_bytes,
-                    uint32_t* num_handles,
-                    bool* no_space,
-                    bool* invalid_message)
-      : read_any_size_(read_any_size),
-        may_discard_(may_discard),
-        num_bytes_(num_bytes),
-        num_handles_(num_handles),
-        no_space_(no_space),
-        invalid_message_(invalid_message) {}
-
-  ~ReadMessageFilter() override {}
-
-  // ports::MessageFilter:
-  bool Match(const ports::Message& m) override {
-    const PortsMessage& message = static_cast<const PortsMessage&>(m);
-    if (message.num_payload_bytes() < sizeof(MessageHeader)) {
-      *invalid_message_ = true;
-      return true;
-    }
-
-    const MessageHeader* header =
-        static_cast<const MessageHeader*>(message.payload_bytes());
-    if (header->header_size > message.num_payload_bytes()) {
-      *invalid_message_ = true;
-      return true;
-    }
-
-    uint32_t bytes_to_read = 0;
-    uint32_t bytes_available =
-        static_cast<uint32_t>(message.num_payload_bytes()) -
-        header->header_size;
-    if (num_bytes_) {
-      bytes_to_read = std::min(*num_bytes_, bytes_available);
-      *num_bytes_ = bytes_available;
-    }
-
-    uint32_t handles_to_read = 0;
-    uint32_t handles_available = header->num_dispatchers;
-    if (num_handles_) {
-      handles_to_read = std::min(*num_handles_, handles_available);
-      *num_handles_ = handles_available;
-    }
-
-    if (handles_to_read < handles_available ||
-        (!read_any_size_ && bytes_to_read < bytes_available)) {
-      *no_space_ = true;
-      return may_discard_;
-    }
-
-    return true;
-  }
-
- private:
-  const bool read_any_size_;
-  const bool may_discard_;
-  uint32_t* const num_bytes_;
-  uint32_t* const num_handles_;
-  bool* const no_space_;
-  bool* const invalid_message_;
-
-  DISALLOW_COPY_AND_ASSIGN(ReadMessageFilter);
-};
-
 #if DCHECK_IS_ON()
 
 // A MessageFilter which never matches a message. Used to peek at the size of
@@ -142,8 +67,8 @@
   ~PeekSizeMessageFilter() override {}
 
   // ports::MessageFilter:
-  bool Match(const ports::Message& message) override {
-    message_size_ = message.num_payload_bytes();
+  bool Match(const ports::UserMessageEvent& message) override {
+    message_size_ = message.GetMessage<UserMessageImpl>()->user_payload_size();
     return false;
   }
 
@@ -211,13 +136,14 @@
 }
 
 MojoResult MessagePipeDispatcher::WriteMessage(
-    std::unique_ptr<MessageForTransit> message,
+    std::unique_ptr<ports::UserMessageEvent> message,
     MojoWriteMessageFlags flags) {
   if (port_closed_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
-  size_t num_bytes = message->num_bytes();
-  int rv = node_controller_->SendMessage(port_, message->TakePortsMessage());
+  const size_t num_bytes =
+      message->GetMessage<UserMessageImpl>()->user_payload_size();
+  int rv = node_controller_->SendUserMessage(port_, std::move(message));
 
   DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_
            << " [port=" << port_.name() << "; rv=" << rv
@@ -240,7 +166,7 @@
 }
 
 MojoResult MessagePipeDispatcher::ReadMessage(
-    std::unique_ptr<MessageForTransit>* message,
+    std::unique_ptr<ports::UserMessageEvent>* message,
     uint32_t* num_bytes,
     MojoHandle* handles,
     uint32_t* num_handles,
@@ -250,9 +176,7 @@
   if (port_closed_ || in_transit_)
     return MOJO_RESULT_INVALID_ARGUMENT;
 
-  bool no_space = false;
-  bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD;
-  bool invalid_message = false;
+  const bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD;
 
   // Grab a message if the provided handles buffer is large enough. If the input
   // |num_bytes| is provided and |read_any_size| is false, we also ensure
@@ -260,127 +184,16 @@
   //
   // If |read_any_size| is true, the input value of |*num_bytes| is ignored.
   // This flag exists to support both new and old API behavior.
+  MojoResult read_result = UserMessageImpl::ReadMessageEventFromPort(
+      node_controller_, port_, read_any_size, may_discard, num_bytes, handles,
+      num_handles, message);
 
-  ports::ScopedMessage ports_message;
-  ReadMessageFilter filter(read_any_size, may_discard, num_bytes, num_handles,
-                           &no_space, &invalid_message);
-  int rv = node_controller_->node()->GetMessage(port_, &ports_message, &filter);
+  // We may need to update anyone watching our signals in case we just read the
+  // last available message.
+  base::AutoLock lock(signal_lock_);
+  watchers_.NotifyState(GetHandleSignalsStateNoLock());
 
-  if (invalid_message)
-    return MOJO_RESULT_UNKNOWN;
-
-  if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) {
-    if (rv == ports::ERROR_PORT_UNKNOWN ||
-        rv == ports::ERROR_PORT_STATE_UNEXPECTED)
-      return MOJO_RESULT_INVALID_ARGUMENT;
-
-    NOTREACHED();
-    return MOJO_RESULT_UNKNOWN;  // TODO: Add a better error code here?
-  }
-
-  if (no_space) {
-    if (may_discard) {
-      // May have been the last message on the pipe. Need to update signals just
-      // in case.
-      base::AutoLock lock(signal_lock_);
-      watchers_.NotifyState(GetHandleSignalsStateNoLock());
-    }
-    // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't
-    // sufficient to hold this message's data. The message will still be in
-    // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set.
-    return MOJO_RESULT_RESOURCE_EXHAUSTED;
-  }
-
-  if (!ports_message) {
-    // No message was available in queue.
-
-    if (rv == ports::OK)
-      return MOJO_RESULT_SHOULD_WAIT;
-
-    // Peer is closed and there are no more messages to read.
-    DCHECK_EQ(rv, ports::ERROR_PORT_PEER_CLOSED);
-    return MOJO_RESULT_FAILED_PRECONDITION;
-  }
-
-  // Alright! We have a message and the caller has provided sufficient storage
-  // in which to receive it.
-
-  {
-    // We need to update anyone watching our signals in case that was the last
-    // available message.
-    base::AutoLock lock(signal_lock_);
-    watchers_.NotifyState(GetHandleSignalsStateNoLock());
-  }
-
-  std::unique_ptr<PortsMessage> msg(
-      static_cast<PortsMessage*>(ports_message.release()));
-
-  const MessageHeader* header =
-      static_cast<const MessageHeader*>(msg->payload_bytes());
-  const DispatcherHeader* dispatcher_headers =
-      reinterpret_cast<const DispatcherHeader*>(header + 1);
-
-  if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())
-    return MOJO_RESULT_UNKNOWN;
-
-  // Deserialize dispatchers.
-  if (header->num_dispatchers > 0) {
-    CHECK(handles);
-    std::vector<DispatcherInTransit> dispatchers(header->num_dispatchers);
-    size_t data_payload_index = sizeof(MessageHeader) +
-        header->num_dispatchers * sizeof(DispatcherHeader);
-    if (data_payload_index > header->header_size)
-      return MOJO_RESULT_UNKNOWN;
-    const char* dispatcher_data = reinterpret_cast<const char*>(
-        dispatcher_headers + header->num_dispatchers);
-    size_t port_index = 0;
-    size_t platform_handle_index = 0;
-    ScopedPlatformHandleVectorPtr msg_handles = msg->TakeHandles();
-    const size_t num_msg_handles = msg_handles ? msg_handles->size() : 0;
-    for (size_t i = 0; i < header->num_dispatchers; ++i) {
-      const DispatcherHeader& dh = dispatcher_headers[i];
-      Type type = static_cast<Type>(dh.type);
-
-      size_t next_payload_index = data_payload_index + dh.num_bytes;
-      if (msg->num_payload_bytes() < next_payload_index ||
-          next_payload_index < data_payload_index) {
-        return MOJO_RESULT_UNKNOWN;
-      }
-
-      size_t next_port_index = port_index + dh.num_ports;
-      if (msg->num_ports() < next_port_index || next_port_index < port_index)
-        return MOJO_RESULT_UNKNOWN;
-
-      size_t next_platform_handle_index =
-          platform_handle_index + dh.num_platform_handles;
-      if (num_msg_handles < next_platform_handle_index ||
-          next_platform_handle_index < platform_handle_index) {
-        return MOJO_RESULT_UNKNOWN;
-      }
-
-      PlatformHandle* out_handles =
-          num_msg_handles ? msg_handles->data() + platform_handle_index
-                          : nullptr;
-      dispatchers[i].dispatcher = Dispatcher::Deserialize(
-          type, dispatcher_data, dh.num_bytes, msg->ports() + port_index,
-          dh.num_ports, out_handles, dh.num_platform_handles);
-      if (!dispatchers[i].dispatcher)
-        return MOJO_RESULT_UNKNOWN;
-
-      dispatcher_data += dh.num_bytes;
-      data_payload_index = next_payload_index;
-      port_index = next_port_index;
-      platform_handle_index = next_platform_handle_index;
-    }
-
-    if (!node_controller_->core()->AddDispatchersFromTransit(dispatchers,
-                                                             handles))
-      return MOJO_RESULT_UNKNOWN;
-  }
-
-  CHECK(msg);
-  *message = MessageForTransit::WrapPortsMessage(std::move(msg));
-  return MOJO_RESULT_OK;
+  return read_result;
 }
 
 HandleSignalsState
@@ -533,7 +346,7 @@
   ports::PortStatus port_status;
   if (node_controller_->node()->GetStatus(port_, &port_status) == ports::OK) {
     if (port_status.has_messages) {
-      ports::ScopedMessage unused;
+      std::unique_ptr<ports::UserMessageEvent> unused;
       PeekSizeMessageFilter filter;
       node_controller_->node()->GetMessage(port_, &unused, &filter);
       DVLOG(4) << "New message detected on message pipe " << pipe_id_
diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h
index 574ad660..6425f308 100644
--- a/mojo/edk/system/message_pipe_dispatcher.h
+++ b/mojo/edk/system/message_pipe_dispatcher.h
@@ -13,7 +13,6 @@
 #include "base/macros.h"
 #include "mojo/edk/system/atomic_flag.h"
 #include "mojo/edk/system/dispatcher.h"
-#include "mojo/edk/system/message_for_transit.h"
 #include "mojo/edk/system/ports/port_ref.h"
 #include "mojo/edk/system/watcher_set.h"
 
@@ -48,9 +47,9 @@
   // Dispatcher:
   Type GetType() const override;
   MojoResult Close() override;
-  MojoResult WriteMessage(std::unique_ptr<MessageForTransit> message,
+  MojoResult WriteMessage(std::unique_ptr<ports::UserMessageEvent> message,
                           MojoWriteMessageFlags flags) override;
-  MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* message,
+  MojoResult ReadMessage(std::unique_ptr<ports::UserMessageEvent>* message,
                          uint32_t* num_bytes,
                          MojoHandle* handles,
                          uint32_t* num_handles,
diff --git a/mojo/edk/system/node_channel.cc b/mojo/edk/system/node_channel.cc
index d0e25c5..813a01d 100644
--- a/mojo/edk/system/node_channel.cc
+++ b/mojo/edk/system/node_channel.cc
@@ -30,16 +30,16 @@
   ADD_BROKER_CLIENT,
   BROKER_CLIENT_ADDED,
   ACCEPT_BROKER_CLIENT,
-  PORTS_MESSAGE,
+  EVENT_MESSAGE,
   REQUEST_PORT_MERGE,
   REQUEST_INTRODUCTION,
   INTRODUCE,
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-  RELAY_PORTS_MESSAGE,
+  RELAY_EVENT_MESSAGE,
 #endif
-  BROADCAST,
+  BROADCAST_EVENT,
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-  PORTS_MESSAGE_FROM_RELAY,
+  EVENT_MESSAGE_FROM_RELAY,
 #endif
   ACCEPT_PEER,
 };
@@ -113,12 +113,12 @@
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
 // This struct is followed by the full payload of a message to be relayed.
-struct RelayPortsMessageData {
+struct RelayEventMessageData {
   ports::NodeName destination;
 };
 
 // This struct is followed by the full payload of a relayed message.
-struct PortsMessageFromRelayData {
+struct EventMessageFromRelayData {
   ports::NodeName source;
 };
 #endif
@@ -167,17 +167,19 @@
 }
 
 // static
-Channel::MessagePtr NodeChannel::CreatePortsMessage(size_t payload_size,
+Channel::MessagePtr NodeChannel::CreateEventMessage(size_t payload_size,
                                                     void** payload,
                                                     size_t num_handles) {
-  return CreateMessage(MessageType::PORTS_MESSAGE, payload_size, num_handles,
+  return CreateMessage(MessageType::EVENT_MESSAGE, payload_size, num_handles,
                        payload);
 }
 
 // static
-void NodeChannel::GetPortsMessageData(Channel::Message* message,
+void NodeChannel::GetEventMessageData(Channel::Message* message,
                                       void** data,
                                       size_t* num_data_bytes) {
+  // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message
+  // with a payload of fewer than |sizeof(Header)| bytes.
   *data = reinterpret_cast<Header*>(message->mutable_payload()) + 1;
   *num_data_bytes = message->payload_size() - sizeof(Header);
 }
@@ -342,10 +344,6 @@
   WriteChannelMessage(std::move(message));
 }
 
-void NodeChannel::PortsMessage(Channel::MessagePtr message) {
-  WriteChannelMessage(std::move(message));
-}
-
 void NodeChannel::RequestPortMerge(const ports::PortName& connector_port_name,
                                    const std::string& token) {
   RequestPortMergeData* data;
@@ -378,17 +376,21 @@
   WriteChannelMessage(std::move(message));
 }
 
+void NodeChannel::SendChannelMessage(Channel::MessagePtr message) {
+  WriteChannelMessage(std::move(message));
+}
+
 void NodeChannel::Broadcast(Channel::MessagePtr message) {
   DCHECK(!message->has_handles());
   void* data;
   Channel::MessagePtr broadcast_message = CreateMessage(
-      MessageType::BROADCAST, message->data_num_bytes(), 0, &data);
+      MessageType::BROADCAST_EVENT, message->data_num_bytes(), 0, &data);
   memcpy(data, message->data(), message->data_num_bytes());
   WriteChannelMessage(std::move(broadcast_message));
 }
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-void NodeChannel::RelayPortsMessage(const ports::NodeName& destination,
+void NodeChannel::RelayEventMessage(const ports::NodeName& destination,
                                     Channel::MessagePtr message) {
 #if defined(OS_WIN)
   DCHECK(message->has_handles());
@@ -396,10 +398,10 @@
   // Note that this is only used on Windows, and on Windows all platform
   // handles are included in the message data. We blindly copy all the data
   // here and the relay node (the parent) will duplicate handles as needed.
-  size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes();
-  RelayPortsMessageData* data;
-  Channel::MessagePtr relay_message = CreateMessage(
-      MessageType::RELAY_PORTS_MESSAGE, num_bytes, 0, &data);
+  size_t num_bytes = sizeof(RelayEventMessageData) + message->data_num_bytes();
+  RelayEventMessageData* data;
+  Channel::MessagePtr relay_message =
+      CreateMessage(MessageType::RELAY_EVENT_MESSAGE, num_bytes, 0, &data);
   data->destination = destination;
   memcpy(data + 1, message->data(), message->data_num_bytes());
 
@@ -419,10 +421,10 @@
   // message may contain fds which need to be attached to the outer message so
   // that they can be transferred to the broker.
   ScopedPlatformHandleVectorPtr handles = message->TakeHandles();
-  size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes();
-  RelayPortsMessageData* data;
+  size_t num_bytes = sizeof(RelayEventMessageData) + message->data_num_bytes();
+  RelayEventMessageData* data;
   Channel::MessagePtr relay_message = CreateMessage(
-      MessageType::RELAY_PORTS_MESSAGE, num_bytes, handles->size(), &data);
+      MessageType::RELAY_EVENT_MESSAGE, num_bytes, handles->size(), &data);
   data->destination = destination;
   memcpy(data + 1, message->data(), message->data_num_bytes());
   relay_message->SetHandles(std::move(handles));
@@ -431,14 +433,14 @@
   WriteChannelMessage(std::move(relay_message));
 }
 
-void NodeChannel::PortsMessageFromRelay(const ports::NodeName& source,
+void NodeChannel::EventMessageFromRelay(const ports::NodeName& source,
                                         Channel::MessagePtr message) {
-  size_t num_bytes = sizeof(PortsMessageFromRelayData) +
-      message->payload_size();
-  PortsMessageFromRelayData* data;
-  Channel::MessagePtr relayed_message = CreateMessage(
-      MessageType::PORTS_MESSAGE_FROM_RELAY, num_bytes, message->num_handles(),
-      &data);
+  size_t num_bytes =
+      sizeof(EventMessageFromRelayData) + message->payload_size();
+  EventMessageFromRelayData* data;
+  Channel::MessagePtr relayed_message =
+      CreateMessage(MessageType::EVENT_MESSAGE_FROM_RELAY, num_bytes,
+                    message->num_handles(), &data);
   data->source = source;
   if (message->payload_size())
     memcpy(data + 1, message->payload(), message->payload_size());
@@ -504,7 +506,7 @@
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
   // If we're not the root, receive any mach ports from the message. If we're
   // the root, the only message containing mach ports should be a
-  // RELAY_PORTS_MESSAGE.
+  // RELAY_EVENT_MESSAGE.
   {
     MachPortRelay* relay = delegate_->GetMachPortRelay();
     if (handles && !relay) {
@@ -605,13 +607,13 @@
       break;
     }
 
-    case MessageType::PORTS_MESSAGE: {
+    case MessageType::EVENT_MESSAGE: {
       size_t num_handles = handles ? handles->size() : 0;
       Channel::MessagePtr message(
           new Channel::Message(payload_size, num_handles));
       message->SetHandles(std::move(handles));
       memcpy(message->mutable_payload(), payload, payload_size);
-      delegate_->OnPortsMessage(remote_node_name_, std::move(message));
+      delegate_->OnEventMessage(remote_node_name_, std::move(message));
       return;
     }
 
@@ -659,16 +661,16 @@
     }
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-    case MessageType::RELAY_PORTS_MESSAGE: {
+    case MessageType::RELAY_EVENT_MESSAGE: {
       base::ProcessHandle from_process;
       {
         base::AutoLock lock(remote_process_handle_lock_);
         from_process = remote_process_handle_;
       }
-      const RelayPortsMessageData* data;
+      const RelayEventMessageData* data;
       if (GetMessagePayload(payload, payload_size, &data)) {
         // Don't try to relay an empty message.
-        if (payload_size <= sizeof(Header) + sizeof(RelayPortsMessageData))
+        if (payload_size <= sizeof(Header) + sizeof(RelayEventMessageData))
           break;
 
         const void* message_start = data + 1;
@@ -696,7 +698,7 @@
           }
         }
   #endif
-        delegate_->OnRelayPortsMessage(remote_node_name_, from_process,
+        delegate_->OnRelayEventMessage(remote_node_name_, from_process,
                                        data->destination, std::move(message));
         return;
       }
@@ -704,7 +706,7 @@
     }
 #endif
 
-    case MessageType::BROADCAST: {
+    case MessageType::BROADCAST_EVENT: {
       if (payload_size <= sizeof(Header))
         break;
       const void* data = static_cast<const void*>(
@@ -720,8 +722,8 @@
     }
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-    case MessageType::PORTS_MESSAGE_FROM_RELAY:
-      const PortsMessageFromRelayData* data;
+    case MessageType::EVENT_MESSAGE_FROM_RELAY:
+      const EventMessageFromRelayData* data;
       if (GetMessagePayload(payload, payload_size, &data)) {
         size_t num_bytes = payload_size - sizeof(*data);
         if (num_bytes < sizeof(Header))
@@ -734,8 +736,8 @@
         message->SetHandles(std::move(handles));
         if (num_bytes)
           memcpy(message->mutable_payload(), data + 1, num_bytes);
-        delegate_->OnPortsMessageFromRelay(
-            remote_node_name_, data->source, std::move(message));
+        delegate_->OnEventMessageFromRelay(remote_node_name_, data->source,
+                                           std::move(message));
         return;
       }
       break;
@@ -821,7 +823,7 @@
     ports::NodeName destination = pending_relays.front().first;
     Channel::MessagePtr message = std::move(pending_relays.front().second);
     pending_relays.pop();
-    delegate_->OnRelayPortsMessage(remote_node_name_, remote_process_handle,
+    delegate_->OnRelayEventMessage(remote_node_name_, remote_process_handle,
                                    destination, std::move(message));
   }
 }
@@ -831,7 +833,7 @@
 #if defined(OS_WIN)
   // Map handles to the destination process. Note: only messages from a
   // privileged node should contain handles on Windows. If an unprivileged
-  // node needs to send handles, it should do so via RelayPortsMessage which
+  // node needs to send handles, it should do so via RelayEventMessage which
   // stashes the handles in the message in such a way that they go undetected
   // here (they'll be unpacked and duplicated by a privileged parent.)
 
diff --git a/mojo/edk/system/node_channel.h b/mojo/edk/system/node_channel.h
index 95dc3410..9ca2eb9 100644
--- a/mojo/edk/system/node_channel.h
+++ b/mojo/edk/system/node_channel.h
@@ -56,7 +56,7 @@
     virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,
                                       const ports::NodeName& broker_name,
                                       ScopedPlatformHandle broker_channel) = 0;
-    virtual void OnPortsMessage(const ports::NodeName& from_node,
+    virtual void OnEventMessage(const ports::NodeName& from_node,
                                 Channel::MessagePtr message) = 0;
     virtual void OnRequestPortMerge(const ports::NodeName& from_node,
                                     const ports::PortName& connector_port_name,
@@ -69,11 +69,11 @@
     virtual void OnBroadcast(const ports::NodeName& from_node,
                              Channel::MessagePtr message) = 0;
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-    virtual void OnRelayPortsMessage(const ports::NodeName& from_node,
+    virtual void OnRelayEventMessage(const ports::NodeName& from_node,
                                      base::ProcessHandle from_process,
                                      const ports::NodeName& destination,
                                      Channel::MessagePtr message) = 0;
-    virtual void OnPortsMessageFromRelay(const ports::NodeName& from_node,
+    virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,
                                          const ports::NodeName& source_node,
                                          Channel::MessagePtr message) = 0;
 #endif
@@ -95,11 +95,12 @@
       scoped_refptr<base::TaskRunner> io_task_runner,
       const ProcessErrorCallback& process_error_callback);
 
-  static Channel::MessagePtr CreatePortsMessage(size_t payload_size,
+  static Channel::MessagePtr CreateEventMessage(size_t payload_size,
                                                 void** payload,
                                                 size_t num_handles);
 
-  static void GetPortsMessageData(Channel::Message* message, void** data,
+  static void GetEventMessageData(Channel::Message* message,
+                                  void** data,
                                   size_t* num_data_bytes);
 
   // Start receiving messages.
@@ -137,12 +138,12 @@
                          ScopedPlatformHandle broker_channel);
   void AcceptBrokerClient(const ports::NodeName& broker_name,
                           ScopedPlatformHandle broker_channel);
-  void PortsMessage(Channel::MessagePtr message);
   void RequestPortMerge(const ports::PortName& connector_port_name,
                         const std::string& token);
   void RequestIntroduction(const ports::NodeName& name);
   void Introduce(const ports::NodeName& name,
                  ScopedPlatformHandle channel_handle);
+  void SendChannelMessage(Channel::MessagePtr message);
   void Broadcast(Channel::MessagePtr message);
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
@@ -150,13 +151,13 @@
   // pass windows handles between two processes that do not have permission to
   // duplicate handles into the other's address space. The relay process is
   // assumed to have that permission.
-  void RelayPortsMessage(const ports::NodeName& destination,
+  void RelayEventMessage(const ports::NodeName& destination,
                          Channel::MessagePtr message);
 
   // Sends a message to its destination from a relay. This is interpreted by the
-  // receiver similarly to PortsMessage, but the original source node is
+  // receiver similarly to EventMessage, but the original source node is
   // provided as additional message metadata from the (trusted) relay node.
-  void PortsMessageFromRelay(const ports::NodeName& source,
+  void EventMessageFromRelay(const ports::NodeName& source,
                              Channel::MessagePtr message);
 #endif
 
diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc
index 477d350b..c895379 100644
--- a/mojo/edk/system/node_controller.cc
+++ b/mojo/edk/system/node_controller.cc
@@ -25,8 +25,8 @@
 #include "mojo/edk/system/broker_host.h"
 #include "mojo/edk/system/configuration.h"
 #include "mojo/edk/system/core.h"
-#include "mojo/edk/system/ports_message.h"
 #include "mojo/edk/system/request_context.h"
+#include "mojo/edk/system/user_message_impl.h"
 
 #if defined(OS_MACOSX) && !defined(OS_IOS)
 #include "mojo/edk/system/mach_port_relay.h"
@@ -77,25 +77,49 @@
                               50 /* bucket count */);
 }
 
-bool ParsePortsMessage(Channel::Message* message,
-                       void** data,
-                       size_t* num_data_bytes,
-                       size_t* num_header_bytes,
-                       size_t* num_payload_bytes,
-                       size_t* num_ports_bytes) {
-  DCHECK(data && num_data_bytes && num_header_bytes && num_payload_bytes &&
-         num_ports_bytes);
-
-  NodeChannel::GetPortsMessageData(message, data, num_data_bytes);
-  if (!*num_data_bytes)
-    return false;
-
-  if (!ports::Message::Parse(*data, *num_data_bytes, num_header_bytes,
-                             num_payload_bytes, num_ports_bytes)) {
-    return false;
+Channel::MessagePtr SerializeEventMessage(ports::ScopedEvent event) {
+  if (event->type() == ports::Event::Type::kUserMessage) {
+    // User message events may already be serialized or need to perform some
+    // custom-bound serialization routines.
+    return UserMessageImpl::SerializeEventMessage(
+        ports::Event::Cast<ports::UserMessageEvent>(&event));
   }
 
-  return true;
+  void* data;
+  auto message =
+      NodeChannel::CreateEventMessage(event->GetSerializedSize(), &data, 0);
+  event->Serialize(data);
+  return message;
+}
+
+ports::ScopedEvent DeserializeEventMessage(
+    const ports::NodeName& from_node,
+    Channel::MessagePtr channel_message) {
+  void* data;
+  size_t size;
+  NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);
+  auto event = ports::Event::Deserialize(data, size);
+  if (!event)
+    return nullptr;
+
+  if (event->type() != ports::Event::Type::kUserMessage)
+    return event;
+
+  // User messages require extra parsing.
+  const size_t event_size = event->GetSerializedSize();
+
+  // Note that if this weren't true, the event couldn't have been deserialized
+  // in the first place.
+  DCHECK_LE(event_size, size);
+
+  auto message = UserMessageImpl::CreateFromChannelMessage(
+      std::move(channel_message), static_cast<uint8_t*>(data) + event_size,
+      size - event_size);
+  message->set_source_node(from_node);
+
+  auto message_event = ports::Event::Cast<ports::UserMessageEvent>(&event);
+  message_event->AttachMessage(std::move(message));
+  return std::move(message_event);
 }
 
 // Used by NodeController to watch for shutdown. Since no IO can happen once
@@ -266,11 +290,10 @@
   AcceptIncomingMessages();
 }
 
-int NodeController::SendMessage(const ports::PortRef& port,
-                                std::unique_ptr<PortsMessage> message) {
-  ports::ScopedMessage ports_message(message.release());
-  int rv = node_->SendMessage(port, std::move(ports_message));
-
+int NodeController::SendUserMessage(
+    const ports::PortRef& port,
+    std::unique_ptr<ports::UserMessageEvent> message) {
+  int rv = node_->SendUserMessage(port, std::move(message));
   AcceptIncomingMessages();
   return rv;
 }
@@ -519,7 +542,7 @@
 
   // Flush any queued message we need to deliver to this node.
   while (!pending_messages.empty()) {
-    channel->PortsMessage(std::move(pending_messages.front()));
+    channel->SendChannelMessage(std::move(pending_messages.front()));
     pending_messages.pop();
   }
 }
@@ -584,30 +607,28 @@
   AcceptIncomingMessages();
 }
 
-void NodeController::SendPeerMessage(const ports::NodeName& name,
-                                     ports::ScopedMessage message) {
-  Channel::MessagePtr channel_message =
-      static_cast<PortsMessage*>(message.get())->TakeChannelMessage();
-
+void NodeController::SendPeerEvent(const ports::NodeName& name,
+                                   ports::ScopedEvent event) {
+  Channel::MessagePtr event_message = SerializeEventMessage(std::move(event));
   scoped_refptr<NodeChannel> peer = GetPeerChannel(name);
 #if defined(OS_WIN)
-  if (channel_message->has_handles()) {
+  if (event_message->has_handles()) {
     // If we're sending a message with handles we aren't the destination
     // node's parent or broker (i.e. we don't know its process handle), ask
     // the broker to relay for us.
     scoped_refptr<NodeChannel> broker = GetBrokerChannel();
     if (!peer || !peer->HasRemoteProcessHandle()) {
       if (!GetConfiguration().is_broker_process && broker) {
-        broker->RelayPortsMessage(name, std::move(channel_message));
+        broker->RelayEventMessage(name, std::move(event_message));
       } else {
         base::AutoLock lock(broker_lock_);
-        pending_relay_messages_[name].emplace(std::move(channel_message));
+        pending_relay_messages_[name].emplace(std::move(event_message));
       }
       return;
     }
   }
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
-  if (channel_message->has_mach_ports()) {
+  if (event_message->has_mach_ports()) {
     // Messages containing Mach ports are always routed through the broker, even
     // if the broker process is the intended recipient.
     bool use_broker = false;
@@ -620,10 +641,10 @@
     if (use_broker) {
       scoped_refptr<NodeChannel> broker = GetBrokerChannel();
       if (broker) {
-        broker->RelayPortsMessage(name, std::move(channel_message));
+        broker->RelayEventMessage(name, std::move(event_message));
       } else {
         base::AutoLock lock(broker_lock_);
-        pending_relay_messages_[name].emplace(std::move(channel_message));
+        pending_relay_messages_[name].emplace(std::move(event_message));
       }
       return;
     }
@@ -631,7 +652,7 @@
 #endif  // defined(OS_WIN)
 
   if (peer) {
-    peer->PortsMessage(std::move(channel_message));
+    peer->SendChannelMessage(std::move(event_message));
     return;
   }
 
@@ -651,7 +672,7 @@
     base::AutoLock lock(peers_lock_);
     auto& queue = pending_peer_messages_[name];
     needs_introduction = queue.empty();
-    queue.emplace(std::move(channel_message));
+    queue.emplace(std::move(event_message));
   }
   if (needs_introduction)
     broker->RequestIntroduction(name);
@@ -660,47 +681,42 @@
 void NodeController::AcceptIncomingMessages() {
   // This is an impactically large value which should never be reached in
   // practice. See the CHECK below for usage.
-  constexpr size_t kMaxAcceptedMessages = 1000000;
+  constexpr size_t kMaxAcceptedEvents = 1000000;
 
-  size_t num_messages_accepted = 0;
-  while (incoming_messages_flag_) {
+  size_t num_events_accepted = 0;
+  while (incoming_events_flag_) {
     // TODO: We may need to be more careful to avoid starving the rest of the
     // thread here. Revisit this if it turns out to be a problem. One
     // alternative would be to schedule a task to continue pumping messages
     // after flushing once.
 
-    messages_lock_.Acquire();
-    if (incoming_messages_.empty()) {
-      messages_lock_.Release();
+    events_lock_.Acquire();
+    if (incoming_events_.empty()) {
+      events_lock_.Release();
       break;
     }
 
-    // libstdc++'s deque creates an internal buffer on construction, even when
-    // the size is 0. So avoid creating it until it is necessary.
-    std::queue<ports::ScopedMessage> messages;
-    std::swap(messages, incoming_messages_);
-    incoming_messages_flag_.Set(false);
-    messages_lock_.Release();
+    std::vector<ports::ScopedEvent> events;
+    std::swap(events, incoming_events_);
+    incoming_events_flag_.Set(false);
+    events_lock_.Release();
 
-    num_messages_accepted += messages.size();
-    while (!messages.empty()) {
-      node_->AcceptMessage(std::move(messages.front()));
-      messages.pop();
-    }
+    num_events_accepted += events.size();
+    for (auto& event : events)
+      node_->AcceptEvent(std::move(event));
 
     // This is effectively a safeguard against potential bugs which might lead
     // to runaway message cycles. If any such cycles arise, we'll start seeing
     // crash reports from this location.
-    CHECK_LE(num_messages_accepted, kMaxAcceptedMessages);
+    CHECK_LE(num_events_accepted, kMaxAcceptedEvents);
   }
 
-  if (num_messages_accepted >= 4) {
+  if (num_events_accepted >= 4) {
     // Note: We avoid logging this histogram for the vast majority of cases.
     // See https://crbug.com/685763 for more context.
     UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.MessagesAcceptedPerEvent",
-                                static_cast<int32_t>(num_messages_accepted),
-                                1 /* min */,
-                                500 /* max */,
+                                static_cast<int32_t>(num_events_accepted),
+                                1 /* min */, 500 /* max */,
                                 50 /* bucket count */);
   }
 
@@ -711,12 +727,12 @@
   RequestContext request_context(RequestContext::Source::SYSTEM);
 
   {
-    base::AutoLock lock(messages_lock_);
-    // Allow a new incoming messages processing task to be posted. This can't be
-    // done after AcceptIncomingMessages() otherwise a message might be missed.
+    base::AutoLock lock(events_lock_);
+    // Allow a new incoming event processing task to be posted. This can't be
+    // done after AcceptIncomingMessages() otherwise an event might be missed.
     // Doing it here may result in at most two tasks existing at the same time;
     // this running one, and one pending in the task runner.
-    incoming_messages_task_posted_ = false;
+    incoming_events_task_posted_ = false;
   }
 
   AcceptIncomingMessages();
@@ -762,40 +778,35 @@
   GenerateRandomName(port_name);
 }
 
-void NodeController::AllocMessage(size_t num_header_bytes,
-                                  ports::ScopedMessage* message) {
-  message->reset(new PortsMessage(num_header_bytes, 0, 0, nullptr));
-}
-
-void NodeController::ForwardMessage(const ports::NodeName& node,
-                                    ports::ScopedMessage message) {
-  DCHECK(message);
+void NodeController::ForwardEvent(const ports::NodeName& node,
+                                  ports::ScopedEvent event) {
+  DCHECK(event);
   bool schedule_pump_task = false;
   if (node == name_) {
     // NOTE: We need to avoid re-entering the Node instance within
-    // ForwardMessage. Because ForwardMessage is only ever called
-    // (synchronously) in response to Node's ClosePort, SendMessage, or
-    // AcceptMessage, we flush the queue after calling any of those methods.
-    base::AutoLock lock(messages_lock_);
+    // ForwardEvent. Because ForwardEvent is only ever called
+    // (synchronously) in response to Node's ClosePort, SendUserMessage, or
+    // AcceptEvent, we flush the queue after calling any of those methods.
+    base::AutoLock lock(events_lock_);
     // |io_task_runner_| may be null in tests or processes that don't require
     // multi-process Mojo.
-    schedule_pump_task = incoming_messages_.empty() && io_task_runner_ &&
-        !incoming_messages_task_posted_;
-    incoming_messages_task_posted_ |= schedule_pump_task;
-    incoming_messages_.emplace(std::move(message));
-    incoming_messages_flag_.Set(true);
+    schedule_pump_task = incoming_events_.empty() && io_task_runner_ &&
+                         !incoming_events_task_posted_;
+    incoming_events_task_posted_ |= schedule_pump_task;
+    incoming_events_.emplace_back(std::move(event));
+    incoming_events_flag_.Set(true);
   } else {
-    SendPeerMessage(node, std::move(message));
+    SendPeerEvent(node, std::move(event));
   }
 
   if (schedule_pump_task) {
     // Normally, the queue is processed after the action that added the local
-    // message is done (i.e. SendMessage, ClosePort, etc). However, it's also
-    // possible for a local message to be added as a result of a remote message,
-    // and OnChannelMessage() doesn't process this queue (although
-    // OnPortsMessage() does). There may also be other code paths, now or added
-    // in the future, which cause local messages to be added but don't process
-    // this message queue.
+    // event is done (i.e. SendUserMessage, ClosePort, etc). However, it's also
+    // possible for a local event to be added as a result of a remote event, and
+    // OnChannelMessage() doesn't process this queue (although OnEventMessage()
+    // does.) There may also be other code paths, now or added in the future,
+    // which cause local messages to be added but don't process this message
+    // queue.
     //
     // Instead of adding a call to AcceptIncomingMessages() on every possible
     // code path, post a task to the IO thread to process the queue. If the
@@ -807,11 +818,9 @@
   }
 }
 
-void NodeController::BroadcastMessage(ports::ScopedMessage message) {
-  CHECK_EQ(message->num_ports(), 0u);
-  Channel::MessagePtr channel_message =
-      static_cast<PortsMessage*>(message.get())->TakeChannelMessage();
-  CHECK(!channel_message->has_handles());
+void NodeController::BroadcastEvent(ports::ScopedEvent event) {
+  Channel::MessagePtr channel_message = SerializeEventMessage(std::move(event));
+  DCHECK(!channel_message->has_handles());
 
   scoped_refptr<NodeChannel> broker = GetBrokerChannel();
   if (broker)
@@ -1060,7 +1069,7 @@
     const ports::NodeName& destination = entry.first;
     auto& message_queue = entry.second;
     while (!message_queue.empty()) {
-      broker->RelayPortsMessage(destination, std::move(message_queue.front()));
+      broker->RelayEventMessage(destination, std::move(message_queue.front()));
       message_queue.pop();
     }
   }
@@ -1069,27 +1078,19 @@
   DVLOG(1) << "Child " << name_ << " accepted by broker " << broker_name;
 }
 
-void NodeController::OnPortsMessage(const ports::NodeName& from_node,
+void NodeController::OnEventMessage(const ports::NodeName& from_node,
                                     Channel::MessagePtr channel_message) {
   DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
 
-  void* data;
-  size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes;
-  if (!ParsePortsMessage(channel_message.get(), &data, &num_data_bytes,
-                         &num_header_bytes, &num_payload_bytes,
-                         &num_ports_bytes)) {
-    DropPeer(from_node, nullptr);
+  auto event = DeserializeEventMessage(from_node, std::move(channel_message));
+  if (!event) {
+    // We silently ignore unparseable events, as they may come from a process
+    // running a newer version of Mojo.
+    DVLOG(1) << "Ignoring invalid or unknown event from " << from_node;
     return;
   }
 
-  CHECK(channel_message);
-  std::unique_ptr<PortsMessage> ports_message(
-      new PortsMessage(num_header_bytes,
-                       num_payload_bytes,
-                       num_ports_bytes,
-                       std::move(channel_message)));
-  ports_message->set_source_node(from_node);
-  node_->AcceptMessage(ports::ScopedMessage(ports_message.release()));
+  node_->AcceptEvent(std::move(event));
   AcceptIncomingMessages();
 }
 
@@ -1182,34 +1183,33 @@
                                  Channel::MessagePtr message) {
   DCHECK(!message->has_handles());
 
-  void* data;
-  size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes;
-  if (!ParsePortsMessage(message.get(), &data, &num_data_bytes,
-                         &num_header_bytes, &num_payload_bytes,
-                         &num_ports_bytes)) {
-    DropPeer(from_node, nullptr);
-    return;
-  }
-
-  // Broadcast messages must not contain ports.
-  if (num_ports_bytes > 0) {
-    DropPeer(from_node, nullptr);
+  auto event = DeserializeEventMessage(from_node, std::move(message));
+  if (!event) {
+    // We silently ignore unparseable events, as they may come from a process
+    // running a newer version of Mojo.
+    DVLOG(1) << "Ignoring request to broadcast invalid or unknown event from "
+             << from_node;
     return;
   }
 
   base::AutoLock lock(peers_lock_);
   for (auto& iter : peers_) {
-    // Copy and send the message to each known peer.
-    Channel::MessagePtr peer_message(
-        new Channel::Message(message->payload_size(), 0));
-    memcpy(peer_message->mutable_payload(), message->payload(),
-           message->payload_size());
-    iter.second->PortsMessage(std::move(peer_message));
+    // Clone and send the event to each known peer. Events which cannot be
+    // cloned cannot be broadcast.
+    ports::ScopedEvent clone = event->Clone();
+    if (!clone) {
+      DVLOG(1) << "Ignoring request to broadcast invalid event from "
+               << from_node << " [type=" << static_cast<uint32_t>(event->type())
+               << "]";
+      return;
+    }
+
+    iter.second->SendChannelMessage(SerializeEventMessage(std::move(clone)));
   }
 }
 
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node,
+void NodeController::OnRelayEventMessage(const ports::NodeName& from_node,
                                          base::ProcessHandle from_process,
                                          const ports::NodeName& destination,
                                          Channel::MessagePtr message) {
@@ -1263,18 +1263,18 @@
 
   if (destination == name_) {
     // Great, we can deliver this message locally.
-    OnPortsMessage(from_node, std::move(message));
+    OnEventMessage(from_node, std::move(message));
     return;
   }
 
   scoped_refptr<NodeChannel> peer = GetPeerChannel(destination);
   if (peer)
-    peer->PortsMessageFromRelay(from_node, std::move(message));
+    peer->EventMessageFromRelay(from_node, std::move(message));
   else
     DLOG(ERROR) << "Dropping relay message for unknown node " << destination;
 }
 
-void NodeController::OnPortsMessageFromRelay(const ports::NodeName& from_node,
+void NodeController::OnEventMessageFromRelay(const ports::NodeName& from_node,
                                              const ports::NodeName& source_node,
                                              Channel::MessagePtr message) {
   if (GetPeerChannel(from_node) != GetBrokerChannel()) {
@@ -1283,7 +1283,7 @@
     return;
   }
 
-  OnPortsMessage(source_node, std::move(message));
+  OnEventMessage(source_node, std::move(message));
 }
 #endif
 
diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h
index f5eed33e..9d1c816 100644
--- a/mojo/edk/system/node_controller.h
+++ b/mojo/edk/system/node_controller.h
@@ -24,6 +24,7 @@
 #include "mojo/edk/embedder/scoped_platform_handle.h"
 #include "mojo/edk/system/atomic_flag.h"
 #include "mojo/edk/system/node_channel.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/name.h"
 #include "mojo/edk/system/ports/node.h"
 #include "mojo/edk/system/ports/node_delegate.h"
@@ -38,7 +39,6 @@
 class Broker;
 class Core;
 class MachPortRelay;
-class PortsMessage;
 
 // The owner of ports::Node which facilitates core EDK implementation. All
 // public interface methods are safe to call from any thread.
@@ -103,8 +103,8 @@
   void ClosePort(const ports::PortRef& port);
 
   // Sends a message on a port to its peer.
-  int SendMessage(const ports::PortRef& port_ref,
-                  std::unique_ptr<PortsMessage> message);
+  int SendUserMessage(const ports::PortRef& port_ref,
+                      std::unique_ptr<ports::UserMessageEvent> message);
 
   // Merges a local port |port| into a port reserved by |name| in the parent.
   void MergePortIntoParent(const std::string& name, const ports::PortRef& port);
@@ -176,19 +176,16 @@
                scoped_refptr<NodeChannel> channel,
                bool start_channel);
   void DropPeer(const ports::NodeName& name, NodeChannel* channel);
-  void SendPeerMessage(const ports::NodeName& name,
-                       ports::ScopedMessage message);
+  void SendPeerEvent(const ports::NodeName& name, ports::ScopedEvent event);
   void AcceptIncomingMessages();
   void ProcessIncomingMessages();
   void DropAllPeers();
 
   // ports::NodeDelegate:
   void GenerateRandomPortName(ports::PortName* port_name) override;
-  void AllocMessage(size_t num_header_bytes,
-                    ports::ScopedMessage* message) override;
-  void ForwardMessage(const ports::NodeName& node,
-                      ports::ScopedMessage message) override;
-  void BroadcastMessage(ports::ScopedMessage message) override;
+  void ForwardEvent(const ports::NodeName& node,
+                    ports::ScopedEvent event) override;
+  void BroadcastEvent(ports::ScopedEvent event) override;
   void PortStatusChanged(const ports::PortRef& port) override;
 
   // NodeChannel::Delegate:
@@ -207,7 +204,7 @@
   void OnAcceptBrokerClient(const ports::NodeName& from_node,
                             const ports::NodeName& broker_name,
                             ScopedPlatformHandle broker_channel) override;
-  void OnPortsMessage(const ports::NodeName& from_node,
+  void OnEventMessage(const ports::NodeName& from_node,
                       Channel::MessagePtr message) override;
   void OnRequestPortMerge(const ports::NodeName& from_node,
                           const ports::PortName& connector_port_name,
@@ -220,11 +217,11 @@
   void OnBroadcast(const ports::NodeName& from_node,
                    Channel::MessagePtr message) override;
 #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
-  void OnRelayPortsMessage(const ports::NodeName& from_node,
+  void OnRelayEventMessage(const ports::NodeName& from_node,
                            base::ProcessHandle from_process,
                            const ports::NodeName& destination,
                            Channel::MessagePtr message) override;
-  void OnPortsMessageFromRelay(const ports::NodeName& from_node,
+  void OnEventMessageFromRelay(const ports::NodeName& from_node,
                                const ports::NodeName& source_node,
                                Channel::MessagePtr message) override;
 #endif
@@ -311,14 +308,14 @@
   std::unordered_map<ports::NodeName, OutgoingMessageQueue>
       pending_relay_messages_;
 
-  // Guards |incoming_messages_| and |incoming_messages_task_posted_|.
-  base::Lock messages_lock_;
-  std::queue<ports::ScopedMessage> incoming_messages_;
+  // Guards |incoming_events_| and |incoming_events_task_posted_|.
+  base::Lock events_lock_;
+  std::vector<ports::ScopedEvent> incoming_events_;
   // Ensures that there is only one incoming messages task posted to the IO
   // thread.
-  bool incoming_messages_task_posted_ = false;
-  // Flag to fast-path checking |incoming_messages_|.
-  AtomicFlag incoming_messages_flag_;
+  bool incoming_events_task_posted_ = false;
+  // Flag to fast-path checking |incoming_events_|.
+  AtomicFlag incoming_events_flag_;
 
   // Guards |shutdown_callback_|.
   base::Lock shutdown_lock_;
diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn
index 5c82761..b5562d0 100644
--- a/mojo/edk/system/ports/BUILD.gn
+++ b/mojo/edk/system/ports/BUILD.gn
@@ -8,8 +8,6 @@
   sources = [
     "event.cc",
     "event.h",
-    "message.cc",
-    "message.h",
     "message_filter.h",
     "message_queue.cc",
     "message_queue.h",
@@ -23,6 +21,7 @@
     "port_ref.cc",
     "port_ref.h",
     "user_data.h",
+    "user_message.h",
   ]
 
   public_deps = [
diff --git a/mojo/edk/system/ports/event.cc b/mojo/edk/system/ports/event.cc
index 2e22086..78a2168dc 100644
--- a/mojo/edk/system/ports/event.cc
+++ b/mojo/edk/system/ports/event.cc
@@ -4,8 +4,12 @@
 
 #include "mojo/edk/system/ports/event.h"
 
+#include <stdint.h>
 #include <string.h>
 
+#include "base/numerics/safe_math.h"
+#include "mojo/edk/system/ports/user_message.h"
+
 namespace mojo {
 namespace edk {
 namespace ports {
@@ -14,13 +18,49 @@
 
 const size_t kPortsMessageAlignment = 8;
 
-static_assert(sizeof(PortDescriptor) % kPortsMessageAlignment == 0,
+#pragma pack(push, 1)
+
+struct SerializedHeader {
+  Event::Type type;
+  uint32_t padding;
+  PortName port_name;
+};
+
+struct UserMessageEventData {
+  uint64_t sequence_num;
+  uint32_t num_ports;
+  uint32_t padding;
+};
+
+struct ObserveProxyEventData {
+  NodeName proxy_node_name;
+  PortName proxy_port_name;
+  NodeName proxy_target_node_name;
+  PortName proxy_target_port_name;
+};
+
+struct ObserveProxyAckEventData {
+  uint64_t last_sequence_num;
+};
+
+struct ObserveClosureEventData {
+  uint64_t last_sequence_num;
+};
+
+struct MergePortEventData {
+  PortName new_port_name;
+  Event::PortDescriptor new_port_descriptor;
+};
+
+#pragma pack(pop)
+
+static_assert(sizeof(Event::PortDescriptor) % kPortsMessageAlignment == 0,
               "Invalid PortDescriptor size.");
 
-static_assert(sizeof(EventHeader) % kPortsMessageAlignment == 0,
-              "Invalid EventHeader size.");
+static_assert(sizeof(SerializedHeader) % kPortsMessageAlignment == 0,
+              "Invalid SerializedHeader size.");
 
-static_assert(sizeof(UserEventData) % kPortsMessageAlignment == 0,
+static_assert(sizeof(UserMessageEventData) % kPortsMessageAlignment == 0,
               "Invalid UserEventData size.");
 
 static_assert(sizeof(ObserveProxyEventData) % kPortsMessageAlignment == 0,
@@ -37,10 +77,296 @@
 
 }  // namespace
 
-PortDescriptor::PortDescriptor() {
+Event::PortDescriptor::PortDescriptor() {
   memset(padding, 0, sizeof(padding));
 }
 
+Event::~Event() = default;
+
+// static
+ScopedEvent Event::Deserialize(const void* buffer, size_t num_bytes) {
+  if (num_bytes < sizeof(SerializedHeader))
+    return nullptr;
+
+  const auto* header = static_cast<const SerializedHeader*>(buffer);
+  const PortName& port_name = header->port_name;
+  const size_t data_size = num_bytes - sizeof(header);
+  switch (header->type) {
+    case Type::kUserMessage:
+      return UserMessageEvent::Deserialize(port_name, header + 1, data_size);
+    case Type::kPortAccepted:
+      return PortAcceptedEvent::Deserialize(port_name, header + 1, data_size);
+    case Type::kObserveProxy:
+      return ObserveProxyEvent::Deserialize(port_name, header + 1, data_size);
+    case Type::kObserveProxyAck:
+      return ObserveProxyAckEvent::Deserialize(port_name, header + 1,
+                                               data_size);
+    case Type::kObserveClosure:
+      return ObserveClosureEvent::Deserialize(port_name, header + 1, data_size);
+    case Type::kMergePort:
+      return MergePortEvent::Deserialize(port_name, header + 1, data_size);
+    default:
+      DVLOG(2) << "Ingoring unknown port event type: "
+               << static_cast<uint32_t>(header->type);
+      return nullptr;
+  }
+}
+
+Event::Event(Type type, const PortName& port_name)
+    : type_(type), port_name_(port_name) {}
+
+size_t Event::GetSerializedSize() const {
+  return sizeof(SerializedHeader) + GetSerializedDataSize();
+}
+
+void Event::Serialize(void* buffer) const {
+  auto* header = static_cast<SerializedHeader*>(buffer);
+  header->type = type_;
+  header->padding = 0;
+  header->port_name = port_name_;
+  SerializeData(header + 1);
+}
+
+ScopedEvent Event::Clone() const {
+  return nullptr;
+}
+
+UserMessageEvent::~UserMessageEvent() = default;
+
+UserMessageEvent::UserMessageEvent(size_t num_ports)
+    : Event(Type::kUserMessage, kInvalidPortName) {
+  ReservePorts(num_ports);
+}
+
+void UserMessageEvent::AttachMessage(std::unique_ptr<UserMessage> message) {
+  DCHECK(!message_);
+  message_ = std::move(message);
+}
+
+void UserMessageEvent::ReservePorts(size_t num_ports) {
+  port_descriptors_.resize(num_ports);
+  ports_.resize(num_ports);
+}
+
+// static
+ScopedEvent UserMessageEvent::Deserialize(const PortName& port_name,
+                                          const void* buffer,
+                                          size_t num_bytes) {
+  if (num_bytes < sizeof(UserMessageEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const UserMessageEventData*>(buffer);
+  base::CheckedNumeric<size_t> port_data_size = data->num_ports;
+  port_data_size *= sizeof(PortDescriptor) + sizeof(PortName);
+  if (!port_data_size.IsValid())
+    return nullptr;
+
+  base::CheckedNumeric<size_t> total_size = port_data_size.ValueOrDie();
+  total_size += sizeof(UserMessageEventData);
+  if (!total_size.IsValid() || num_bytes < total_size.ValueOrDie())
+    return nullptr;
+
+  auto event =
+      base::WrapUnique(new UserMessageEvent(port_name, data->sequence_num));
+  event->ReservePorts(data->num_ports);
+  const auto* in_descriptors =
+      reinterpret_cast<const PortDescriptor*>(data + 1);
+  std::copy(in_descriptors, in_descriptors + data->num_ports,
+            event->port_descriptors());
+
+  const auto* in_names =
+      reinterpret_cast<const PortName*>(in_descriptors + data->num_ports);
+  std::copy(in_names, in_names + data->num_ports, event->ports());
+  return std::move(event);
+}
+
+UserMessageEvent::UserMessageEvent(const PortName& port_name,
+                                   uint64_t sequence_num)
+    : Event(Type::kUserMessage, port_name), sequence_num_(sequence_num) {}
+
+size_t UserMessageEvent::GetSerializedDataSize() const {
+  DCHECK_EQ(ports_.size(), port_descriptors_.size());
+  base::CheckedNumeric<size_t> size = sizeof(UserMessageEventData);
+  base::CheckedNumeric<size_t> ports_size =
+      sizeof(PortDescriptor) + sizeof(PortName);
+  ports_size *= ports_.size();
+  return (size + ports_size.ValueOrDie()).ValueOrDie();
+}
+
+void UserMessageEvent::SerializeData(void* buffer) const {
+  DCHECK_EQ(ports_.size(), port_descriptors_.size());
+  auto* data = static_cast<UserMessageEventData*>(buffer);
+  data->sequence_num = sequence_num_;
+  DCHECK(base::IsValueInRangeForNumericType<uint32_t>(ports_.size()));
+  data->num_ports = static_cast<uint32_t>(ports_.size());
+  data->padding = 0;
+
+  auto* ports_data = reinterpret_cast<PortDescriptor*>(data + 1);
+  std::copy(port_descriptors_.begin(), port_descriptors_.end(), ports_data);
+
+  auto* port_names_data =
+      reinterpret_cast<PortName*>(ports_data + ports_.size());
+  std::copy(ports_.begin(), ports_.end(), port_names_data);
+}
+
+PortAcceptedEvent::PortAcceptedEvent(const PortName& port_name)
+    : Event(Type::kPortAccepted, port_name) {}
+
+PortAcceptedEvent::~PortAcceptedEvent() = default;
+
+// static
+ScopedEvent PortAcceptedEvent::Deserialize(const PortName& port_name,
+                                           const void* buffer,
+                                           size_t num_bytes) {
+  return base::MakeUnique<PortAcceptedEvent>(port_name);
+}
+
+size_t PortAcceptedEvent::GetSerializedDataSize() const {
+  return 0;
+}
+
+void PortAcceptedEvent::SerializeData(void* buffer) const {}
+
+ObserveProxyEvent::ObserveProxyEvent(const PortName& port_name,
+                                     const NodeName& proxy_node_name,
+                                     const PortName& proxy_port_name,
+                                     const NodeName& proxy_target_node_name,
+                                     const PortName& proxy_target_port_name)
+    : Event(Type::kObserveProxy, port_name),
+      proxy_node_name_(proxy_node_name),
+      proxy_port_name_(proxy_port_name),
+      proxy_target_node_name_(proxy_target_node_name),
+      proxy_target_port_name_(proxy_target_port_name) {}
+
+ObserveProxyEvent::~ObserveProxyEvent() = default;
+
+// static
+ScopedEvent ObserveProxyEvent::Deserialize(const PortName& port_name,
+                                           const void* buffer,
+                                           size_t num_bytes) {
+  if (num_bytes < sizeof(ObserveProxyEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const ObserveProxyEventData*>(buffer);
+  return base::MakeUnique<ObserveProxyEvent>(
+      port_name, data->proxy_node_name, data->proxy_port_name,
+      data->proxy_target_node_name, data->proxy_target_port_name);
+}
+
+size_t ObserveProxyEvent::GetSerializedDataSize() const {
+  return sizeof(ObserveProxyEventData);
+}
+
+void ObserveProxyEvent::SerializeData(void* buffer) const {
+  auto* data = static_cast<ObserveProxyEventData*>(buffer);
+  data->proxy_node_name = proxy_node_name_;
+  data->proxy_port_name = proxy_port_name_;
+  data->proxy_target_node_name = proxy_target_node_name_;
+  data->proxy_target_port_name = proxy_target_port_name_;
+}
+
+ScopedEvent ObserveProxyEvent::Clone() const {
+  return base::MakeUnique<ObserveProxyEvent>(
+      port_name(), proxy_node_name_, proxy_port_name_, proxy_target_node_name_,
+      proxy_target_port_name_);
+}
+
+ObserveProxyAckEvent::ObserveProxyAckEvent(const PortName& port_name,
+                                           uint64_t last_sequence_num)
+    : Event(Type::kObserveProxyAck, port_name),
+      last_sequence_num_(last_sequence_num) {}
+
+ObserveProxyAckEvent::~ObserveProxyAckEvent() = default;
+
+// static
+ScopedEvent ObserveProxyAckEvent::Deserialize(const PortName& port_name,
+                                              const void* buffer,
+                                              size_t num_bytes) {
+  if (num_bytes < sizeof(ObserveProxyAckEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const ObserveProxyAckEventData*>(buffer);
+  return base::MakeUnique<ObserveProxyAckEvent>(port_name,
+                                                data->last_sequence_num);
+}
+
+size_t ObserveProxyAckEvent::GetSerializedDataSize() const {
+  return sizeof(ObserveProxyAckEventData);
+}
+
+void ObserveProxyAckEvent::SerializeData(void* buffer) const {
+  auto* data = static_cast<ObserveProxyAckEventData*>(buffer);
+  data->last_sequence_num = last_sequence_num_;
+}
+
+ScopedEvent ObserveProxyAckEvent::Clone() const {
+  return base::MakeUnique<ObserveProxyAckEvent>(port_name(),
+                                                last_sequence_num_);
+}
+
+ObserveClosureEvent::ObserveClosureEvent(const PortName& port_name,
+                                         uint64_t last_sequence_num)
+    : Event(Type::kObserveClosure, port_name),
+      last_sequence_num_(last_sequence_num) {}
+
+ObserveClosureEvent::~ObserveClosureEvent() = default;
+
+// static
+ScopedEvent ObserveClosureEvent::Deserialize(const PortName& port_name,
+                                             const void* buffer,
+                                             size_t num_bytes) {
+  if (num_bytes < sizeof(ObserveClosureEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const ObserveClosureEventData*>(buffer);
+  return base::MakeUnique<ObserveClosureEvent>(port_name,
+                                               data->last_sequence_num);
+}
+
+size_t ObserveClosureEvent::GetSerializedDataSize() const {
+  return sizeof(ObserveClosureEventData);
+}
+
+void ObserveClosureEvent::SerializeData(void* buffer) const {
+  auto* data = static_cast<ObserveClosureEventData*>(buffer);
+  data->last_sequence_num = last_sequence_num_;
+}
+
+ScopedEvent ObserveClosureEvent::Clone() const {
+  return base::MakeUnique<ObserveClosureEvent>(port_name(), last_sequence_num_);
+}
+
+MergePortEvent::MergePortEvent(const PortName& port_name,
+                               const PortName& new_port_name,
+                               const PortDescriptor& new_port_descriptor)
+    : Event(Type::kMergePort, port_name),
+      new_port_name_(new_port_name),
+      new_port_descriptor_(new_port_descriptor) {}
+
+MergePortEvent::~MergePortEvent() = default;
+
+// static
+ScopedEvent MergePortEvent::Deserialize(const PortName& port_name,
+                                        const void* buffer,
+                                        size_t num_bytes) {
+  if (num_bytes < sizeof(MergePortEventData))
+    return nullptr;
+
+  const auto* data = static_cast<const MergePortEventData*>(buffer);
+  return base::MakeUnique<MergePortEvent>(port_name, data->new_port_name,
+                                          data->new_port_descriptor);
+}
+
+size_t MergePortEvent::GetSerializedDataSize() const {
+  return sizeof(MergePortEventData);
+}
+
+void MergePortEvent::SerializeData(void* buffer) const {
+  auto* data = static_cast<MergePortEventData*>(buffer);
+  data->new_port_name = new_port_name_;
+  data->new_port_descriptor = new_port_descriptor_;
+}
+
 }  // namespace ports
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/system/ports/event.h b/mojo/edk/system/ports/event.h
index a66dfc1..3e2871c 100644
--- a/mojo/edk/system/ports/event.h
+++ b/mojo/edk/system/ports/event.h
@@ -7,102 +7,247 @@
 
 #include <stdint.h>
 
-#include "mojo/edk/system/ports/message.h"
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
 #include "mojo/edk/system/ports/name.h"
+#include "mojo/edk/system/ports/user_message.h"
 
 namespace mojo {
 namespace edk {
 namespace ports {
 
+class Event;
+
+using ScopedEvent = std::unique_ptr<Event>;
+
+class Event {
+ public:
+  enum Type : uint32_t {
+    kUserMessage,
+    kPortAccepted,
+    kObserveProxy,
+    kObserveProxyAck,
+    kObserveClosure,
+    kMergePort,
+  };
+
 #pragma pack(push, 1)
+  struct PortDescriptor {
+    PortDescriptor();
 
-// TODO: Add static assertions of alignment.
-
-struct PortDescriptor {
-  PortDescriptor();
-
-  NodeName peer_node_name;
-  PortName peer_port_name;
-  NodeName referring_node_name;
-  PortName referring_port_name;
-  uint64_t next_sequence_num_to_send;
-  uint64_t next_sequence_num_to_receive;
-  uint64_t last_sequence_num_to_receive;
-  bool peer_closed;
-  char padding[7];
-};
-
-enum struct EventType : uint32_t {
-  kUser,
-  kPortAccepted,
-  kObserveProxy,
-  kObserveProxyAck,
-  kObserveClosure,
-  kMergePort,
-};
-
-struct EventHeader {
-  EventType type;
-  uint32_t padding;
-  PortName port_name;
-};
-
-struct UserEventData {
-  uint64_t sequence_num;
-  uint32_t num_ports;
-  uint32_t padding;
-};
-
-struct ObserveProxyEventData {
-  NodeName proxy_node_name;
-  PortName proxy_port_name;
-  NodeName proxy_to_node_name;
-  PortName proxy_to_port_name;
-};
-
-struct ObserveProxyAckEventData {
-  uint64_t last_sequence_num;
-};
-
-struct ObserveClosureEventData {
-  uint64_t last_sequence_num;
-};
-
-struct MergePortEventData {
-  PortName new_port_name;
-  PortDescriptor new_port_descriptor;
-};
-
+    NodeName peer_node_name;
+    PortName peer_port_name;
+    NodeName referring_node_name;
+    PortName referring_port_name;
+    uint64_t next_sequence_num_to_send;
+    uint64_t next_sequence_num_to_receive;
+    uint64_t last_sequence_num_to_receive;
+    bool peer_closed;
+    char padding[7];
+  };
 #pragma pack(pop)
+  virtual ~Event();
 
-inline const EventHeader* GetEventHeader(const Message& message) {
-  return static_cast<const EventHeader*>(message.header_bytes());
-}
+  static ScopedEvent Deserialize(const void* buffer, size_t num_bytes);
 
-inline EventHeader* GetMutableEventHeader(Message* message) {
-  return static_cast<EventHeader*>(message->mutable_header_bytes());
-}
+  template <typename T>
+  static std::unique_ptr<T> Cast(ScopedEvent* event) {
+    return base::WrapUnique(static_cast<T*>(event->release()));
+  }
 
-template <typename EventData>
-inline const EventData* GetEventData(const Message& message) {
-  return reinterpret_cast<const EventData*>(
-      reinterpret_cast<const char*>(GetEventHeader(message) + 1));
-}
+  Type type() const { return type_; }
+  const PortName& port_name() const { return port_name_; }
+  void set_port_name(const PortName& port_name) { port_name_ = port_name; }
 
-template <typename EventData>
-inline EventData* GetMutableEventData(Message* message) {
-  return reinterpret_cast<EventData*>(
-      reinterpret_cast<char*>(GetMutableEventHeader(message) + 1));
-}
+  size_t GetSerializedSize() const;
+  void Serialize(void* buffer) const;
+  virtual ScopedEvent Clone() const;
 
-inline const PortDescriptor* GetPortDescriptors(const UserEventData* event) {
-  return reinterpret_cast<const PortDescriptor*>(
-      reinterpret_cast<const char*>(event + 1));
-}
+ protected:
+  Event(Type type, const PortName& port_name);
 
-inline PortDescriptor* GetMutablePortDescriptors(UserEventData* event) {
-  return reinterpret_cast<PortDescriptor*>(reinterpret_cast<char*>(event + 1));
-}
+  virtual size_t GetSerializedDataSize() const = 0;
+  virtual void SerializeData(void* buffer) const = 0;
+
+ private:
+  const Type type_;
+  PortName port_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(Event);
+};
+
+class UserMessageEvent : public Event {
+ public:
+  explicit UserMessageEvent(size_t num_ports);
+  ~UserMessageEvent() override;
+
+  bool HasMessage() const { return !!message_; }
+  void AttachMessage(std::unique_ptr<UserMessage> message);
+
+  template <typename T>
+  T* GetMessage() {
+    DCHECK(HasMessage());
+    DCHECK_EQ(&T::kUserMessageTypeInfo, message_->type_info());
+    return static_cast<T*>(message_.get());
+  }
+
+  template <typename T>
+  const T* GetMessage() const {
+    DCHECK(HasMessage());
+    DCHECK_EQ(&T::kUserMessageTypeInfo, message_->type_info());
+    return static_cast<const T*>(message_.get());
+  }
+
+  void ReservePorts(size_t num_ports);
+
+  uint32_t sequence_num() const { return sequence_num_; }
+  void set_sequence_num(uint32_t sequence_num) { sequence_num_ = sequence_num; }
+
+  size_t num_ports() const { return ports_.size(); }
+  PortDescriptor* port_descriptors() { return port_descriptors_.data(); }
+  PortName* ports() { return ports_.data(); }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  UserMessageEvent(const PortName& port_name, uint64_t sequence_num);
+
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+
+  uint64_t sequence_num_ = 0;
+  std::vector<PortDescriptor> port_descriptors_;
+  std::vector<PortName> ports_;
+  std::unique_ptr<UserMessage> message_;
+
+  DISALLOW_COPY_AND_ASSIGN(UserMessageEvent);
+};
+
+class PortAcceptedEvent : public Event {
+ public:
+  explicit PortAcceptedEvent(const PortName& port_name);
+  ~PortAcceptedEvent() override;
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(PortAcceptedEvent);
+};
+
+class ObserveProxyEvent : public Event {
+ public:
+  ObserveProxyEvent(const PortName& port_name,
+                    const NodeName& proxy_node_name,
+                    const PortName& proxy_port_name,
+                    const NodeName& proxy_target_node_name,
+                    const PortName& proxy_target_port_name);
+  ~ObserveProxyEvent() override;
+
+  const NodeName& proxy_node_name() const { return proxy_node_name_; }
+  const PortName& proxy_port_name() const { return proxy_port_name_; }
+  const NodeName& proxy_target_node_name() const {
+    return proxy_target_node_name_;
+  }
+  const PortName& proxy_target_port_name() const {
+    return proxy_target_port_name_;
+  }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+  ScopedEvent Clone() const override;
+
+  const NodeName proxy_node_name_;
+  const PortName proxy_port_name_;
+  const NodeName proxy_target_node_name_;
+  const PortName proxy_target_port_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(ObserveProxyEvent);
+};
+
+class ObserveProxyAckEvent : public Event {
+ public:
+  ObserveProxyAckEvent(const PortName& port_name, uint64_t last_sequence_num);
+  ~ObserveProxyAckEvent() override;
+
+  uint64_t last_sequence_num() const { return last_sequence_num_; }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+  ScopedEvent Clone() const override;
+
+  const uint64_t last_sequence_num_;
+
+  DISALLOW_COPY_AND_ASSIGN(ObserveProxyAckEvent);
+};
+
+class ObserveClosureEvent : public Event {
+ public:
+  ObserveClosureEvent(const PortName& port_name, uint64_t last_sequence_num);
+  ~ObserveClosureEvent() override;
+
+  uint64_t last_sequence_num() const { return last_sequence_num_; }
+  void set_last_sequence_num(uint64_t last_sequence_num) {
+    last_sequence_num_ = last_sequence_num;
+  }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+  ScopedEvent Clone() const override;
+
+  uint64_t last_sequence_num_;
+
+  DISALLOW_COPY_AND_ASSIGN(ObserveClosureEvent);
+};
+
+class MergePortEvent : public Event {
+ public:
+  MergePortEvent(const PortName& port_name,
+                 const PortName& new_port_name,
+                 const PortDescriptor& new_port_descriptor);
+  ~MergePortEvent() override;
+
+  const PortName& new_port_name() const { return new_port_name_; }
+  const PortDescriptor& new_port_descriptor() const {
+    return new_port_descriptor_;
+  }
+
+  static ScopedEvent Deserialize(const PortName& port_name,
+                                 const void* buffer,
+                                 size_t num_bytes);
+
+ private:
+  size_t GetSerializedDataSize() const override;
+  void SerializeData(void* buffer) const override;
+
+  const PortName new_port_name_;
+  const PortDescriptor new_port_descriptor_;
+
+  DISALLOW_COPY_AND_ASSIGN(MergePortEvent);
+};
 
 }  // namespace ports
 }  // namespace edk
diff --git a/mojo/edk/system/ports/message.cc b/mojo/edk/system/ports/message.cc
deleted file mode 100644
index 5d3c000..0000000
--- a/mojo/edk/system/ports/message.cc
+++ /dev/null
@@ -1,100 +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 <stdlib.h>
-
-#include <limits>
-
-#include "base/logging.h"
-#include "mojo/edk/system/ports/event.h"
-
-namespace mojo {
-namespace edk {
-namespace ports {
-
-// static
-bool Message::Parse(const void* bytes,
-                    size_t num_bytes,
-                    size_t* num_header_bytes,
-                    size_t* num_payload_bytes,
-                    size_t* num_ports_bytes) {
-  if (num_bytes < sizeof(EventHeader))
-    return false;
-  const EventHeader* header = static_cast<const EventHeader*>(bytes);
-  switch (header->type) {
-    case EventType::kUser:
-      // See below.
-      break;
-    case EventType::kPortAccepted:
-      *num_header_bytes = sizeof(EventHeader);
-      break;
-    case EventType::kObserveProxy:
-      *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveProxyEventData);
-      break;
-    case EventType::kObserveProxyAck:
-      *num_header_bytes =
-          sizeof(EventHeader) + sizeof(ObserveProxyAckEventData);
-      break;
-    case EventType::kObserveClosure:
-      *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveClosureEventData);
-      break;
-    case EventType::kMergePort:
-      *num_header_bytes = sizeof(EventHeader) + sizeof(MergePortEventData);
-      break;
-    default:
-      return false;
-  }
-
-  if (header->type == EventType::kUser) {
-    if (num_bytes < sizeof(EventHeader) + sizeof(UserEventData))
-      return false;
-    const UserEventData* event_data =
-        reinterpret_cast<const UserEventData*>(
-            reinterpret_cast<const char*>(header + 1));
-    if (event_data->num_ports > std::numeric_limits<uint16_t>::max())
-      return false;
-    *num_header_bytes = sizeof(EventHeader) +
-                        sizeof(UserEventData) +
-                        event_data->num_ports * sizeof(PortDescriptor);
-    *num_ports_bytes = event_data->num_ports * sizeof(PortName);
-    if (num_bytes < *num_header_bytes + *num_ports_bytes)
-      return false;
-    *num_payload_bytes = num_bytes - *num_header_bytes - *num_ports_bytes;
-  } else {
-    if (*num_header_bytes != num_bytes)
-      return false;
-    *num_payload_bytes = 0;
-    *num_ports_bytes = 0;
-  }
-
-  return true;
-}
-
-Message::Message(size_t num_payload_bytes, size_t num_ports)
-    : Message(sizeof(EventHeader) + sizeof(UserEventData) +
-                  num_ports * sizeof(PortDescriptor),
-              num_payload_bytes, num_ports * sizeof(PortName)) {
-  num_ports_ = num_ports;
-}
-
-Message::Message(size_t num_header_bytes,
-                 size_t num_payload_bytes,
-                 size_t num_ports_bytes)
-    : start_(nullptr),
-      num_header_bytes_(num_header_bytes),
-      num_ports_bytes_(num_ports_bytes),
-      num_payload_bytes_(num_payload_bytes) {
-}
-
-void Message::InitializeUserMessageHeader(void* start) {
-  start_ = static_cast<char*>(start);
-  memset(start_, 0, num_header_bytes_);
-  GetMutableEventHeader(this)->type = EventType::kUser;
-  GetMutableEventData<UserEventData>(this)->num_ports =
-      static_cast<uint32_t>(num_ports_);
-}
-
-}  // namespace ports
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/ports/message.h b/mojo/edk/system/ports/message.h
deleted file mode 100644
index 95fa046..0000000
--- a/mojo/edk/system/ports/message.h
+++ /dev/null
@@ -1,93 +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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_
-#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_
-
-#include <stddef.h>
-
-#include <memory>
-
-#include "mojo/edk/system/ports/name.h"
-
-namespace mojo {
-namespace edk {
-namespace ports {
-
-// A message consists of a header (array of bytes), payload (array of bytes)
-// and an array of ports. The header is used by the Node implementation.
-//
-// This class is designed to be subclassed, and the subclass is responsible for
-// providing the underlying storage. The header size will be aligned, and it
-// should be followed in memory by the array of ports and finally the payload.
-//
-// NOTE: This class does not manage the lifetime of the ports it references.
-class Message {
- public:
-  virtual ~Message() {}
-
-  // Inspect the message at |bytes| and return the size of each section. Returns
-  // |false| if the message is malformed and |true| otherwise.
-  static bool Parse(const void* bytes,
-                    size_t num_bytes,
-                    size_t* num_header_bytes,
-                    size_t* num_payload_bytes,
-                    size_t* num_ports_bytes);
-
-  void* mutable_header_bytes() { return start_; }
-  const void* header_bytes() const { return start_; }
-  size_t num_header_bytes() const { return num_header_bytes_; }
-
-  void* mutable_payload_bytes() {
-    return start_ + num_header_bytes_ + num_ports_bytes_;
-  }
-  const void* payload_bytes() const {
-    return const_cast<Message*>(this)->mutable_payload_bytes();
-  }
-  size_t num_payload_bytes() const { return num_payload_bytes_; }
-
-  PortName* mutable_ports() {
-    return reinterpret_cast<PortName*>(start_ + num_header_bytes_);
-  }
-  const PortName* ports() const {
-    return const_cast<Message*>(this)->mutable_ports();
-  }
-  size_t num_ports_bytes() const { return num_ports_bytes_; }
-  size_t num_ports() const { return num_ports_bytes_ / sizeof(PortName); }
-
- protected:
-  // Constructs a new Message base for a user message.
-  //
-  // Note: You MUST call InitializeUserMessageHeader() before this Message is
-  // ready for transmission.
-  Message(size_t num_payload_bytes, size_t num_ports);
-
-  // Constructs a new Message base for an internal message. Do NOT call
-  // InitializeUserMessageHeader() when using this constructor.
-  Message(size_t num_header_bytes,
-          size_t num_payload_bytes,
-          size_t num_ports_bytes);
-
-  Message(const Message& other) = delete;
-  void operator=(const Message& other) = delete;
-
-  // Initializes the header in a newly allocated message buffer to carry a
-  // user message.
-  void InitializeUserMessageHeader(void* start);
-
-  // Note: storage is [header][ports][payload].
-  char* start_ = nullptr;
-  size_t num_ports_ = 0;
-  size_t num_header_bytes_ = 0;
-  size_t num_ports_bytes_ = 0;
-  size_t num_payload_bytes_ = 0;
-};
-
-using ScopedMessage = std::unique_ptr<Message>;
-
-}  // namespace ports
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_
diff --git a/mojo/edk/system/ports/message_filter.h b/mojo/edk/system/ports/message_filter.h
index bf8fa21..1b1f2ce 100644
--- a/mojo/edk/system/ports/message_filter.h
+++ b/mojo/edk/system/ports/message_filter.h
@@ -9,17 +9,17 @@
 namespace edk {
 namespace ports {
 
-class Message;
+class UserMessageEvent;
 
-// An interface which can be implemented to filter port messages according to
+// An interface which can be implemented to user message events according to
 // arbitrary policy.
 class MessageFilter {
  public:
   virtual ~MessageFilter() {}
 
-  // Returns true of |message| should be accepted by whomever is applying this
+  // Returns true if |message| should be accepted by whomever is applying this
   // filter. See MessageQueue::GetNextMessage(), for example.
-  virtual bool Match(const Message& message) = 0;
+  virtual bool Match(const UserMessageEvent& message) = 0;
 };
 
 }  // namespace ports
diff --git a/mojo/edk/system/ports/message_queue.cc b/mojo/edk/system/ports/message_queue.cc
index defb1b6..f1a9719 100644
--- a/mojo/edk/system/ports/message_queue.cc
+++ b/mojo/edk/system/ports/message_queue.cc
@@ -7,20 +7,16 @@
 #include <algorithm>
 
 #include "base/logging.h"
-#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/message_filter.h"
 
 namespace mojo {
 namespace edk {
 namespace ports {
 
-inline uint64_t GetSequenceNum(const ScopedMessage& message) {
-  return GetEventData<UserEventData>(*message)->sequence_num;
-}
-
 // Used by std::{push,pop}_heap functions
-inline bool operator<(const ScopedMessage& a, const ScopedMessage& b) {
-  return GetSequenceNum(a) > GetSequenceNum(b);
+inline bool operator<(const std::unique_ptr<UserMessageEvent>& a,
+                      const std::unique_ptr<UserMessageEvent>& b) {
+  return a->sequence_num() > b->sequence_num();
 }
 
 MessageQueue::MessageQueue() : MessageQueue(kInitialSequenceNum) {}
@@ -42,12 +38,12 @@
 }
 
 bool MessageQueue::HasNextMessage() const {
-  return !heap_.empty() && GetSequenceNum(heap_[0]) == next_sequence_num_;
+  return !heap_.empty() && heap_[0]->sequence_num() == next_sequence_num_;
 }
 
-void MessageQueue::GetNextMessage(ScopedMessage* message,
+void MessageQueue::GetNextMessage(std::unique_ptr<UserMessageEvent>* message,
                                   MessageFilter* filter) {
-  if (!HasNextMessage() || (filter && !filter->Match(*heap_[0].get()))) {
+  if (!HasNextMessage() || (filter && !filter->Match(*heap_[0]))) {
     message->reset();
     return;
   }
@@ -59,10 +55,8 @@
   next_sequence_num_++;
 }
 
-void MessageQueue::AcceptMessage(ScopedMessage message,
+void MessageQueue::AcceptMessage(std::unique_ptr<UserMessageEvent> message,
                                  bool* has_next_message) {
-  DCHECK(GetEventHeader(*message)->type == EventType::kUser);
-
   // TODO: Handle sequence number roll-over.
 
   heap_.emplace_back(std::move(message));
@@ -71,7 +65,7 @@
   if (!signalable_) {
     *has_next_message = false;
   } else {
-    *has_next_message = (GetSequenceNum(heap_[0]) == next_sequence_num_);
+    *has_next_message = (heap_[0]->sequence_num() == next_sequence_num_);
   }
 }
 
diff --git a/mojo/edk/system/ports/message_queue.h b/mojo/edk/system/ports/message_queue.h
index d9a47ed..8ff3ab9 100644
--- a/mojo/edk/system/ports/message_queue.h
+++ b/mojo/edk/system/ports/message_queue.h
@@ -8,12 +8,12 @@
 #include <stdint.h>
 
 #include <deque>
-#include <functional>
 #include <limits>
+#include <memory>
 #include <vector>
 
 #include "base/macros.h"
-#include "mojo/edk/system/ports/message.h"
+#include "mojo/edk/system/ports/event.h"
 
 namespace mojo {
 namespace edk {
@@ -42,7 +42,8 @@
 
   // Gives ownership of the message. If |filter| is non-null, the next message
   // will only be retrieved if the filter successfully matches it.
-  void GetNextMessage(ScopedMessage* message, MessageFilter* filter);
+  void GetNextMessage(std::unique_ptr<UserMessageEvent>* message,
+                      MessageFilter* filter);
 
   // Takes ownership of the message. Note: Messages are ordered, so while we
   // have added a message to the queue, we may still be waiting on a message
@@ -53,13 +54,14 @@
   // until GetNextMessage is called enough times to return a null message.
   // In other words, has_next_message acts like an edge trigger.
   //
-  void AcceptMessage(ScopedMessage message, bool* has_next_message);
+  void AcceptMessage(std::unique_ptr<UserMessageEvent> message,
+                     bool* has_next_message);
 
   // Returns all of the ports referenced by messages in this message queue.
   void GetReferencedPorts(std::deque<PortName>* ports);
 
  private:
-  std::vector<ScopedMessage> heap_;
+  std::vector<std::unique_ptr<UserMessageEvent>> heap_;
   uint64_t next_sequence_num_;
   bool signalable_ = true;
 
diff --git a/mojo/edk/system/ports/node.cc b/mojo/edk/system/ports/node.cc
index 9c6f1fd9..f6217a7 100644
--- a/mojo/edk/system/ports/node.cc
+++ b/mojo/edk/system/ports/node.cc
@@ -12,6 +12,7 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/node_delegate.h"
 
 namespace mojo {
@@ -197,10 +198,9 @@
 int Node::ClosePort(const PortRef& port_ref) {
   std::deque<PortName> referenced_port_names;
 
-  ObserveClosureEventData data;
-
   NodeName peer_node_name;
   PortName peer_port_name;
+  uint64_t last_sequence_num = 0;
   Port* port = port_ref.port();
   {
     // We may need to erase the port, which requires ports_lock_ to be held,
@@ -222,7 +222,7 @@
     // We pass along the sequence number of the last message sent from this
     // port to allow the peer to have the opportunity to consume all inbound
     // messages before notifying the embedder that this port is closed.
-    data.last_sequence_num = port->next_sequence_num_to_send - 1;
+    last_sequence_num = port->next_sequence_num_to_send - 1;
 
     peer_node_name = port->peer_node_name;
     peer_port_name = port->peer_port_name;
@@ -237,9 +237,9 @@
   DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@" << name_
            << " to " << peer_port_name << "@" << peer_node_name;
 
-  delegate_->ForwardMessage(
+  delegate_->ForwardEvent(
       peer_node_name,
-      NewInternalMessage(peer_port_name, EventType::kObserveClosure, data));
+      base::MakeUnique<ObserveClosureEvent>(peer_port_name, last_sequence_num));
 
   for (const auto& name : referenced_port_names) {
     PortRef ref;
@@ -264,7 +264,7 @@
 }
 
 int Node::GetMessage(const PortRef& port_ref,
-                     ScopedMessage* message,
+                     std::unique_ptr<UserMessageEvent>* message,
                      MessageFilter* filter) {
   *message = nullptr;
 
@@ -306,8 +306,9 @@
   return OK;
 }
 
-int Node::SendMessage(const PortRef& port_ref, ScopedMessage message) {
-  int rv = SendMessageInternal(port_ref, &message);
+int Node::SendUserMessage(const PortRef& port_ref,
+                          std::unique_ptr<UserMessageEvent> message) {
+  int rv = SendUserMessageInternal(port_ref, &message);
   if (rv != OK) {
     // If send failed, close all carried ports. Note that we're careful not to
     // close the sending port itself if it happened to be one of the encoded
@@ -324,28 +325,20 @@
   return rv;
 }
 
-int Node::AcceptMessage(ScopedMessage message) {
-  const EventHeader* header = GetEventHeader(*message);
-  switch (header->type) {
-    case EventType::kUser:
-      return OnUserMessage(std::move(message));
-    case EventType::kPortAccepted:
-      return OnPortAccepted(header->port_name);
-    case EventType::kObserveProxy:
-      return OnObserveProxy(
-          header->port_name,
-          *GetEventData<ObserveProxyEventData>(*message));
-    case EventType::kObserveProxyAck:
-      return OnObserveProxyAck(
-          header->port_name,
-          GetEventData<ObserveProxyAckEventData>(*message)->last_sequence_num);
-    case EventType::kObserveClosure:
-      return OnObserveClosure(
-          header->port_name,
-          GetEventData<ObserveClosureEventData>(*message)->last_sequence_num);
-    case EventType::kMergePort:
-      return OnMergePort(header->port_name,
-                         *GetEventData<MergePortEventData>(*message));
+int Node::AcceptEvent(ScopedEvent event) {
+  switch (event->type()) {
+    case Event::Type::kUserMessage:
+      return OnUserMessage(Event::Cast<UserMessageEvent>(&event));
+    case Event::Type::kPortAccepted:
+      return OnPortAccepted(Event::Cast<PortAcceptedEvent>(&event));
+    case Event::Type::kObserveProxy:
+      return OnObserveProxy(Event::Cast<ObserveProxyEvent>(&event));
+    case Event::Type::kObserveProxyAck:
+      return OnObserveProxyAck(Event::Cast<ObserveProxyAckEvent>(&event));
+    case Event::Type::kObserveClosure:
+      return OnObserveClosure(Event::Cast<ObserveClosureEvent>(&event));
+    case Event::Type::kMergePort:
+      return OnMergePort(Event::Cast<MergePortEvent>(&event));
   }
   return OOPS(ERROR_NOT_IMPLEMENTED);
 }
@@ -354,7 +347,8 @@
                      const NodeName& destination_node_name,
                      const PortName& destination_port_name) {
   Port* port = port_ref.port();
-  MergePortEventData data;
+  PortName new_port_name;
+  Event::PortDescriptor new_port_descriptor;
   {
     base::AutoLock lock(port->lock);
 
@@ -363,14 +357,14 @@
 
     // Send the port-to-merge over to the destination node so it can be merged
     // into the port cycle atomically there.
-    data.new_port_name = port_ref.name();
-    WillSendPort(LockedPort(port), destination_node_name, &data.new_port_name,
-                 &data.new_port_descriptor);
+    new_port_name = port_ref.name();
+    WillSendPort(LockedPort(port), destination_node_name, &new_port_name,
+                 &new_port_descriptor);
   }
-  delegate_->ForwardMessage(
+  delegate_->ForwardEvent(
       destination_node_name,
-      NewInternalMessage(destination_port_name,
-                         EventType::kMergePort, data));
+      base::MakeUnique<MergePortEvent>(destination_port_name, new_port_name,
+                                       new_port_descriptor));
   return OK;
 }
 
@@ -412,9 +406,8 @@
   return OK;
 }
 
-int Node::OnUserMessage(ScopedMessage message) {
-  PortName port_name = GetEventHeader(*message)->port_name;
-  const auto* event = GetEventData<UserEventData>(*message);
+int Node::OnUserMessage(std::unique_ptr<UserMessageEvent> message) {
+  PortName port_name = message->port_name();
 
 #if DCHECK_IS_ON()
   std::ostringstream ports_buf;
@@ -424,9 +417,9 @@
     ports_buf << message->ports()[i];
   }
 
-  DVLOG(4) << "AcceptMessage " << event->sequence_num
-             << " [ports=" << ports_buf.str() << "] at "
-             << port_name << "@" << name_;
+  DVLOG(4) << "OnUserMessage " << message->sequence_num()
+           << " [ports=" << ports_buf.str() << "] at " << port_name << "@"
+           << name_;
 #endif
 
   scoped_refptr<Port> port = GetPort(port_name);
@@ -438,7 +431,7 @@
   // newly bound ports will simply be closed.
 
   for (size_t i = 0; i < message->num_ports(); ++i) {
-    int rv = AcceptPort(message->ports()[i], GetPortDescriptors(event)[i]);
+    int rv = AcceptPort(message->ports()[i], message->port_descriptors()[i]);
     if (rv != OK)
       return rv;
   }
@@ -495,20 +488,20 @@
   return OK;
 }
 
-int Node::OnPortAccepted(const PortName& port_name) {
+int Node::OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event) {
+  const PortName& port_name = event->port_name();
   scoped_refptr<Port> port = GetPort(port_name);
   if (!port)
     return ERROR_PORT_UNKNOWN;
 
-  DVLOG(2) << "PortAccepted at " << port_name << "@" << name_
-           << " pointing to "
+  DVLOG(2) << "PortAccepted at " << port_name << "@" << name_ << " pointing to "
            << port->peer_port_name << "@" << port->peer_node_name;
 
   return BeginProxying(PortRef(port_name, std::move(port)));
 }
 
-int Node::OnObserveProxy(const PortName& port_name,
-                         const ObserveProxyEventData& event) {
+int Node::OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event) {
+  const PortName& port_name = event->port_name();
   if (port_name == kInvalidPortName) {
     // An ObserveProxy with an invalid target port name is a broadcast used to
     // inform ports when their peer (which was itself a proxy) has become
@@ -517,9 +510,9 @@
     // Receiving ports affected by this treat it as equivalent to peer closure.
     // Proxies affected by this can be removed and will in turn broadcast their
     // own death with a similar message.
-    CHECK_EQ(event.proxy_to_node_name, kInvalidNodeName);
-    CHECK_EQ(event.proxy_to_port_name, kInvalidPortName);
-    DestroyAllPortsWithPeer(event.proxy_node_name, event.proxy_port_name);
+    DCHECK_EQ(event->proxy_target_node_name(), kInvalidNodeName);
+    DCHECK_EQ(event->proxy_target_port_name(), kInvalidPortName);
+    DestroyAllPortsWithPeer(event->proxy_node_name(), event->proxy_port_name());
     return OK;
   }
 
@@ -533,28 +526,22 @@
   }
 
   DVLOG(2) << "ObserveProxy at " << port_name << "@" << name_ << ", proxy at "
-           << event.proxy_port_name << "@"
-           << event.proxy_node_name << " pointing to "
-           << event.proxy_to_port_name << "@"
-           << event.proxy_to_node_name;
+           << event->proxy_port_name() << "@" << event->proxy_node_name()
+           << " pointing to " << event->proxy_target_port_name() << "@"
+           << event->proxy_target_node_name();
 
   {
     base::AutoLock lock(port->lock);
 
-    if (port->peer_node_name == event.proxy_node_name &&
-        port->peer_port_name == event.proxy_port_name) {
+    if (port->peer_node_name == event->proxy_node_name() &&
+        port->peer_port_name == event->proxy_port_name()) {
       if (port->state == Port::kReceiving) {
-        port->peer_node_name = event.proxy_to_node_name;
-        port->peer_port_name = event.proxy_to_port_name;
-
-        ObserveProxyAckEventData ack;
-        ack.last_sequence_num = port->next_sequence_num_to_send - 1;
-
-        delegate_->ForwardMessage(
-            event.proxy_node_name,
-            NewInternalMessage(event.proxy_port_name,
-                               EventType::kObserveProxyAck,
-                               ack));
+        port->peer_node_name = event->proxy_target_node_name();
+        port->peer_port_name = event->proxy_target_port_name();
+        delegate_->ForwardEvent(
+            event->proxy_node_name(),
+            base::MakeUnique<ObserveProxyAckEvent>(
+                event->proxy_port_name(), port->next_sequence_num_to_send - 1));
       } else {
         // As a proxy ourselves, we don't know how to honor the ObserveProxy
         // event or to populate the last_sequence_num field of ObserveProxyAck.
@@ -566,36 +553,29 @@
         // Otherwise, we might just find ourselves back here again, which
         // would be akin to a busy loop.
 
-        DVLOG(2) << "Delaying ObserveProxyAck to "
-                 << event.proxy_port_name << "@" << event.proxy_node_name;
+        DVLOG(2) << "Delaying ObserveProxyAck to " << event->proxy_port_name()
+                 << "@" << event->proxy_node_name();
 
-        ObserveProxyAckEventData ack;
-        ack.last_sequence_num = kInvalidSequenceNum;
-
-        port->send_on_proxy_removal.reset(
-            new std::pair<NodeName, ScopedMessage>(
-                event.proxy_node_name,
-                NewInternalMessage(event.proxy_port_name,
-                                   EventType::kObserveProxyAck,
-                                   ack)));
+        port->send_on_proxy_removal.reset(new std::pair<NodeName, ScopedEvent>(
+            event->proxy_node_name(),
+            base::MakeUnique<ObserveProxyAckEvent>(event->proxy_port_name(),
+                                                   kInvalidSequenceNum)));
       }
     } else {
       // Forward this event along to our peer. Eventually, it should find the
       // port referring to the proxy.
-      delegate_->ForwardMessage(
-          port->peer_node_name,
-          NewInternalMessage(port->peer_port_name,
-                             EventType::kObserveProxy,
-                             event));
+      event->set_port_name(port->peer_port_name);
+      delegate_->ForwardEvent(port->peer_node_name, std::move(event));
     }
   }
   return OK;
 }
 
-int Node::OnObserveProxyAck(const PortName& port_name,
-                            uint64_t last_sequence_num) {
+int Node::OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event) {
+  const PortName& port_name = event->port_name();
+
   DVLOG(2) << "ObserveProxyAck at " << port_name << "@" << name_
-           << " (last_sequence_num=" << last_sequence_num << ")";
+           << " (last_sequence_num=" << event->last_sequence_num() << ")";
 
   scoped_refptr<Port> port = GetPort(port_name);
   if (!port)
@@ -608,7 +588,7 @@
     if (port->state != Port::kProxying)
       return OOPS(ERROR_PORT_STATE_UNEXPECTED);
 
-    if (last_sequence_num == kInvalidSequenceNum) {
+    if (event->last_sequence_num() == kInvalidSequenceNum) {
       // Send again.
       InitiateProxyRemoval(LockedPort(port.get()), port_name);
       return OK;
@@ -617,14 +597,15 @@
     // We can now remove this port once we have received and forwarded the last
     // message addressed to this port.
     port->remove_proxy_on_last_message = true;
-    port->last_sequence_num_to_receive = last_sequence_num;
+    port->last_sequence_num_to_receive = event->last_sequence_num();
   }
   TryRemoveProxy(PortRef(port_name, std::move(port)));
   return OK;
 }
 
-int Node::OnObserveClosure(const PortName& port_name,
-                           uint64_t last_sequence_num) {
+int Node::OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event) {
+  PortName port_name = event->port_name();
+
   // OK if the port doesn't exist, as it may have been closed already.
   scoped_refptr<Port> port = GetPort(port_name);
   if (!port)
@@ -636,7 +617,6 @@
   // ObserveProxyAck.
 
   bool notify_delegate = false;
-  ObserveClosureEventData forwarded_data;
   NodeName peer_node_name;
   PortName peer_port_name;
   bool try_remove_proxy = false;
@@ -644,12 +624,12 @@
     base::AutoLock lock(port->lock);
 
     port->peer_closed = true;
-    port->last_sequence_num_to_receive = last_sequence_num;
+    port->last_sequence_num_to_receive = event->last_sequence_num();
 
     DVLOG(2) << "ObserveClosure at " << port_name << "@" << name_
              << " (state=" << port->state << ") pointing to "
              << port->peer_port_name << "@" << port->peer_node_name
-             << " (last_sequence_num=" << last_sequence_num << ")";
+             << " (last_sequence_num=" << event->last_sequence_num() << ")";
 
     // We always forward ObserveClosure, even beyond the receiving port which
     // cares about it. This ensures that any dead-end proxies beyond that port
@@ -665,12 +645,10 @@
       // TODO: Repurposing ObserveClosure for this has the desired result but
       // may be semantically confusing since the forwarding port is not actually
       // closed. Consider replacing this with a new event type.
-      forwarded_data.last_sequence_num = port->next_sequence_num_to_send - 1;
+      event->set_last_sequence_num(port->next_sequence_num_to_send - 1);
     } else {
-      // We haven't yet reached the receiving peer of the closed port, so
+      // We haven't yet reached the receiving peer of the closed port, so we'll
       // forward the message along as-is.
-      forwarded_data.last_sequence_num = last_sequence_num;
-
       // See about removing the port if it is a proxy as our peer won't be able
       // to participate in proxy removal.
       port->remove_proxy_on_last_message = true;
@@ -678,11 +656,10 @@
         try_remove_proxy = true;
     }
 
-    DVLOG(2) << "Forwarding ObserveClosure from "
-             << port_name << "@" << name_ << " to peer "
-             << port->peer_port_name << "@" << port->peer_node_name
-             << " (last_sequence_num=" << forwarded_data.last_sequence_num
-             << ")";
+    DVLOG(2) << "Forwarding ObserveClosure from " << port_name << "@" << name_
+             << " to peer " << port->peer_port_name << "@"
+             << port->peer_node_name
+             << " (last_sequence_num=" << event->last_sequence_num() << ")";
 
     peer_node_name = port->peer_node_name;
     peer_port_name = port->peer_port_name;
@@ -690,10 +667,8 @@
   if (try_remove_proxy)
     TryRemoveProxy(PortRef(port_name, port));
 
-  delegate_->ForwardMessage(
-      peer_node_name,
-      NewInternalMessage(peer_port_name, EventType::kObserveClosure,
-                         forwarded_data));
+  event->set_port_name(peer_port_name);
+  delegate_->ForwardEvent(peer_node_name, std::move(event));
 
   if (notify_delegate) {
     PortRef port_ref(port_name, std::move(port));
@@ -702,25 +677,24 @@
   return OK;
 }
 
-int Node::OnMergePort(const PortName& port_name,
-                      const MergePortEventData& event) {
+int Node::OnMergePort(std::unique_ptr<MergePortEvent> event) {
+  const PortName& port_name = event->port_name();
   scoped_refptr<Port> port = GetPort(port_name);
 
-  DVLOG(1) << "MergePort at " << port_name << "@" << name_ << " (state="
-           << (port ? port->state : -1) << ") merging with proxy "
-           << event.new_port_name
-           << "@" << name_ << " pointing to "
-           << event.new_port_descriptor.peer_port_name << "@"
-           << event.new_port_descriptor.peer_node_name << " referred by "
-           << event.new_port_descriptor.referring_port_name << "@"
-           << event.new_port_descriptor.referring_node_name;
+  DVLOG(1) << "MergePort at " << port_name << "@" << name_
+           << " (state=" << (port ? port->state : -1) << ") merging with proxy "
+           << event->new_port_name() << "@" << name_ << " pointing to "
+           << event->new_port_descriptor().peer_port_name << "@"
+           << event->new_port_descriptor().peer_node_name << " referred by "
+           << event->new_port_descriptor().referring_port_name << "@"
+           << event->new_port_descriptor().referring_node_name;
 
   bool close_target_port = false;
   bool close_new_port = false;
 
   // Accept the new port. This is now the receiving end of the other port cycle
   // to be merged with ours.
-  int rv = AcceptPort(event.new_port_name, event.new_port_descriptor);
+  int rv = AcceptPort(event->new_port_name(), event->new_port_descriptor());
   if (rv != OK) {
     close_target_port = true;
   } else if (port) {
@@ -732,7 +706,7 @@
     if (port->state != Port::kReceiving) {
       close_new_port = true;
     } else {
-      scoped_refptr<Port> new_port = GetPort_Locked(event.new_port_name);
+      scoped_refptr<Port> new_port = GetPort_Locked(event->new_port_name());
       base::AutoLock new_port_lock(new_port->lock);
       DCHECK(new_port->state == Port::kReceiving);
 
@@ -740,7 +714,7 @@
       // information and set them up as proxies.
 
       PortRef port0_ref(port_name, port);
-      PortRef port1_ref(event.new_port_name, new_port);
+      PortRef port1_ref(event->new_port_name(), new_port);
       int rv = MergePorts_Locked(port0_ref, port1_ref);
       if (rv == OK)
         return rv;
@@ -762,7 +736,7 @@
 
   if (close_new_port) {
     PortRef new_port;
-    rv = GetPort(event.new_port_name, &new_port);
+    rv = GetPort(event->new_port_name(), &new_port);
     DCHECK(rv == OK);
 
     ClosePort(new_port);
@@ -811,8 +785,9 @@
   return iter->second;
 }
 
-int Node::SendMessageInternal(const PortRef& port_ref, ScopedMessage* message) {
-  ScopedMessage& m = *message;
+int Node::SendUserMessageInternal(const PortRef& port_ref,
+                                  std::unique_ptr<UserMessageEvent>* message) {
+  std::unique_ptr<UserMessageEvent>& m = *message;
   for (size_t i = 0; i < m->num_ports(); ++i) {
     if (m->ports()[i] == port_ref.name())
       return ERROR_PORT_CANNOT_SEND_SELF;
@@ -822,7 +797,8 @@
   NodeName peer_node_name;
   {
     // We must acquire |ports_lock_| before grabbing any port locks, because
-    // WillSendMessage_Locked may need to lock multiple ports out of order.
+    // WillForwardUserMessage_Locked may need to lock multiple ports out of
+    // order.
     base::AutoLock ports_lock(ports_lock_);
     base::AutoLock lock(port->lock);
 
@@ -832,7 +808,8 @@
     if (port->peer_closed)
       return ERROR_PORT_PEER_CLOSED;
 
-    int rv = WillSendMessage_Locked(LockedPort(port), port_ref.name(), m.get());
+    int rv = WillForwardUserMessage_Locked(LockedPort(port), port_ref.name(),
+                                           m.get());
     if (rv != OK)
       return rv;
 
@@ -845,14 +822,14 @@
   }
 
   if (peer_node_name != name_) {
-    delegate_->ForwardMessage(peer_node_name, std::move(m));
+    delegate_->ForwardEvent(peer_node_name, std::move(m));
     return OK;
   }
 
-  int rv = AcceptMessage(std::move(m));
+  int rv = AcceptEvent(std::move(m));
   if (rv != OK) {
     // See comment above for why we don't return an error in this case.
-    DVLOG(2) << "AcceptMessage failed: " << rv;
+    DVLOG(2) << "AcceptEvent failed: " << rv;
   }
 
   return OK;
@@ -903,21 +880,17 @@
       // If either merged port had a closed peer, its new peer needs to be
       // informed of this.
       if (port1->peer_closed) {
-        ObserveClosureEventData data;
-        data.last_sequence_num = port0->last_sequence_num_to_receive;
-        delegate_->ForwardMessage(
+        delegate_->ForwardEvent(
             port0->peer_node_name,
-            NewInternalMessage(port0->peer_port_name,
-                               EventType::kObserveClosure, data));
+            base::MakeUnique<ObserveClosureEvent>(
+                port0->peer_port_name, port0->last_sequence_num_to_receive));
       }
 
       if (port0->peer_closed) {
-        ObserveClosureEventData data;
-        data.last_sequence_num = port1->last_sequence_num_to_receive;
-        delegate_->ForwardMessage(
+        delegate_->ForwardEvent(
             port1->peer_node_name,
-            NewInternalMessage(port1->peer_port_name,
-                               EventType::kObserveClosure, data));
+            base::MakeUnique<ObserveClosureEvent>(
+                port1->peer_port_name, port1->last_sequence_num_to_receive));
       }
 
       return OK;
@@ -940,7 +913,7 @@
 void Node::WillSendPort(const LockedPort& port,
                         const NodeName& to_node_name,
                         PortName* port_name,
-                        PortDescriptor* port_descriptor) {
+                        Event::PortDescriptor* port_descriptor) {
   port->lock.AssertAcquired();
 
   PortName local_port_name = *port_name;
@@ -978,7 +951,7 @@
 }
 
 int Node::AcceptPort(const PortName& port_name,
-                     const PortDescriptor& port_descriptor) {
+                     const Event::PortDescriptor& port_descriptor) {
   scoped_refptr<Port> port = make_scoped_refptr(
       new Port(port_descriptor.next_sequence_num_to_send,
                port_descriptor.next_sequence_num_to_receive));
@@ -1002,16 +975,15 @@
     return rv;
 
   // Allow referring port to forward messages.
-  delegate_->ForwardMessage(
+  delegate_->ForwardEvent(
       port_descriptor.referring_node_name,
-      NewInternalMessage(port_descriptor.referring_port_name,
-                         EventType::kPortAccepted));
+      base::MakeUnique<PortAcceptedEvent>(port_descriptor.referring_port_name));
   return OK;
 }
 
-int Node::WillSendMessage_Locked(const LockedPort& port,
-                                 const PortName& port_name,
-                                 Message* message) {
+int Node::WillForwardUserMessage_Locked(const LockedPort& port,
+                                        const PortName& port_name,
+                                        UserMessageEvent* message) {
   ports_lock_.AssertAcquired();
   port->lock.AssertAcquired();
 
@@ -1019,10 +991,8 @@
 
   // Messages may already have a sequence number if they're being forwarded
   // by a proxy. Otherwise, use the next outgoing sequence number.
-  uint64_t* sequence_num =
-      &GetMutableEventData<UserEventData>(message)->sequence_num;
-  if (*sequence_num == 0)
-    *sequence_num = port->next_sequence_num_to_send++;
+  if (message->sequence_num() == 0)
+    message->set_sequence_num(port->next_sequence_num_to_send++);
 
 #if DCHECK_IS_ON()
   std::ostringstream ports_buf;
@@ -1063,14 +1033,10 @@
       }
     }
 
-    PortDescriptor* port_descriptors =
-        GetMutablePortDescriptors(GetMutableEventData<UserEventData>(message));
-
+    Event::PortDescriptor* port_descriptors = message->port_descriptors();
     for (size_t i = 0; i < message->num_ports(); ++i) {
-      WillSendPort(LockedPort(ports[i].get()),
-                   port->peer_node_name,
-                   message->mutable_ports() + i,
-                   port_descriptors + i);
+      WillSendPort(LockedPort(ports[i].get()), port->peer_node_name,
+                   message->ports() + i, port_descriptors + i);
     }
 
     for (size_t i = 0; i < message->num_ports(); ++i)
@@ -1078,14 +1044,13 @@
   }
 
 #if DCHECK_IS_ON()
-  DVLOG(4) << "Sending message "
-           << GetEventData<UserEventData>(*message)->sequence_num
+  DVLOG(4) << "Sending message " << message->sequence_num()
            << " [ports=" << ports_buf.str() << "]"
-           << " from " << port_name << "@" << name_
-           << " to " << port->peer_port_name << "@" << port->peer_node_name;
+           << " from " << port_name << "@" << name_ << " to "
+           << port->peer_port_name << "@" << port->peer_node_name;
 #endif
 
-  GetMutableEventHeader(message)->port_name = port->peer_port_name;
+  message->set_port_name(port->peer_port_name);
   return OK;
 }
 
@@ -1111,12 +1076,10 @@
     MaybeRemoveProxy_Locked(LockedPort(port), port_name);
 
     // Make sure we propagate closure to our current peer.
-    ObserveClosureEventData data;
-    data.last_sequence_num = port->last_sequence_num_to_receive;
-    delegate_->ForwardMessage(
+    delegate_->ForwardEvent(
         port->peer_node_name,
-        NewInternalMessage(port->peer_port_name,
-                           EventType::kObserveClosure, data));
+        base::MakeUnique<ObserveClosureEvent>(
+            port->peer_port_name, port->last_sequence_num_to_receive));
   } else {
     InitiateProxyRemoval(LockedPort(port), port_name);
   }
@@ -1142,7 +1105,7 @@
 
   bool should_remove;
   NodeName peer_node_name;
-  ScopedMessage closure_message;
+  ScopedEvent closure_event;
   {
     base::AutoLock lock(port->lock);
     if (port->state != Port::kProxying)
@@ -1151,11 +1114,9 @@
     should_remove = port->remove_proxy_on_last_message;
     if (should_remove) {
       // Make sure we propagate closure to our current peer.
-      ObserveClosureEventData data;
-      data.last_sequence_num = port->last_sequence_num_to_receive;
       peer_node_name = port->peer_node_name;
-      closure_message = NewInternalMessage(port->peer_port_name,
-                                           EventType::kObserveClosure, data);
+      closure_event = base::MakeUnique<ObserveClosureEvent>(
+          port->peer_port_name, port->last_sequence_num_to_receive);
     } else {
       InitiateProxyRemoval(LockedPort(port), port_ref.name());
     }
@@ -1163,7 +1124,7 @@
 
   if (should_remove) {
     TryRemoveProxy(port_ref);
-    delegate_->ForwardMessage(peer_node_name, std::move(closure_message));
+    delegate_->ForwardEvent(peer_node_name, std::move(closure_event));
   }
 
   return OK;
@@ -1175,16 +1136,17 @@
   port->lock.AssertAcquired();
 
   for (;;) {
-    ScopedMessage message;
+    std::unique_ptr<UserMessageEvent> message;
     port->message_queue.GetNextMessage(&message, nullptr);
     if (!message)
       break;
 
-    int rv = WillSendMessage_Locked(LockedPort(port), port_name, message.get());
+    int rv = WillForwardUserMessage_Locked(LockedPort(port), port_name,
+                                           message.get());
     if (rv != OK)
       return rv;
 
-    delegate_->ForwardMessage(port->peer_node_name, std::move(message));
+    delegate_->ForwardEvent(port->peer_node_name, std::move(message));
   }
   return OK;
 }
@@ -1198,15 +1160,10 @@
   // Eventually, this node will receive ObserveProxyAck (or ObserveClosure if
   // the peer was closed in the meantime).
 
-  ObserveProxyEventData data;
-  data.proxy_node_name = name_;
-  data.proxy_port_name = port_name;
-  data.proxy_to_node_name = port->peer_node_name;
-  data.proxy_to_port_name = port->peer_port_name;
-
-  delegate_->ForwardMessage(
-      port->peer_node_name,
-      NewInternalMessage(port->peer_port_name, EventType::kObserveProxy, data));
+  delegate_->ForwardEvent(port->peer_node_name,
+                          base::MakeUnique<ObserveProxyEvent>(
+                              port->peer_port_name, name_, port_name,
+                              port->peer_node_name, port->peer_port_name));
 }
 
 void Node::MaybeRemoveProxy_Locked(const LockedPort& port,
@@ -1227,9 +1184,9 @@
 
     if (port->send_on_proxy_removal) {
       NodeName to_node = port->send_on_proxy_removal->first;
-      ScopedMessage& message = port->send_on_proxy_removal->second;
+      ScopedEvent& event = port->send_on_proxy_removal->second;
 
-      delegate_->ForwardMessage(to_node, std::move(message));
+      delegate_->ForwardEvent(to_node, std::move(event));
       port->send_on_proxy_removal.reset();
     }
   } else {
@@ -1241,7 +1198,7 @@
 void Node::TryRemoveProxy(PortRef port_ref) {
   Port* port = port_ref.port();
   bool should_erase = false;
-  ScopedMessage msg;
+  ScopedEvent event;
   NodeName to_node;
   {
     base::AutoLock lock(port->lock);
@@ -1262,7 +1219,7 @@
 
       if (port->send_on_proxy_removal) {
         to_node = port->send_on_proxy_removal->first;
-        msg = std::move(port->send_on_proxy_removal->second);
+        event = std::move(port->send_on_proxy_removal->second);
         port->send_on_proxy_removal.reset();
       }
     } else {
@@ -1274,8 +1231,8 @@
   if (should_erase)
     ErasePort(port_ref.name());
 
-  if (msg)
-    delegate_->ForwardMessage(to_node, std::move(msg));
+  if (event)
+    delegate_->ForwardEvent(to_node, std::move(event));
 }
 
 void Node::DestroyAllPortsWithPeer(const NodeName& node_name,
@@ -1339,13 +1296,9 @@
 
   for (const auto& proxy_name : dead_proxies_to_broadcast) {
     // Broadcast an event signifying that this proxy is no longer functioning.
-    ObserveProxyEventData event;
-    event.proxy_node_name = name_;
-    event.proxy_port_name = proxy_name;
-    event.proxy_to_node_name = kInvalidNodeName;
-    event.proxy_to_port_name = kInvalidPortName;
-    delegate_->BroadcastMessage(NewInternalMessage(
-        kInvalidPortName, EventType::kObserveProxy, event));
+    delegate_->BroadcastEvent(base::MakeUnique<ObserveProxyEvent>(
+        kInvalidPortName, name_, proxy_name, kInvalidNodeName,
+        kInvalidPortName));
 
     // Also process death locally since the port that points this closed one
     // could be on the current node.
@@ -1362,24 +1315,6 @@
   }
 }
 
-ScopedMessage Node::NewInternalMessage_Helper(const PortName& port_name,
-                                              const EventType& type,
-                                              const void* data,
-                                              size_t num_data_bytes) {
-  ScopedMessage message;
-  delegate_->AllocMessage(sizeof(EventHeader) + num_data_bytes, &message);
-
-  EventHeader* header = GetMutableEventHeader(message.get());
-  header->port_name = port_name;
-  header->type = type;
-  header->padding = 0;
-
-  if (num_data_bytes)
-    memcpy(header + 1, data, num_data_bytes);
-
-  return message;
-}
-
 }  // namespace ports
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/system/ports/node.h b/mojo/edk/system/ports/node.h
index 55b8d27..7970279 100644
--- a/mojo/edk/system/ports/node.h
+++ b/mojo/edk/system/ports/node.h
@@ -15,14 +15,11 @@
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
 #include "mojo/edk/system/ports/event.h"
-#include "mojo/edk/system/ports/message.h"
 #include "mojo/edk/system/ports/name.h"
 #include "mojo/edk/system/ports/port.h"
 #include "mojo/edk/system/ports/port_ref.h"
 #include "mojo/edk/system/ports/user_data.h"
 
-#undef SendMessage  // Gah, windows
-
 namespace mojo {
 namespace edk {
 namespace ports {
@@ -114,16 +111,17 @@
   // available. Ownership of |filter| is not taken, and it must outlive the
   // extent of this call.
   int GetMessage(const PortRef& port_ref,
-                 ScopedMessage* message,
+                 std::unique_ptr<UserMessageEvent>* message,
                  MessageFilter* filter);
 
   // Sends a message from the specified port to its peer. Note that the message
   // notification may arrive synchronously (via PortStatusChanged() on the
   // delegate) if the peer is local to this Node.
-  int SendMessage(const PortRef& port_ref, ScopedMessage message);
+  int SendUserMessage(const PortRef& port_ref,
+                      std::unique_ptr<UserMessageEvent> message);
 
-  // Corresponding to NodeDelegate::ForwardMessage.
-  int AcceptMessage(ScopedMessage message);
+  // Corresponding to NodeDelegate::ForwardEvent.
+  int AcceptEvent(ScopedEvent event);
 
   // Called to merge two ports with each other. If you have two independent
   // port pairs A <=> B and C <=> D, the net result of merging B and C is a
@@ -155,13 +153,12 @@
 
   // Note: Functions that end with _Locked require |ports_lock_| to be held
   // before calling.
-  int OnUserMessage(ScopedMessage message);
-  int OnPortAccepted(const PortName& port_name);
-  int OnObserveProxy(const PortName& port_name,
-                     const ObserveProxyEventData& event);
-  int OnObserveProxyAck(const PortName& port_name, uint64_t last_sequence_num);
-  int OnObserveClosure(const PortName& port_name, uint64_t last_sequence_num);
-  int OnMergePort(const PortName& port_name, const MergePortEventData& event);
+  int OnUserMessage(std::unique_ptr<UserMessageEvent> message);
+  int OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event);
+  int OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event);
+  int OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event);
+  int OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event);
+  int OnMergePort(std::unique_ptr<MergePortEvent> event);
 
   int AddPortWithName(const PortName& port_name, scoped_refptr<Port> port);
   void ErasePort(const PortName& port_name);
@@ -169,18 +166,19 @@
   scoped_refptr<Port> GetPort(const PortName& port_name);
   scoped_refptr<Port> GetPort_Locked(const PortName& port_name);
 
-  int SendMessageInternal(const PortRef& port_ref, ScopedMessage* message);
+  int SendUserMessageInternal(const PortRef& port_ref,
+                              std::unique_ptr<UserMessageEvent>* message);
   int MergePorts_Locked(const PortRef& port0_ref, const PortRef& port1_ref);
   void WillSendPort(const LockedPort& port,
                     const NodeName& to_node_name,
                     PortName* port_name,
-                    PortDescriptor* port_descriptor);
+                    Event::PortDescriptor* port_descriptor);
   int AcceptPort(const PortName& port_name,
-                 const PortDescriptor& port_descriptor);
+                 const Event::PortDescriptor& port_descriptor);
 
-  int WillSendMessage_Locked(const LockedPort& port,
-                             const PortName& port_name,
-                             Message* message);
+  int WillForwardUserMessage_Locked(const LockedPort& port,
+                                    const PortName& port_name,
+                                    UserMessageEvent* message);
   int BeginProxying_Locked(const LockedPort& port, const PortName& port_name);
   int BeginProxying(PortRef port_ref);
   int ForwardMessages_Locked(const LockedPort& port, const PortName& port_name);
@@ -191,30 +189,13 @@
   void DestroyAllPortsWithPeer(const NodeName& node_name,
                                const PortName& port_name);
 
-  ScopedMessage NewInternalMessage_Helper(const PortName& port_name,
-                                          const EventType& type,
-                                          const void* data,
-                                          size_t num_data_bytes);
-
-  ScopedMessage NewInternalMessage(const PortName& port_name,
-                                   const EventType& type) {
-    return NewInternalMessage_Helper(port_name, type, nullptr, 0);
-  }
-
-  template <typename EventData>
-  ScopedMessage NewInternalMessage(const PortName& port_name,
-                                   const EventType& type,
-                                   const EventData& data) {
-    return NewInternalMessage_Helper(port_name, type, &data, sizeof(data));
-  }
-
   const NodeName name_;
   NodeDelegate* const delegate_;
 
   // Guards |ports_| as well as any operation which needs to hold multiple port
   // locks simultaneously. Usage of this is subtle: it must NEVER be acquired
   // after a Port lock is acquired, and it must ALWAYS be acquired before
-  // calling WillSendMessage_Locked or ForwardMessages_Locked.
+  // calling WillForwardUserMessage_Locked or ForwardMessages_Locked.
   base::Lock ports_lock_;
   std::unordered_map<PortName, scoped_refptr<Port>> ports_;
 
diff --git a/mojo/edk/system/ports/node_delegate.h b/mojo/edk/system/ports/node_delegate.h
index 8547302a..13f264f 100644
--- a/mojo/edk/system/ports/node_delegate.h
+++ b/mojo/edk/system/ports/node_delegate.h
@@ -7,7 +7,7 @@
 
 #include <stddef.h>
 
-#include "mojo/edk/system/ports/message.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/name.h"
 #include "mojo/edk/system/ports/port_ref.h"
 
@@ -22,18 +22,12 @@
   // Port names should be difficult to guess.
   virtual void GenerateRandomPortName(PortName* port_name) = 0;
 
-  // Allocate a message, including a header that can be used by the Node
-  // implementation. |num_header_bytes| will be aligned. The newly allocated
-  // memory need not be zero-filled.
-  virtual void AllocMessage(size_t num_header_bytes,
-                            ScopedMessage* message) = 0;
+  // Forward an event asynchronously to the specified node. This method MUST NOT
+  // synchronously call any methods on Node.
+  virtual void ForwardEvent(const NodeName& node, ScopedEvent event) = 0;
 
-  // Forward a message asynchronously to the specified node. This method MUST
-  // NOT synchronously call any methods on Node.
-  virtual void ForwardMessage(const NodeName& node, ScopedMessage message) = 0;
-
-  // Broadcast a message to all nodes.
-  virtual void BroadcastMessage(ScopedMessage message) = 0;
+  // Broadcast an event to all nodes.
+  virtual void BroadcastEvent(ScopedEvent event) = 0;
 
   // Indicates that the port's status has changed recently. Use Node::GetStatus
   // to query the latest status of the port. Note, this event could be spurious
diff --git a/mojo/edk/system/ports/port.h b/mojo/edk/system/ports/port.h
index ea53d43..aa16d3b1 100644
--- a/mojo/edk/system/ports/port.h
+++ b/mojo/edk/system/ports/port.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
+#include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/message_queue.h"
 #include "mojo/edk/system/ports/user_data.h"
 
@@ -37,7 +38,7 @@
   uint64_t next_sequence_num_to_send;
   uint64_t last_sequence_num_to_receive;
   MessageQueue message_queue;
-  std::unique_ptr<std::pair<NodeName, ScopedMessage>> send_on_proxy_removal;
+  std::unique_ptr<std::pair<NodeName, ScopedEvent>> send_on_proxy_removal;
   scoped_refptr<UserData> user_data;
   bool remove_proxy_on_last_message;
   bool peer_closed;
diff --git a/mojo/edk/system/ports/ports_unittest.cc b/mojo/edk/system/ports/ports_unittest.cc
index 4b384b4..e20fda38 100644
--- a/mojo/edk/system/ports/ports_unittest.cc
+++ b/mojo/edk/system/ports/ports_unittest.cc
@@ -26,6 +26,7 @@
 #include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/node.h"
 #include "mojo/edk/system/ports/node_delegate.h"
+#include "mojo/edk/system/ports/user_message.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo {
@@ -35,36 +36,35 @@
 
 namespace {
 
-bool MessageEquals(const ScopedMessage& message, const base::StringPiece& s) {
-  return !strcmp(static_cast<const char*>(message->payload_bytes()), s.data());
+// TODO(rockot): Remove this unnecessary alias.
+using ScopedMessage = std::unique_ptr<UserMessageEvent>;
+
+class TestMessage : public UserMessage {
+ public:
+  static const TypeInfo kUserMessageTypeInfo;
+
+  TestMessage(const base::StringPiece& payload)
+      : UserMessage(&kUserMessageTypeInfo), payload_(payload) {}
+  ~TestMessage() override {}
+
+  const std::string& payload() const { return payload_; }
+
+ private:
+  std::string payload_;
+};
+
+const UserMessage::TypeInfo TestMessage::kUserMessageTypeInfo = {};
+
+ScopedMessage NewUserMessageEvent(const base::StringPiece& payload,
+                                  size_t num_ports) {
+  auto event = base::MakeUnique<UserMessageEvent>(num_ports);
+  event->AttachMessage(base::MakeUnique<TestMessage>(payload));
+  return event;
 }
 
-class TestMessage : public Message {
- public:
-  static ScopedMessage NewUserMessage(size_t num_payload_bytes,
-                                      size_t num_ports) {
-    return ScopedMessage(new TestMessage(num_payload_bytes, num_ports));
-  }
-
-  TestMessage(size_t num_payload_bytes, size_t num_ports)
-      : Message(num_payload_bytes, num_ports) {
-    start_ = new char[num_header_bytes_ + num_ports_bytes_ + num_payload_bytes];
-    InitializeUserMessageHeader(start_);
-  }
-
-  TestMessage(size_t num_header_bytes,
-              size_t num_payload_bytes,
-              size_t num_ports_bytes)
-      : Message(num_header_bytes,
-                num_payload_bytes,
-                num_ports_bytes) {
-    start_ = new char[num_header_bytes + num_payload_bytes + num_ports_bytes];
-  }
-
-  ~TestMessage() override {
-    delete[] start_;
-  }
-};
+bool MessageEquals(const ScopedMessage& message, const base::StringPiece& s) {
+  return message->GetMessage<TestMessage>()->payload() == s;
+}
 
 class TestNode;
 
@@ -73,10 +73,10 @@
   virtual ~MessageRouter() {}
 
   virtual void GeneratePortName(PortName* name) = 0;
-  virtual void ForwardMessage(TestNode* from_node,
-                              const NodeName& node_name,
-                              ScopedMessage message) = 0;
-  virtual void BroadcastMessage(TestNode* from_node, ScopedMessage message) = 0;
+  virtual void ForwardEvent(TestNode* from_node,
+                            const NodeName& node_name,
+                            ScopedEvent event) = 0;
+  virtual void BroadcastEvent(TestNode* from_node, ScopedEvent event) = 0;
 };
 
 class TestNode : public NodeDelegate {
@@ -85,13 +85,11 @@
       : node_name_(id, 1),
         node_(node_name_, this),
         node_thread_(base::StringPrintf("Node %" PRIu64 " thread", id)),
-        messages_available_event_(
+        events_available_event_(
             base::WaitableEvent::ResetPolicy::AUTOMATIC,
             base::WaitableEvent::InitialState::NOT_SIGNALED),
-        idle_event_(
-            base::WaitableEvent::ResetPolicy::MANUAL,
-            base::WaitableEvent::InitialState::SIGNALED) {
-  }
+        idle_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+                    base::WaitableEvent::InitialState::SIGNALED) {}
 
   ~TestNode() override {
     StopWhenIdle();
@@ -108,10 +106,10 @@
   bool IsIdle() {
     base::AutoLock lock(lock_);
     return started_ && !dispatching_ &&
-        (incoming_messages_.empty() || (block_on_event_ && blocked_));
+           (incoming_events_.empty() || (block_on_event_ && blocked_));
   }
 
-  void BlockOnEvent(EventType type) {
+  void BlockOnEvent(Event::Type type) {
     base::AutoLock lock(lock_);
     blocked_event_type_ = type;
     block_on_event_ = true;
@@ -120,7 +118,7 @@
   void Unblock() {
     base::AutoLock lock(lock_);
     block_on_event_ = false;
-    messages_available_event_.Signal();
+    events_available_event_.Signal();
   }
 
   void Start(MessageRouter* router) {
@@ -128,32 +126,27 @@
     node_thread_.Start();
     node_thread_.task_runner()->PostTask(
         FROM_HERE,
-        base::Bind(&TestNode::ProcessMessages, base::Unretained(this)));
+        base::Bind(&TestNode::ProcessEvents, base::Unretained(this)));
   }
 
   void StopWhenIdle() {
     base::AutoLock lock(lock_);
     should_quit_ = true;
-    messages_available_event_.Signal();
+    events_available_event_.Signal();
   }
 
-  void WakeUp() { messages_available_event_.Signal(); }
+  void WakeUp() { events_available_event_.Signal(); }
 
   int SendStringMessage(const PortRef& port, const std::string& s) {
-    size_t size = s.size() + 1;
-    ScopedMessage message = TestMessage::NewUserMessage(size, 0);
-    memcpy(message->mutable_payload_bytes(), s.data(), size);
-    return node_.SendMessage(port, std::move(message));
+    return node_.SendUserMessage(port, NewUserMessageEvent(s, 0));
   }
 
   int SendStringMessageWithPort(const PortRef& port,
                                 const std::string& s,
                                 const PortName& sent_port_name) {
-    size_t size = s.size() + 1;
-    ScopedMessage message = TestMessage::NewUserMessage(size, 1);
-    memcpy(message->mutable_payload_bytes(), s.data(), size);
-    message->mutable_ports()[0] = sent_port_name;
-    return node_.SendMessage(port, std::move(message));
+    auto event = NewUserMessageEvent(s, 1);
+    event->ports()[0] = sent_port_name;
+    return node_.SendUserMessage(port, std::move(event));
   }
 
   int SendStringMessageWithPort(const PortRef& port,
@@ -187,14 +180,14 @@
     return true;
   }
 
-  void EnqueueMessage(ScopedMessage message) {
+  void EnqueueEvent(ScopedEvent event) {
     idle_event_.Reset();
 
     // NOTE: This may be called from ForwardMessage and thus must not reenter
     // |node_|.
     base::AutoLock lock(lock_);
-    incoming_messages_.emplace(std::move(message));
-    messages_available_event_.Signal();
+    incoming_events_.emplace(std::move(event));
+    events_available_event_.Signal();
   }
 
   void GenerateRandomPortName(PortName* port_name) override {
@@ -202,12 +195,7 @@
     router_->GeneratePortName(port_name);
   }
 
-  void AllocMessage(size_t num_header_bytes, ScopedMessage* message) override {
-    message->reset(new TestMessage(num_header_bytes, 0, 0));
-  }
-
-  void ForwardMessage(const NodeName& node_name,
-                      ScopedMessage message) override {
+  void ForwardEvent(const NodeName& node_name, ScopedEvent event) override {
     {
       base::AutoLock lock(lock_);
       if (drop_messages_) {
@@ -215,19 +203,18 @@
                  << node_name_ << " to " << node_name;
 
         base::AutoUnlock unlock(lock_);
-        ClosePortsInMessage(message.get());
+        ClosePortsInEvent(event.get());
         return;
       }
     }
 
     DCHECK(router_);
-    DVLOG(1) << "ForwardMessage from node "
-             << node_name_ << " to " << node_name;
-    router_->ForwardMessage(this, node_name, std::move(message));
+    DVLOG(1) << "ForwardEvent from node " << node_name_ << " to " << node_name;
+    router_->ForwardEvent(this, node_name, std::move(event));
   }
 
-  void BroadcastMessage(ScopedMessage message) override {
-    router_->BroadcastMessage(this, std::move(message));
+  void BroadcastEvent(ScopedEvent event) override {
+    router_->BroadcastEvent(this, std::move(event));
   }
 
   void PortStatusChanged(const PortRef& port) override {
@@ -248,42 +235,44 @@
     }
   }
 
-  void ClosePortsInMessage(Message* message) {
-    for (size_t i = 0; i < message->num_ports(); ++i) {
+  void ClosePortsInEvent(Event* event) {
+    if (event->type() != Event::Type::kUserMessage)
+      return;
+
+    UserMessageEvent* message_event = static_cast<UserMessageEvent*>(event);
+    for (size_t i = 0; i < message_event->num_ports(); ++i) {
       PortRef port;
-      ASSERT_EQ(OK, node_.GetPort(message->ports()[i], &port));
+      ASSERT_EQ(OK, node_.GetPort(message_event->ports()[i], &port));
       EXPECT_EQ(OK, node_.ClosePort(port));
     }
   }
 
  private:
-  void ProcessMessages() {
+  void ProcessEvents() {
     for (;;) {
-      messages_available_event_.Wait();
-
+      events_available_event_.Wait();
       base::AutoLock lock(lock_);
 
       if (should_quit_)
         return;
 
       dispatching_ = true;
-      while (!incoming_messages_.empty()) {
+      while (!incoming_events_.empty()) {
         if (block_on_event_ &&
-            GetEventHeader(*incoming_messages_.front())->type ==
-                blocked_event_type_) {
+            incoming_events_.front()->type() == blocked_event_type_) {
           blocked_ = true;
           // Go idle if we hit a blocked event type.
           break;
         } else {
           blocked_ = false;
         }
-        ScopedMessage message = std::move(incoming_messages_.front());
-        incoming_messages_.pop();
+        ScopedEvent event = std::move(incoming_events_.front());
+        incoming_events_.pop();
 
         // NOTE: AcceptMessage() can re-enter this object to call any of the
         // NodeDelegate interface methods.
         base::AutoUnlock unlock(lock_);
-        node_.AcceptMessage(std::move(message));
+        node_.AcceptEvent(std::move(event));
       }
 
       dispatching_ = false;
@@ -297,7 +286,7 @@
   MessageRouter* router_ = nullptr;
 
   base::Thread node_thread_;
-  base::WaitableEvent messages_available_event_;
+  base::WaitableEvent events_available_event_;
   base::WaitableEvent idle_event_;
 
   // Guards fields below.
@@ -309,8 +298,8 @@
   bool save_messages_ = false;
   bool blocked_ = false;
   bool block_on_event_ = false;
-  EventType blocked_event_type_;
-  std::queue<ScopedMessage> incoming_messages_;
+  Event::Type blocked_event_type_;
+  std::queue<ScopedEvent> incoming_events_;
   std::queue<ScopedMessage> saved_messages_;
 };
 
@@ -384,14 +373,14 @@
     name->v2 = 0;
   }
 
-  void ForwardMessage(TestNode* from_node,
-                      const NodeName& node_name,
-                      ScopedMessage message) override {
+  void ForwardEvent(TestNode* from_node,
+                    const NodeName& node_name,
+                    ScopedEvent event) override {
     base::AutoLock global_lock(global_lock_);
     base::AutoLock lock(lock_);
     // Drop messages from nodes that have been removed.
     if (nodes_.find(from_node->name()) == nodes_.end()) {
-      from_node->ClosePortsInMessage(message.get());
+      from_node->ClosePortsInEvent(event.get());
       return;
     }
 
@@ -401,10 +390,10 @@
       return;
     }
 
-    it->second->EnqueueMessage(std::move(message));
+    it->second->EnqueueEvent(std::move(event));
   }
 
-  void BroadcastMessage(TestNode* from_node, ScopedMessage message) override {
+  void BroadcastEvent(TestNode* from_node, ScopedEvent event) override {
     base::AutoLock global_lock(global_lock_);
     base::AutoLock lock(lock_);
 
@@ -417,14 +406,7 @@
       // Broadcast doesn't deliver to the local node.
       if (node == from_node)
         continue;
-
-      // NOTE: We only need to support broadcast of events. Events have no
-      // payload or ports bytes.
-      ScopedMessage new_message(
-          new TestMessage(message->num_header_bytes(), 0, 0));
-      memcpy(new_message->mutable_header_bytes(), message->header_bytes(),
-             message->num_header_bytes());
-      node->EnqueueMessage(std::move(new_message));
+      node->EnqueueEvent(event->Clone());
     }
   }
 
@@ -592,7 +574,7 @@
 
   EXPECT_EQ(OK, node1.node().GetMessage(x1, &message, nullptr));
   EXPECT_TRUE(message);
-  node1.ClosePortsInMessage(message.get());
+  node1.ClosePortsInEvent(message.get());
 
   EXPECT_EQ(OK, node1.node().ClosePort(x1));
 
@@ -638,7 +620,7 @@
   // port A on node 0 will eventually also become aware of it.
 
   // Make sure node2 stops processing events when it encounters an ObserveProxy.
-  node2.BlockOnEvent(EventType::kObserveProxy);
+  node2.BlockOnEvent(Event::Type::kObserveProxy);
 
   EXPECT_EQ(OK, node1.SendStringMessageWithPort(C, ".", F));
   WaitForIdle();
@@ -687,7 +669,7 @@
   EXPECT_EQ(OK, node0.node().CreatePortPair(&C, &D));
 
   // Send D but block node0 on an ObserveProxy event.
-  node0.BlockOnEvent(EventType::kObserveProxy);
+  node0.BlockOnEvent(Event::Type::kObserveProxy);
   EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, ".", D));
 
   // node0 won't collapse the proxy but node1 will receive the message before
@@ -888,7 +870,7 @@
     bool got_hello = false;
     ScopedMessage message;
     while (node1.GetSavedMessage(&message)) {
-      node1.ClosePortsInMessage(message.get());
+      node1.ClosePortsInEvent(message.get());
       if (MessageEquals(message, "hello")) {
         got_hello = true;
         break;
@@ -1433,7 +1415,7 @@
 
   // Block the merge from proceeding until we can do something stupid with port
   // C. This avoids the test logic racing with async merge logic.
-  node1.BlockOnEvent(EventType::kMergePort);
+  node1.BlockOnEvent(Event::Type::kMergePort);
 
   // Initiate the merge between B and C.
   EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name()));
diff --git a/mojo/edk/system/ports/user_message.h b/mojo/edk/system/ports/user_message.h
new file mode 100644
index 0000000..1401e59
--- /dev/null
+++ b/mojo/edk/system/ports/user_message.h
@@ -0,0 +1,44 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_EDK_SYSTEM_PORTS_USER_MESSAGE_H_
+#define MOJO_EDK_SYSTEM_PORTS_USER_MESSAGE_H_
+
+#include "base/macros.h"
+
+namespace mojo {
+namespace edk {
+namespace ports {
+
+// Base type to use for any embedder-defined user message implementation. This
+// class is intentionally empty.
+//
+// Provides a bit of type-safety help to subclasses since by design downcasting
+// from this type is a common operation in embedders.
+//
+// Each subclass should define a static const instance of TypeInfo named
+// |kUserMessageTypeInfo| and pass its address down to the UserMessage
+// constructor. The type of a UserMessage can then be dynamically inspected by
+// comparing |type_info()| to any subclass's |&kUserMessageTypeInfo|.
+class UserMessage {
+ public:
+  struct TypeInfo {};
+
+  virtual ~UserMessage() {}
+
+  explicit UserMessage(const TypeInfo* type_info) : type_info_(type_info) {}
+
+  const TypeInfo* type_info() const { return type_info_; }
+
+ private:
+  const TypeInfo* const type_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(UserMessage);
+};
+
+}  // namespace ports
+}  // namespace edk
+}  // namespace mojo
+
+#endif  // MOJO_EDK_SYSTEM_PORTS_USER_MESSAGE_H_
diff --git a/mojo/edk/system/ports_message.cc b/mojo/edk/system/ports_message.cc
deleted file mode 100644
index 5f3e8c01..0000000
--- a/mojo/edk/system/ports_message.cc
+++ /dev/null
@@ -1,62 +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 "mojo/edk/system/ports_message.h"
-
-#include "base/memory/ptr_util.h"
-#include "mojo/edk/system/node_channel.h"
-
-namespace mojo {
-namespace edk {
-
-// static
-std::unique_ptr<PortsMessage> PortsMessage::NewUserMessage(
-    size_t num_payload_bytes,
-    size_t num_ports,
-    size_t num_handles) {
-  return base::WrapUnique(
-      new PortsMessage(num_payload_bytes, num_ports, num_handles));
-}
-
-PortsMessage::~PortsMessage() {}
-
-PortsMessage::PortsMessage(size_t num_payload_bytes,
-                           size_t num_ports,
-                           size_t num_handles)
-    : ports::Message(num_payload_bytes, num_ports) {
-  size_t size = num_header_bytes_ + num_ports_bytes_ + num_payload_bytes;
-  void* ptr;
-  channel_message_ = NodeChannel::CreatePortsMessage(size, &ptr, num_handles);
-  InitializeUserMessageHeader(ptr);
-}
-
-PortsMessage::PortsMessage(size_t num_header_bytes,
-                           size_t num_payload_bytes,
-                           size_t num_ports_bytes,
-                           Channel::MessagePtr channel_message)
-    : ports::Message(num_header_bytes,
-                     num_payload_bytes,
-                     num_ports_bytes) {
-  if (channel_message) {
-    channel_message_ = std::move(channel_message);
-    void* data;
-    size_t num_data_bytes;
-    NodeChannel::GetPortsMessageData(channel_message_.get(), &data,
-                                     &num_data_bytes);
-    start_ = static_cast<char*>(data);
-  } else {
-    // TODO: Clean this up. In practice this branch of the constructor should
-    // only be reached from Node-internal calls to AllocMessage, which never
-    // carry ports or non-header bytes.
-    CHECK_EQ(num_payload_bytes, 0u);
-    CHECK_EQ(num_ports_bytes, 0u);
-    void* ptr;
-    channel_message_ =
-        NodeChannel::CreatePortsMessage(num_header_bytes, &ptr, 0);
-    start_ = static_cast<char*>(ptr);
-  }
-}
-
-}  // namespace edk
-}  // namespace mojo
diff --git a/mojo/edk/system/ports_message.h b/mojo/edk/system/ports_message.h
deleted file mode 100644
index 542b981..0000000
--- a/mojo/edk/system/ports_message.h
+++ /dev/null
@@ -1,69 +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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__
-#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__
-
-#include <memory>
-#include <utility>
-
-#include "mojo/edk/embedder/platform_handle_vector.h"
-#include "mojo/edk/system/channel.h"
-#include "mojo/edk/system/ports/message.h"
-#include "mojo/edk/system/ports/name.h"
-
-namespace mojo {
-namespace edk {
-
-class NodeController;
-
-class PortsMessage : public ports::Message {
- public:
-  static std::unique_ptr<PortsMessage> NewUserMessage(size_t num_payload_bytes,
-                                                      size_t num_ports,
-                                                      size_t num_handles);
-
-  ~PortsMessage() override;
-
-  size_t num_handles() const { return channel_message_->num_handles(); }
-  bool has_handles() const { return channel_message_->has_handles(); }
-
-  void SetHandles(ScopedPlatformHandleVectorPtr handles) {
-    channel_message_->SetHandles(std::move(handles));
-  }
-
-  ScopedPlatformHandleVectorPtr TakeHandles() {
-    return channel_message_->TakeHandles();
-  }
-
-  Channel::MessagePtr TakeChannelMessage() {
-    return std::move(channel_message_);
-  }
-
-  void set_source_node(const ports::NodeName& name) { source_node_ = name; }
-  const ports::NodeName& source_node() const { return source_node_; }
-
- private:
-  friend class NodeController;
-
-  // Construct a new user PortsMessage backed by a new Channel::Message.
-  PortsMessage(size_t num_payload_bytes, size_t num_ports, size_t num_handles);
-
-  // Construct a new PortsMessage backed by a Channel::Message. If
-  // |channel_message| is null, a new one is allocated internally.
-  PortsMessage(size_t num_header_bytes,
-               size_t num_payload_bytes,
-               size_t num_ports_bytes,
-               Channel::MessagePtr channel_message);
-
-  Channel::MessagePtr channel_message_;
-
-  // The node name from which this message was received, if known.
-  ports::NodeName source_node_ = ports::kInvalidNodeName;
-};
-
-}  // namespace edk
-}  // namespace mojo
-
-#endif  // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__
diff --git a/mojo/edk/system/user_message_impl.cc b/mojo/edk/system/user_message_impl.cc
new file mode 100644
index 0000000..44d9bb8
--- /dev/null
+++ b/mojo/edk/system/user_message_impl.cc
@@ -0,0 +1,457 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/system/user_message_impl.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/node_channel.h"
+#include "mojo/edk/system/node_controller.h"
+#include "mojo/edk/system/ports/event.h"
+#include "mojo/edk/system/ports/message_filter.h"
+#include "mojo/edk/system/ports/node.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+#pragma pack(push, 1)
+// Header attached to every message.
+struct MessageHeader {
+  // The number of serialized dispatchers included in this header.
+  uint32_t num_dispatchers;
+
+  // Total size of the header, including serialized dispatcher data.
+  uint32_t header_size;
+};
+
+// Header for each dispatcher in a message, immediately following the message
+// header.
+struct DispatcherHeader {
+  // The type of the dispatcher, correpsonding to the Dispatcher::Type enum.
+  int32_t type;
+
+  // The size of the serialized dispatcher, not including this header.
+  uint32_t num_bytes;
+
+  // The number of ports needed to deserialize this dispatcher.
+  uint32_t num_ports;
+
+  // The number of platform handles needed to deserialize this dispatcher.
+  uint32_t num_platform_handles;
+};
+#pragma pack(pop)
+
+static_assert(sizeof(MessageHeader) % 8 == 0, "Invalid MessageHeader size.");
+static_assert(sizeof(DispatcherHeader) % 8 == 0,
+              "Invalid DispatcherHeader size.");
+
+}  // namespace
+
+// A MessageFilter used by UserMessageImpl::ReadMessageEventFromPort to
+// determine whether a message should actually be consumed yet.
+class UserMessageImpl::ReadMessageFilter : public ports::MessageFilter {
+ public:
+  // Creates a new ReadMessageFilter which captures and potentially modifies
+  // various (unowned) local state within
+  // UserMessageImpl::ReadMessageEventFromPort.
+  ReadMessageFilter(bool read_any_size,
+                    bool may_discard,
+                    uint32_t* num_bytes,
+                    uint32_t* num_handles,
+                    bool* no_space,
+                    bool* invalid_message)
+      : read_any_size_(read_any_size),
+        may_discard_(may_discard),
+        num_bytes_(num_bytes),
+        num_handles_(num_handles),
+        no_space_(no_space),
+        invalid_message_(invalid_message) {}
+
+  ~ReadMessageFilter() override {}
+
+  // ports::MessageFilter:
+  bool Match(const ports::UserMessageEvent& event) override {
+    const auto* message = event.GetMessage<UserMessageImpl>();
+    if (!message->IsSerialized()) {
+      // Not a serialized message, so there's nothing to validate or filter
+      // against. We only ensure that the caller expected a message object and
+      // not a specific serialized buffer size.
+      if (!read_any_size_)
+        *invalid_message_ = true;
+      return true;
+    }
+
+    // All messages which reach this filter have already had a basic level of
+    // validation applied by UserMessageImpl::CreateFromChannelMessage()
+    // so we know there is at least well-formed header.
+    DCHECK(message->header_);
+    auto* header = static_cast<MessageHeader*>(message->header_);
+
+    uint32_t bytes_to_read = 0;
+    base::CheckedNumeric<uint32_t> checked_bytes_available =
+        message->user_payload_size();
+    if (!checked_bytes_available.IsValid()) {
+      *invalid_message_ = true;
+      return true;
+    }
+    const uint32_t bytes_available = checked_bytes_available.ValueOrDie();
+    if (num_bytes_) {
+      bytes_to_read = std::min(*num_bytes_, bytes_available);
+      *num_bytes_ = bytes_available;
+    }
+
+    uint32_t handles_to_read = 0;
+    uint32_t handles_available = header->num_dispatchers;
+    if (num_handles_) {
+      handles_to_read = std::min(*num_handles_, handles_available);
+      *num_handles_ = handles_available;
+    }
+
+    if (handles_to_read < handles_available ||
+        (!read_any_size_ && bytes_to_read < bytes_available)) {
+      *no_space_ = true;
+      return may_discard_;
+    }
+
+    return true;
+  }
+
+ private:
+  const bool read_any_size_;
+  const bool may_discard_;
+  uint32_t* const num_bytes_;
+  uint32_t* const num_handles_;
+  bool* const no_space_;
+  bool* const invalid_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadMessageFilter);
+};
+
+// static
+const ports::UserMessage::TypeInfo UserMessageImpl::kUserMessageTypeInfo = {};
+
+UserMessageImpl::~UserMessageImpl() {}
+
+// static
+MojoResult UserMessageImpl::CreateEventForNewSerializedMessage(
+    uint32_t num_bytes,
+    const Dispatcher::DispatcherInTransit* dispatchers,
+    uint32_t num_dispatchers,
+    std::unique_ptr<ports::UserMessageEvent>* out_event) {
+  // A structure for retaining information about every Dispatcher that will be
+  // serialized into this message.
+  struct DispatcherInfo {
+    uint32_t num_bytes;
+    uint32_t num_ports;
+    uint32_t num_handles;
+  };
+
+  // This is only the base header size. It will grow as we accumulate the
+  // size of serialized state for each dispatcher.
+  size_t header_size =
+      sizeof(MessageHeader) + num_dispatchers * sizeof(DispatcherHeader);
+  size_t num_ports = 0;
+  size_t num_handles = 0;
+
+  std::vector<DispatcherInfo> dispatcher_info(num_dispatchers);
+  for (size_t i = 0; i < num_dispatchers; ++i) {
+    Dispatcher* d = dispatchers[i].dispatcher.get();
+    d->StartSerialize(&dispatcher_info[i].num_bytes,
+                      &dispatcher_info[i].num_ports,
+                      &dispatcher_info[i].num_handles);
+    header_size += dispatcher_info[i].num_bytes;
+    num_ports += dispatcher_info[i].num_ports;
+    num_handles += dispatcher_info[i].num_handles;
+  }
+
+  // We now have enough information to fully allocate the message storage.
+  auto message_event = base::MakeUnique<ports::UserMessageEvent>(num_ports);
+  auto message = base::WrapUnique(
+      new UserMessageImpl(message_event->GetSerializedSize(), header_size,
+                          num_bytes, num_ports, num_handles));
+
+  // Populate the message header with information about serialized dispatchers.
+  // The front of the message is always a MessageHeader followed by a
+  // DispatcherHeader for each dispatcher to be sent.
+  MessageHeader* header = static_cast<MessageHeader*>(message->header_);
+  DispatcherHeader* dispatcher_headers =
+      reinterpret_cast<DispatcherHeader*>(header + 1);
+
+  // Serialized dispatcher state immediately follows the series of
+  // DispatcherHeaders.
+  char* dispatcher_data =
+      reinterpret_cast<char*>(dispatcher_headers + num_dispatchers);
+
+  header->num_dispatchers = num_dispatchers;
+
+  // |header_size| is the total number of bytes preceding the message payload,
+  // including all dispatcher headers and serialized dispatcher state.
+  if (!base::IsValueInRangeForNumericType<uint32_t>(header_size))
+    return MOJO_RESULT_OUT_OF_RANGE;
+
+  header->header_size = static_cast<uint32_t>(header_size);
+
+  if (num_dispatchers > 0) {
+    ScopedPlatformHandleVectorPtr handles(
+        new PlatformHandleVector(num_handles));
+    size_t port_index = 0;
+    size_t handle_index = 0;
+    bool fail = false;
+    for (size_t i = 0; i < num_dispatchers; ++i) {
+      Dispatcher* d = dispatchers[i].dispatcher.get();
+      DispatcherHeader* dh = &dispatcher_headers[i];
+      const DispatcherInfo& info = dispatcher_info[i];
+
+      // Fill in the header for this dispatcher.
+      dh->type = static_cast<int32_t>(d->GetType());
+      dh->num_bytes = info.num_bytes;
+      dh->num_ports = info.num_ports;
+      dh->num_platform_handles = info.num_handles;
+
+      // Fill in serialized state, ports, and platform handles. We'll cancel
+      // the send if the dispatcher implementation rejects for some reason.
+      if (!d->EndSerialize(static_cast<void*>(dispatcher_data),
+                           message_event->ports() + port_index,
+                           handles->data() + handle_index)) {
+        fail = true;
+        break;
+      }
+
+      dispatcher_data += info.num_bytes;
+      port_index += info.num_ports;
+      handle_index += info.num_handles;
+    }
+
+    if (fail) {
+      // Release any platform handles we've accumulated. Their dispatchers
+      // retain ownership when message creation fails, so these are not actually
+      // leaking.
+      handles->clear();
+      return MOJO_RESULT_INVALID_ARGUMENT;
+    }
+
+    // Take ownership of all the handles and move them into message storage.
+    message->channel_message_->SetHandles(std::move(handles));
+  }
+
+  message_event->AttachMessage(std::move(message));
+  *out_event = std::move(message_event);
+  return MOJO_RESULT_OK;
+}
+
+// static
+std::unique_ptr<UserMessageImpl> UserMessageImpl::CreateFromChannelMessage(
+    Channel::MessagePtr channel_message,
+    void* payload,
+    size_t payload_size) {
+  DCHECK(channel_message);
+  if (payload_size < sizeof(MessageHeader))
+    return nullptr;
+
+  auto* header = static_cast<MessageHeader*>(payload);
+  const size_t header_size = header->header_size;
+  if (header_size > payload_size)
+    return nullptr;
+
+  void* user_payload = static_cast<uint8_t*>(payload) + header_size;
+  const size_t user_payload_size = payload_size - header_size;
+  return base::WrapUnique(new UserMessageImpl(
+      std::move(channel_message), header, user_payload, user_payload_size));
+}
+
+// static
+MojoResult UserMessageImpl::ReadMessageEventFromPort(
+    NodeController* node_controller,
+    const ports::PortRef& port,
+    bool read_any_size,
+    bool may_discard,
+    uint32_t* num_bytes,
+    MojoHandle* handles,
+    uint32_t* num_handles,
+    std::unique_ptr<ports::UserMessageEvent>* out_event) {
+  bool no_space = false;
+  bool invalid_message = false;
+  ReadMessageFilter filter(read_any_size, may_discard, num_bytes, num_handles,
+                           &no_space, &invalid_message);
+  std::unique_ptr<ports::UserMessageEvent> message_event;
+  int rv = node_controller->node()->GetMessage(port, &message_event, &filter);
+  if (invalid_message)
+    return MOJO_RESULT_UNKNOWN;
+
+  if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) {
+    if (rv == ports::ERROR_PORT_UNKNOWN ||
+        rv == ports::ERROR_PORT_STATE_UNEXPECTED)
+      return MOJO_RESULT_INVALID_ARGUMENT;
+
+    NOTREACHED();
+    return MOJO_RESULT_UNKNOWN;  // TODO: Add a better error code here?
+  }
+
+  if (no_space) {
+    // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't
+    // sufficient to hold this message's data. The message will still be in
+    // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set.
+    return MOJO_RESULT_RESOURCE_EXHAUSTED;
+  }
+
+  if (!message_event) {
+    // No message was available in queue.
+    if (rv == ports::OK)
+      return MOJO_RESULT_SHOULD_WAIT;
+    // Peer is closed and there are no more messages to read.
+    DCHECK_EQ(rv, ports::ERROR_PORT_PEER_CLOSED);
+    return MOJO_RESULT_FAILED_PRECONDITION;
+  }
+
+  // Alright! We have a message and the caller has provided sufficient storage
+  // in which to receive it, if applicable.
+
+  auto* message = message_event->GetMessage<UserMessageImpl>();
+  if (message->HasContext()) {
+    // Not a serialized message, so there's no more work to do.
+    *out_event = std::move(message_event);
+    return MOJO_RESULT_OK;
+  }
+
+  DCHECK(message->IsSerialized());
+
+  const MessageHeader* header =
+      static_cast<const MessageHeader*>(message->header_);
+  const DispatcherHeader* dispatcher_headers =
+      reinterpret_cast<const DispatcherHeader*>(header + 1);
+
+  if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())
+    return MOJO_RESULT_UNKNOWN;
+
+  // Deserialize dispatchers.
+  if (header->num_dispatchers > 0) {
+    DCHECK(handles);
+    std::vector<Dispatcher::DispatcherInTransit> dispatchers(
+        header->num_dispatchers);
+
+    size_t data_payload_index =
+        sizeof(MessageHeader) +
+        header->num_dispatchers * sizeof(DispatcherHeader);
+    if (data_payload_index > header->header_size)
+      return MOJO_RESULT_UNKNOWN;
+    const char* dispatcher_data = reinterpret_cast<const char*>(
+        dispatcher_headers + header->num_dispatchers);
+    size_t port_index = 0;
+    size_t platform_handle_index = 0;
+    ScopedPlatformHandleVectorPtr msg_handles =
+        message->channel_message_->TakeHandles();
+    const size_t num_msg_handles = msg_handles ? msg_handles->size() : 0;
+    for (size_t i = 0; i < header->num_dispatchers; ++i) {
+      const DispatcherHeader& dh = dispatcher_headers[i];
+      auto type = static_cast<Dispatcher::Type>(dh.type);
+
+      base::CheckedNumeric<size_t> next_payload_index = data_payload_index;
+      next_payload_index += dh.num_bytes;
+      if (!next_payload_index.IsValid() ||
+          header->header_size < next_payload_index.ValueOrDie()) {
+        return MOJO_RESULT_UNKNOWN;
+      }
+
+      base::CheckedNumeric<size_t> next_port_index = port_index;
+      next_port_index += dh.num_ports;
+      if (!next_port_index.IsValid() ||
+          message_event->num_ports() < next_port_index.ValueOrDie()) {
+        return MOJO_RESULT_UNKNOWN;
+      }
+
+      base::CheckedNumeric<size_t> next_platform_handle_index =
+          platform_handle_index;
+      next_platform_handle_index += dh.num_platform_handles;
+      if (!next_platform_handle_index.IsValid() ||
+          num_msg_handles < next_platform_handle_index.ValueOrDie()) {
+        return MOJO_RESULT_UNKNOWN;
+      }
+
+      PlatformHandle* out_handles =
+          num_msg_handles ? msg_handles->data() + platform_handle_index
+                          : nullptr;
+      dispatchers[i].dispatcher = Dispatcher::Deserialize(
+          type, dispatcher_data, dh.num_bytes,
+          message_event->ports() + port_index, dh.num_ports, out_handles,
+          dh.num_platform_handles);
+      if (!dispatchers[i].dispatcher)
+        return MOJO_RESULT_UNKNOWN;
+
+      dispatcher_data += dh.num_bytes;
+      data_payload_index = next_payload_index.ValueOrDie();
+      port_index = next_port_index.ValueOrDie();
+      platform_handle_index = next_platform_handle_index.ValueOrDie();
+    }
+
+    if (!node_controller->core()->AddDispatchersFromTransit(dispatchers,
+                                                            handles)) {
+      return MOJO_RESULT_UNKNOWN;
+    }
+  }
+
+  *out_event = std::move(message_event);
+  return MOJO_RESULT_OK;
+}
+
+// static
+Channel::MessagePtr UserMessageImpl::SerializeEventMessage(
+    std::unique_ptr<ports::UserMessageEvent> message_event) {
+  auto* message = message_event->GetMessage<UserMessageImpl>();
+  Channel::MessagePtr channel_message;
+  if (message->IsSerialized()) {
+    DCHECK(message->channel_message_);
+    message->user_payload_ = nullptr;
+    message->user_payload_size_ = 0;
+    channel_message = std::move(message->channel_message_);
+  } else {
+    // TODO(crbug.com/725321): Implement lazy serialization.
+    NOTREACHED();
+    return nullptr;
+  }
+
+  // Serialize the UserMessageEvent into the front of the message payload where
+  // there is already space reserved for it.
+  void* data;
+  size_t size;
+  NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);
+  message_event->Serialize(data);
+  return channel_message;
+}
+
+size_t UserMessageImpl::num_handles() const {
+  DCHECK(IsSerialized());
+  DCHECK(header_);
+  return static_cast<const MessageHeader*>(header_)->num_dispatchers;
+}
+
+UserMessageImpl::UserMessageImpl(size_t event_size,
+                                 size_t header_size,
+                                 size_t payload_size,
+                                 size_t num_ports,
+                                 size_t num_handles)
+    : ports::UserMessage(&kUserMessageTypeInfo) {
+  const size_t size = event_size + header_size + payload_size;
+  void* data;
+  channel_message_ = NodeChannel::CreateEventMessage(size, &data, num_handles);
+  header_ = static_cast<uint8_t*>(data) + event_size;
+  user_payload_ = static_cast<uint8_t*>(header_) + header_size;
+  user_payload_size_ = payload_size;
+}
+
+UserMessageImpl::UserMessageImpl(Channel::MessagePtr channel_message,
+                                 void* header,
+                                 void* user_payload,
+                                 size_t user_payload_size)
+    : ports::UserMessage(&kUserMessageTypeInfo),
+      channel_message_(std::move(channel_message)),
+      header_(header),
+      user_payload_(user_payload),
+      user_payload_size_(user_payload_size) {}
+
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/system/user_message_impl.h b/mojo/edk/system/user_message_impl.h
new file mode 100644
index 0000000..6699f9a
--- /dev/null
+++ b/mojo/edk/system/user_message_impl.h
@@ -0,0 +1,176 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_EDK_SYSTEM_USER_MESSAGE_IMPL_H_
+#define MOJO_EDK_SYSTEM_USER_MESSAGE_IMPL_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/ports/event.h"
+#include "mojo/edk/system/ports/name.h"
+#include "mojo/edk/system/ports/port_ref.h"
+#include "mojo/edk/system/ports/user_message.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace edk {
+
+class NodeController;
+
+// UserMessageImpl is the sole implementation of ports::UserMessage used to
+// attach message data to any ports::UserMessageEvent.
+//
+// A UserMessageImpl may be either serialized or unserialized. Unserialized
+// instances are serialized lazily only when necessary, i.e., if and when
+// Serialize() is called to obtain a serialized message for wire transfer.
+//
+// TODO(crbug.com/725321): Implement support for unserialized messages.
+class MOJO_SYSTEM_IMPL_EXPORT UserMessageImpl
+    : public NON_EXPORTED_BASE(ports::UserMessage) {
+ public:
+  static const TypeInfo kUserMessageTypeInfo;
+
+  ~UserMessageImpl() override;
+
+  // Creates a new ports::UserMessageEvent with an attached serialized
+  // UserMessageImpl. May fail iff one or more |dispatchers| fails to serialize
+  // (e.g. due to it being in an invalid state.)
+  //
+  // Upon success, MOJO_RESULT_OK is returned and the new UserMessageEvent is
+  // stored in |*out_event|.
+  static MojoResult CreateEventForNewSerializedMessage(
+      uint32_t num_bytes,
+      const Dispatcher::DispatcherInTransit* dispatchers,
+      uint32_t num_dispatchers,
+      std::unique_ptr<ports::UserMessageEvent>* out_event);
+
+  // Creates a new UserMessageImpl from an existing serialized message buffer
+  // which was read from a Channel. Takes ownership of |channel_message|.
+  // |payload| and |payload_size| represent the range of bytes within
+  // |channel_message| which should be parsed by this call.
+  static std::unique_ptr<UserMessageImpl> CreateFromChannelMessage(
+      Channel::MessagePtr channel_message,
+      void* payload,
+      size_t payload_size);
+
+  // Reads a message from |port| on |node_controller|'s Node.
+  //
+  // The message may or may not require deserialization. If the read message is
+  // unserialized, it must have been sent from within the same process that's
+  // receiving it and this call merely passes ownership of the message object
+  // back out of the ports layer. In this case, |read_any_size| must be true,
+  // |*out_event| will own the read message upon return, and all other arguments
+  // are ignored.
+  //
+  // If the read message is still serialized, it must have been created by
+  // CreateFromChannelMessage() above whenever its bytes were first read from a
+  // Channel. In this case, the message will be taken form the port and returned
+  // in |*out_event|, if and only iff |read_any_size| is true or both
+  // |*num_bytes| and |*num_handles| are sufficiently large to contain the
+  // contents of the message. Upon success this returns |MOJO_RESULT_OK|, and
+  // updates |*num_bytes| and |*num_handles| with the actual size of the read
+  // message.
+  //
+  // Upon failure this returns any of various error codes detailed by the
+  // documentation for MojoReadMessage/MojoReadMessageNew in
+  // src/mojo/public/c/system/message_pipe.h.
+  static MojoResult ReadMessageEventFromPort(
+      NodeController* node_controller,
+      const ports::PortRef& port,
+      bool read_any_size,
+      bool may_discard,
+      uint32_t* num_bytes,
+      MojoHandle* handles,
+      uint32_t* num_handles,
+      std::unique_ptr<ports::UserMessageEvent>* out_event);
+
+  // Produces a serialized Channel::Message from the UserMessageEvent in
+  // |event|. |event| must have a UserMessageImpl instance attached.
+  //
+  // If the attached message is not already serialized, it is serialized into a
+  // new Channel::Message; otherwise this simply passes ownership of the
+  // internally owned serialized data.
+  //
+  // In any case, |message_event| is serialized into the front of the message
+  // payload before returning.
+  static Channel::MessagePtr SerializeEventMessage(
+      std::unique_ptr<ports::UserMessageEvent> event);
+
+  // TODO(crbug.com/725321): Support unserialized messages.
+  bool HasContext() const { return false; }
+  uintptr_t context() const { return 0; }
+  bool IsSerialized() const { return true; }
+
+  void* user_payload() {
+    DCHECK(IsSerialized());
+    return user_payload_;
+  }
+
+  const void* user_payload() const {
+    DCHECK(IsSerialized());
+    return user_payload_;
+  }
+
+  size_t user_payload_size() const {
+    DCHECK(IsSerialized());
+    return user_payload_size_;
+  }
+
+  size_t num_handles() const;
+
+  void set_source_node(const ports::NodeName& name) { source_node_ = name; }
+  const ports::NodeName& source_node() const { return source_node_; }
+
+ private:
+  class ReadMessageFilter;
+
+  // Constructs a serialized UserMessageImpl backed by a new Channel::Message
+  // with enough storage for the given number of serialized event, header, and
+  // payload bytes; transferred ports; and system handles.
+  UserMessageImpl(size_t event_size,
+                  size_t header_size,
+                  size_t payload_size,
+                  size_t num_ports,
+                  size_t num_handles);
+
+  // Creates a serialized UserMessageImpl backed by an existing Channel::Message
+  // object. |header| and |user_payload| must be pointers into
+  // |channel_message|'s own storage, and |user_payload_size| is the number of
+  // bytes comprising the user message contents at |user_payload|.
+  UserMessageImpl(Channel::MessagePtr channel_message,
+                  void* header,
+                  void* user_payload,
+                  size_t user_payload_size);
+
+  // Serialized message contents. May be null if this is not a serialized
+  // message.
+  Channel::MessagePtr channel_message_;
+
+  // Only valid if |channel_message_| is non-null. |header_| is the address
+  // of the UserMessageImpl's internal MessageHeader structure within the
+  // serialized message buffer. |user_payload_| is the address of the first byte
+  // after any serialized dispatchers, with the payload comprising the remaining
+  // |user_payload_size_| bytes of the message.
+  void* header_ = nullptr;
+  void* user_payload_ = nullptr;
+  size_t user_payload_size_ = 0;
+
+  // The node name from which this message was received, iff it came from
+  // out-of-process and the source is known.
+  ports::NodeName source_node_ = ports::kInvalidNodeName;
+
+  DISALLOW_COPY_AND_ASSIGN(UserMessageImpl);
+};
+
+}  // namespace edk
+}  // namespace mojo
+
+#endif  // MOJO_EDK_SYSTEM_USER_MESSAGE_IMPL_H_
diff --git a/net/dns/dns_response.h b/net/dns/dns_response.h
index 4d5e6b7..e9231a5d 100644
--- a/net/dns/dns_response.h
+++ b/net/dns/dns_response.h
@@ -26,7 +26,8 @@
 struct Header;
 }
 
-// Parsed resource record.
+// Structure representing a Resource Record as specified in RFC 1035, Section
+// 4.1.3.
 struct NET_EXPORT_PRIVATE DnsResourceRecord {
   DnsResourceRecord();
   ~DnsResourceRecord();
diff --git a/net/socket/tcp_client_socket.cc b/net/socket/tcp_client_socket.cc
index 9e1470ff..df07d11 100644
--- a/net/socket/tcp_client_socket.cc
+++ b/net/socket/tcp_client_socket.cc
@@ -151,32 +151,26 @@
 
   const IPEndPoint& endpoint = addresses_[current_address_index_];
 
-  {
-    // TODO(ricea): Remove ScopedTracker below once crbug.com/436634 is fixed.
-    tracked_objects::ScopedTracker tracking_profile(
-        FROM_HERE_WITH_EXPLICIT_FUNCTION("436634 TCPClientSocket::DoConnect"));
+  if (previously_disconnected_) {
+    use_history_.Reset();
+    connection_attempts_.clear();
+    previously_disconnected_ = false;
+  }
 
-    if (previously_disconnected_) {
-      use_history_.Reset();
-      connection_attempts_.clear();
-      previously_disconnected_ = false;
-    }
+  next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
 
-    next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
+  if (socket_->IsValid()) {
+    DCHECK(bind_address_);
+  } else {
+    int result = OpenSocket(endpoint.GetFamily());
+    if (result != OK)
+      return result;
 
-    if (socket_->IsValid()) {
-      DCHECK(bind_address_);
-    } else {
-      int result = OpenSocket(endpoint.GetFamily());
-      if (result != OK)
+    if (bind_address_) {
+      result = socket_->Bind(*bind_address_);
+      if (result != OK) {
+        socket_->Close();
         return result;
-
-      if (bind_address_) {
-        result = socket_->Bind(*bind_address_);
-        if (result != OK) {
-          socket_->Close();
-          return result;
-        }
       }
     }
   }
diff --git a/net/socket/tcp_socket_win.cc b/net/socket/tcp_socket_win.cc
index 7b4072e..fc85d800 100644
--- a/net/socket/tcp_socket_win.cc
+++ b/net/socket/tcp_socket_win.cc
@@ -815,17 +815,7 @@
   if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len))
     return ERR_ADDRESS_INVALID;
 
-  int result;
-  int os_error;
-  {
-    // TODO(ricea): Remove ScopedTracker below once crbug.com/436634 is fixed.
-    tracked_objects::ScopedTracker tracking_profile(
-        FROM_HERE_WITH_EXPLICIT_FUNCTION("436634 connect()"));
-    result = connect(socket_, storage.addr, storage.addr_len);
-    os_error = WSAGetLastError();
-  }
-
-  if (!result) {
+  if (!connect(socket_, storage.addr, storage.addr_len)) {
     // Connected without waiting!
     //
     // The MSDN page for connect says:
@@ -841,6 +831,7 @@
     if (ResetEventIfSignaled(core_->read_overlapped_.hEvent))
       return OK;
   } else {
+    int os_error = WSAGetLastError();
     if (os_error != WSAEWOULDBLOCK) {
       LOG(ERROR) << "connect failed: " << os_error;
       connect_os_error_ = os_error;
@@ -850,10 +841,6 @@
     }
   }
 
-  // TODO(ricea): Remove ScopedTracker below once crbug.com/436634 is fixed.
-  tracked_objects::ScopedTracker tracking_profile(
-      FROM_HERE_WITH_EXPLICIT_FUNCTION("436634 WatchForRead()"));
-
   core_->WatchForRead();
   return ERR_IO_PENDING;
 }
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index c0279e1..003390a 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -254,11 +254,6 @@
   return rv;
 }
 int TransportConnectJob::DoResolveHost() {
-  // TODO(ricea): Remove ScopedTracker below once crbug.com/436634 is fixed.
-  tracked_objects::ScopedTracker tracking_profile(
-      FROM_HERE_WITH_EXPLICIT_FUNCTION(
-          "436634 TransportConnectJob::DoResolveHost"));
-
   next_state_ = STATE_RESOLVE_HOST_COMPLETE;
   connect_timing_.dns_start = base::TimeTicks::Now();
 
diff --git a/remoting/webapp/base/js/client_plugin_impl.js b/remoting/webapp/base/js/client_plugin_impl.js
index ae72b7e..8ba6065 100644
--- a/remoting/webapp/base/js/client_plugin_impl.js
+++ b/remoting/webapp/base/js/client_plugin_impl.js
@@ -278,10 +278,16 @@
     base.getNumberAttr(message.data, 'videoBandwidth');
     base.getNumberAttr(message.data, 'videoFrameRate');
     base.getNumberAttr(message.data, 'captureLatency');
+    base.getNumberAttr(message.data, 'maxCaptureLatency');
     base.getNumberAttr(message.data, 'encodeLatency');
+    base.getNumberAttr(message.data, 'maxEncodeLatency');
     base.getNumberAttr(message.data, 'decodeLatency');
+    base.getNumberAttr(message.data, 'maxDecodeLatency');
     base.getNumberAttr(message.data, 'renderLatency');
+    base.getNumberAttr(message.data, 'maxRenderLatency');
     base.getNumberAttr(message.data, 'roundtripLatency');
+    base.getNumberAttr(message.data, 'maxRoundtripLatency');
+
     this.perfStats_ =
         /** @type {remoting.ClientSession.PerfStats} */ (message.data);
 
diff --git a/remoting/webapp/base/js/client_session.js b/remoting/webapp/base/js/client_session.js
index 8d02310..9bafa76 100644
--- a/remoting/webapp/base/js/client_session.js
+++ b/remoting/webapp/base/js/client_session.js
@@ -228,6 +228,20 @@
 /** @type {number} */
 remoting.ClientSession.PerfStats.prototype.maxRoundtripLatency = 0;
 
+/**
+ * @param {!remoting.ClientSession.PerfStats} stats
+ * @return {boolean} true if there is any non-zero value in stats, false
+ *     otherwise.
+ */
+remoting.ClientSession.PerfStats.hasValidField = function(stats) {
+  for (var key in stats) {
+    if (stats[key] !== 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // Keys for connection statistics.
 remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH = 'videoBandwidth';
 remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE = 'videoFrameRate';
@@ -561,7 +575,7 @@
 
   if (newState == remoting.ClientSession.State.CONNECTED) {
     this.connectedDisposables_.add(
-        new base.RepeatingTimer(this.reportStatistics.bind(this), 1000));
+        new base.RepeatingTimer(this.reportStatistics.bind(this), 1000 * 60));
     if (this.plugin_.hasCapability(
           remoting.ClientSession.Capability.TOUCH_EVENTS)) {
       this.plugin_.enableTouchEvents(true);
diff --git a/remoting/webapp/base/js/session_logger.js b/remoting/webapp/base/js/session_logger.js
index 616b663..e2a34561 100644
--- a/remoting/webapp/base/js/session_logger.js
+++ b/remoting/webapp/base/js/session_logger.js
@@ -25,8 +25,6 @@
   /** @private */
   this.writeLogEntry_ = writeLogEntry;
   /** @private */
-  this.statsAccumulator_ = new remoting.StatsAccumulator();
-  /** @private */
   this.sessionId_ = '';
   /** @private */
   this.sessionIdGenerationTime_ = 0;
@@ -235,11 +233,7 @@
     this.sessionEndTime_ = Date.now();
   }
 
-  // Don't accumulate connection statistics across state changes.
-  this.logAccumulatedStatistics_();
-  this.statsAccumulator_.empty();
-
-    if (state == remoting.ChromotingEvent.SessionState.CLOSED ||
+  if (state == remoting.ChromotingEvent.SessionState.CLOSED ||
       state == remoting.ChromotingEvent.SessionState.CONNECTION_DROPPED) {
     this.flushFeatureTracker();
   }
@@ -248,17 +242,13 @@
 /**
  * Logs connection statistics.
  *
- * @param {Object<number>} stats The connection statistics
+ * @param {remoting.ClientSession.PerfStats} stats The connection statistics
  */
 remoting.SessionLogger.prototype.logStatistics = function(stats) {
-  this.maybeExpireSessionId_();
-  // Store the statistics.
-  this.statsAccumulator_.add(stats);
-  // Send statistics to the server if they've been accumulating for at least
-  // 60 seconds.
-  if (this.statsAccumulator_.getTimeSinceFirstValue() >=
-      remoting.SessionLogger.CONNECTION_STATS_ACCUMULATE_TIME) {
-    this.logAccumulatedStatistics_();
+  if (stats && remoting.ClientSession.PerfStats.hasValidField(stats)) {
+    this.maybeExpireSessionId_();
+    var entry = this.makeStats_(stats);
+    this.log_(entry);
   }
 };
 
@@ -266,7 +256,9 @@
  * Logs host and client dimensions.
  *
  * @param {{width: number, height: number}} hostSize
+ * @param {number} hostDpi
  * @param {{width: number, height: number}} clientPluginSize
+ * @param {number} clientDpi
  * @param {{width: number, height: number}} clientWindowSize
  * @param {boolean} clientFullscreen
  */
@@ -309,8 +301,10 @@
 
 /**
  * @param {{width: number, height: number}} hostSize
+ * @param {number} hostDpi
  * @param {{width: number, height: number}} clientPluginSize
  * @param {{width: number, height: number}} clientWindowSize
+ * @param {number} clientDpi
  * @param {boolean} clientFullscreen
  * @return {remoting.ChromotingEvent}
  * @private
@@ -354,46 +348,28 @@
 };
 
 /**
- * @return {remoting.ChromotingEvent}
+ * @param {!remoting.ClientSession.PerfStats} perfStats
+ * @return {!remoting.ChromotingEvent}
  * @private
  */
-remoting.SessionLogger.prototype.makeStats_ = function() {
-  var perfStats = this.statsAccumulator_.getPerfStats();
-  if (Boolean(perfStats)) {
-    var entry = new remoting.ChromotingEvent(
-        remoting.ChromotingEvent.Type.CONNECTION_STATISTICS);
-    this.fillEvent_(entry);
-    entry.video_bandwidth = perfStats.videoBandwidth;
-    entry.capture_latency = perfStats.captureLatency;
-    entry.encode_latency = perfStats.encodeLatency;
-    entry.decode_latency = perfStats.decodeLatency;
-    entry.render_latency = perfStats.renderLatency;
-    entry.roundtrip_latency = perfStats.roundtripLatency;
-    entry.max_capture_latency = perfStats.maxCaptureLatency;
-    entry.max_encode_latency = perfStats.maxEncodeLatency;
-    entry.max_decode_latency = perfStats.maxDecodeLatency;
-    entry.max_render_latency = perfStats.maxRenderLatency;
-    entry.max_roundtrip_latency = perfStats.maxRoundtripLatency;
-    return entry;
-  }
-  return null;
+remoting.SessionLogger.prototype.makeStats_ = function(perfStats) {
+  var entry = new remoting.ChromotingEvent(
+      remoting.ChromotingEvent.Type.CONNECTION_STATISTICS);
+  this.fillEvent_(entry);
+  entry.video_bandwidth = perfStats.videoBandwidth;
+  entry.capture_latency = perfStats.captureLatency;
+  entry.encode_latency = perfStats.encodeLatency;
+  entry.decode_latency = perfStats.decodeLatency;
+  entry.render_latency = perfStats.renderLatency;
+  entry.roundtrip_latency = perfStats.roundtripLatency;
+  entry.max_capture_latency = perfStats.maxCaptureLatency;
+  entry.max_encode_latency = perfStats.maxEncodeLatency;
+  entry.max_decode_latency = perfStats.maxDecodeLatency;
+  entry.max_render_latency = perfStats.maxRenderLatency;
+  entry.max_roundtrip_latency = perfStats.maxRoundtripLatency;
+  return entry;
 };
 
-/**
- * Moves connection statistics from the accumulator to the log server.
- *
- * If all the statistics are zero, then the accumulator is still emptied,
- * but the statistics are not sent to the log server.
- *
- * @private
- */
-remoting.SessionLogger.prototype.logAccumulatedStatistics_ = function() {
-  var entry = this.makeStats_();
-  if (entry) {
-    this.log_(entry);
-  }
-  this.statsAccumulator_.empty();
-};
 
 /**
  * @param {remoting.ChromotingEvent} entry
@@ -558,8 +534,4 @@
 // The maximum age of a session ID, in milliseconds.
 remoting.SessionLogger.MAX_SESSION_ID_AGE = 24 * 60 * 60 * 1000;
 
-// The time over which to accumulate connection statistics before logging them
-// to the server, in milliseconds.
-remoting.SessionLogger.CONNECTION_STATS_ACCUMULATE_TIME = 60 * 1000;
-
 })();
diff --git a/remoting/webapp/base/js/session_logger_unittest.js b/remoting/webapp/base/js/session_logger_unittest.js
index 460dec5..c1d55b45 100644
--- a/remoting/webapp/base/js/session_logger_unittest.js
+++ b/remoting/webapp/base/js/session_logger_unittest.js
@@ -277,36 +277,19 @@
   logger.setConnectionType('direct');
   logger.setHost(fakeHost);
 
-  // Log the statistics.
-  logger.logStatistics({
-    videoBandwidth: 1.0,
-    captureLatency: 1.0,
-    encodeLatency: 1.0,
-    decodeLatency: 0.0,
-    renderLatency: 1.0,
-    roundtripLatency: 1.0
-  });
-
-  logger.logStatistics({
-    videoBandwidth: 2.0,
-    captureLatency: 2.0,
-    encodeLatency: 1.0,
-    decodeLatency: 0.0,
-    renderLatency: 2.0,
-    roundtripLatency: 2.0
-  });
-
-  sinon.assert.notCalled(logWriterSpy);
-  // Stats should only be accumulated at |CONNECTION_STATS_ACCUMULATE_TIME|.
-  clock.tick(remoting.SessionLogger.CONNECTION_STATS_ACCUMULATE_TIME + 10);
-
+  // Log the statistics
   logger.logStatistics({
     videoBandwidth: 3.0,
-    captureLatency: 3.0,
-    encodeLatency: 1.0,
-    decodeLatency: 0.0,
-    renderLatency: 0.0,
-    roundtripLatency: 0.0
+    captureLatency: 1.0,
+    maxCaptureLatency: 3.0,
+    encodeLatency: 2.0,
+    maxEncodeLatency: 4.0,
+    decodeLatency: 3.0,
+    maxDecodeLatency: 5.0,
+    renderLatency: 4.0,
+    maxRenderLatency: 6.0,
+    roundtripLatency: 5.0,
+    maxRoundtripLatency: 7.0
   });
 
   verifyEvent(assert, 0, {
@@ -323,12 +306,17 @@
     host_os: remoting.ChromotingEvent.Os.OTHER,
     host_os_version: 'host_os_version',
     session_id: logger.getSessionId(),
-    video_bandwidth: 2.0,
-    capture_latency: 2.0,
-    encode_latency: 1.0,
-    decode_latency: 0.0,
-    render_latency: 1.0,
-    roundtrip_latency: 1.0
+    video_bandwidth: 3.0,
+    capture_latency: 1.0,
+    encode_latency: 2.0,
+    decode_latency: 3.0,
+    render_latency: 4.0,
+    roundtrip_latency: 5.0,
+    max_capture_latency: 3.0,
+    max_encode_latency: 4.0,
+    max_decode_latency: 5.0,
+    max_render_latency: 6.0,
+    max_roundtrip_latency: 7.0
   });
 });
 
@@ -349,17 +337,6 @@
     roundtripLatency: 0.0
   });
 
-  clock.tick(remoting.SessionLogger.CONNECTION_STATS_ACCUMULATE_TIME + 10);
-
-  logger.logStatistics({
-    videoBandwidth: 0.0,
-    captureLatency: 0.0,
-    encodeLatency: 0.0,
-    decodeLatency: 0.0,
-    renderLatency: 0.0,
-    roundtripLatency: 0.0
-  });
-
   sinon.assert.notCalled(logWriterSpy);
 
 });
diff --git a/remoting/webapp/base/js/stats_accumulator.js b/remoting/webapp/base/js/stats_accumulator.js
deleted file mode 100644
index 8d4cb6b..0000000
--- a/remoting/webapp/base/js/stats_accumulator.js
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * The webapp reads the plugin's connection statistics frequently (once per
- * second). It logs statistics to the server less frequently, to keep
- * bandwidth and storage costs down. This class bridges that gap, by
- * accumulating high-frequency numeric data, and providing statistics
- * summarising that data.
- */
-
-'use strict';
-
-/** @suppress {duplicate} */
-var remoting = remoting || {};
-
-(function() {
-
-/**
- * @constructor
- */
-remoting.StatsAccumulator = function() {
-  /**
-   * A map from names to lists of values.
-   * @private {Object<Array<number>>}
-   */
-  this.valueLists_ = {};
-
-  /**
-   * The first time, after this object was most recently initialized or emptied,
-   * at which a value was added to this object.
-   * @private {?number}
-   */
-  this.timeOfFirstValue_ = null;
-};
-
-/**
- * @param {Object<number>} stats
- * @return {boolean} true if there is any non-zero value in stats, false
- *     otherwise.
- */
-function hasValidField(stats) {
-  for (var key in stats) {
-    if (stats[key] !== 0) {
-      return true;
-    }
-  }
-  return false;
-}
-
-/**
- * Adds values to this object. Do nothing if newValues has no valid field.
- *
- * @param {Object<number>} newValues
- */
-remoting.StatsAccumulator.prototype.add = function(newValues) {
-  if (!hasValidField(newValues)) {
-    return;
-  }
-  for (var key in newValues) {
-    this.getValueList(key).push(newValues[key]);
-  }
-  if (this.timeOfFirstValue_ === null) {
-    this.timeOfFirstValue_ = new Date().getTime();
-  }
-};
-
-/**
- * Empties this object.
- */
-remoting.StatsAccumulator.prototype.empty = function() {
-  this.valueLists_ = {};
-  this.timeOfFirstValue_ = null;
-};
-
-/**
- * Gets the number of milliseconds since the first value was added to this
- * object, after this object was most recently initialized or emptied.
- *
- * @return {number} milliseconds since the first value
- */
-remoting.StatsAccumulator.prototype.getTimeSinceFirstValue = function() {
-  if (this.timeOfFirstValue_ === null) {
-    return 0;
-  }
-  return new Date().getTime() - this.timeOfFirstValue_;
-};
-
-/**
- * Calculates the mean of the values for a given key.
- *
- * @param {string} key
- * @return {number} the mean of the values for that key
- */
-remoting.StatsAccumulator.prototype.calcMean = function(key) {
-  /**
-   * @param {Array<number>} values
-   * @return {number}
-   */
-  var calcMean = function(values) {
-    if (values.length == 0) {
-      return 0.0;
-    }
-    var sum = 0;
-    for (var i = 0; i < values.length; i++) {
-      sum += values[i];
-    }
-    return sum / values.length;
-  };
-  return this.map(key, calcMean);
-};
-
-/**
- * Finds the max of the values for a given key.
- *
- * @param {string} key
- * @return {number} the max of the values for that key
- */
-remoting.StatsAccumulator.prototype.calcMax = function(key) {
-  /**
-   * @param {Array<number>} values
-   * @return {number}
-   */
-  var calcMax = function(values) {
-    if (!values || !values.length) {
-      return 0;
-    }
-    return Math.max.apply(null, values);
-  };
-  return this.map(key, calcMax);
-};
-
-/**
- * Applies a given map to the list of values for a given key.
- *
- * @param {string} key
- * @param {function(Array<number>): number} map
- * @return {number} the result of applying that map to the list of values for
- *     that key
- */
-remoting.StatsAccumulator.prototype.map = function(key, map) {
-  return map(this.getValueList(key));
-};
-
-/**
- * Gets the list of values for a given key.
- * If this object contains no values for that key, then this routine creates
- * an empty list, stores it in this object, and returns it.
- *
- * @private
- * @param {string} key
- * @return {Array<number>} the list of values for that key
- */
-remoting.StatsAccumulator.prototype.getValueList = function(key) {
-  var valueList = this.valueLists_[key];
-  if (!valueList) {
-    valueList = [];
-    this.valueLists_[key] = valueList;
-  }
-  return valueList;
-};
-
-/**
- * @return {?remoting.ClientSession.PerfStats} returns null if all fields are
- *     zero.
- */
-remoting.StatsAccumulator.prototype.getPerfStats = function() {
-  var stats = new remoting.ClientSession.PerfStats();
-  stats.videoBandwidth = this.calcMean('videoBandwidth');
-  stats.captureLatency = this.calcMean('captureLatency');
-  stats.maxCaptureLatency = this.calcMax('maxCaptureLatency');
-  stats.encodeLatency = this.calcMean('encodeLatency');
-  stats.maxEncodeLatency = this.calcMax('maxEncodeLatency');
-  stats.decodeLatency = this.calcMean('decodeLatency');
-  stats.maxDecodeLatency = this.calcMax('maxDecodeLatency');
-  stats.renderLatency = this.calcMean('renderLatency');
-  stats.maxRenderLatency = this.calcMax('maxRenderLatency');
-  stats.roundtripLatency = this.calcMean('roundtripLatency');
-  stats.maxRoundtripLatency = this.calcMax('maxRoundtripLatency');
-
-  return hasValidField(stats) ? stats : null;
-};
-
-})();
\ No newline at end of file
diff --git a/remoting/webapp/build_template.gni b/remoting/webapp/build_template.gni
index e3c0339..28b4c91 100644
--- a/remoting/webapp/build_template.gni
+++ b/remoting/webapp/build_template.gni
@@ -83,7 +83,7 @@
                 "--out_file",
                 rebase_path(target_jscompile_stamp, root_build_dir),
                 "--closure_args",
-              ] + closure_args + extra_closure_args
+              ] + default_closure_args + extra_closure_args
       args += [ "--externs" ] + rebase_path(externs, root_build_dir)
     }
   }
diff --git a/remoting/webapp/files.gni b/remoting/webapp/files.gni
index f32fbad..2dfd7428 100644
--- a/remoting/webapp/files.gni
+++ b/remoting/webapp/files.gni
@@ -207,7 +207,6 @@
 remoting_webapp_shared_js_logging_files = [
   "base/js/format_iq.js",
   "base/js/session_logger.js",
-  "base/js/stats_accumulator.js",
 ]
 
 # Remoting signaling files.
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index e9c3b38..61df261 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -161,7 +161,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -624,7 +624,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -663,7 +663,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -702,7 +702,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -781,7 +781,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -889,7 +889,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1007,7 +1007,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1047,7 +1047,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1323,7 +1323,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1362,7 +1362,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1401,7 +1401,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -1714,7 +1714,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2177,7 +2177,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2216,7 +2216,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2255,7 +2255,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2334,7 +2334,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2442,7 +2442,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2560,7 +2560,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2600,7 +2600,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2907,7 +2907,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2946,7 +2946,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
@@ -2985,7 +2985,7 @@
           ],
           "dimension_sets": [
             {
-              "android_devices": "4",
+              "android_devices": "1",
               "device_os": "KTU84P",
               "device_type": "hammerhead"
             }
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f723435..0f8714a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2915,6 +2915,26 @@
             ]
         }
     ],
+    "SocketReadIfReady": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "ios",
+                "win"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SocketReadIfReady"
+                    ]
+                }
+            ]
+        }
+    ],
     "SpeculativeLaunchServiceWorker": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 936d1f2..096bd59 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2016,6 +2016,13 @@
 crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/crossorigin.html [ Failure ]
 crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/errorhandling.html [ Failure ]
 crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/imports.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html [ Failure ]
+crbug.com/594639 external/wpt/html/semantics/scripting-1/the-script-element/module/specifier-error.html [ Failure ]
 
 # This test has a failure console message with specific performance
 # numbers so a consistent baseline cannot be added. This test could be
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js
new file mode 100644
index 0000000..a53a3beb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/bad-module-specifier.js
@@ -0,0 +1,3 @@
+import "string-without-dot-slash-prefix";
+import "./this.js";
+log.push("bad-module-specifier");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html
new file mode 100644
index 0000000..ff580d48
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of compilation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that syntax errors lead to SyntaxError events on window, " +
+        "and that exceptions are remembered.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 5);
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_true(log.every(exn => exn === log[0]));
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html
new file mode 100644
index 0000000..131a6e4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/compilation-error-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of compilation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that syntax errors lead to SyntaxError events on window, " +
+        "and that exceptions are remembered.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 5);
+      assert_equals(log[0].constructor, SyntaxError);
+      assert_true(log.every(exn => exn === log[0]));
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./syntaxerror.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js
new file mode 100644
index 0000000..1f91f93
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access-a.js
@@ -0,0 +1,3 @@
+log.push("cycle-tdz-access-a");
+import { Y } from "./cycle-tdz-access.js";
+export var X = Y;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js
new file mode 100644
index 0000000..9df68b3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-tdz-access.js
@@ -0,0 +1,3 @@
+log.push("cycle-tdz-access");
+import { X } from "./cycle-tdz-access-a.js";
+export let Y = X;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js
new file mode 100644
index 0000000..12994f2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable-a.js
@@ -0,0 +1,2 @@
+export {x} from "./cycle-unresolvable.js";
+log.push("cycle-unresolvable-a");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js
new file mode 100644
index 0000000..61c6d8d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/cycle-unresolvable.js
@@ -0,0 +1,2 @@
+export {x} from "./cycle-unresolvable-a.js";
+log.push("cycle-unresolvable");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html
new file mode 100644
index 0000000..21f005a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that exceptions during evaluation lead to error events on " +
+        "window, and that exceptions are remembered.\n");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[1];
+      assert_array_equals(log, ["throw", exn, exn, exn, exn]);
+      assert_true(exn.foo);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="throw.js" onerror="unreachable()"></script>
+<script type="module" src="throw.js" onerror="unreachable()"></script>
+<script type="module" src="throw.js" async onerror="unreachable()"></script>
+<script type="module" src="throw.js" nomodule onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html
new file mode 100644
index 0000000..6aedc060
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-2.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that ill-founded cyclic dependencies cause ReferenceError " +
+        "during evaluation, which leads to error events on window, and that " +
+        "exceptions are remembered.\n");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[1];
+      assert_array_equals(log, ["cycle-tdz-access-a", exn, exn, exn]);
+      assert_equals(exn.constructor, ReferenceError);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="cycle-tdz-access.js" async onerror="unreachable()"></script>
+<script type="module" src="cycle-tdz-access.js" nomodule onerror="unreachable()"></script>
+<script type="module" src="cycle-tdz-access.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html
new file mode 100644
index 0000000..71d61f5a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-3.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 3</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that exceptions during evaluation lead to error events on " +
+        "window, and that exceptions are remembered.\n");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[1];
+      assert_array_equals(log, ["throw", exn, exn, exn, exn, exn]);
+      assert_true(exn.foo);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw.js" onerror="unreachable()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html
new file mode 100644
index 0000000..dcb0108
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/evaluation-error-4.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Handling of evaluation errors, 4</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that exceptions during evaluation lead to error events on " +
+        "window, and that exceptions are remembered.\n");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[1];
+      assert_array_equals(log, ["throw", exn, exn, exn, exn, exn]);
+      assert_true(exn.foo);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"></script>
+<script type="module" src="./throw-nested.js" onerror="unreachable()"></script>
+<script type="module" src="./throw.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something-nested.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something-nested.js
new file mode 100644
index 0000000..ca806eb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something-nested.js
@@ -0,0 +1,2 @@
+log.push("export-something-nested");
+export * from "./export-something.js";
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something.js
new file mode 100644
index 0000000..cf2c3a99
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/export-something.js
@@ -0,0 +1,3 @@
+log.push("export-something");
+export let foo = 42;
+export function set_foo(x) { foo = x };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html
new file mode 100644
index 0000000..170bb665
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Handling of fetch errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that failure to fetch root leads to error event on script.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, ["script"]);
+    }));
+</script>
+<script type="module" src="./no-such-file.js" onerror="log.push('script')"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html
new file mode 100644
index 0000000..9386ce6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Handling of fetch errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that failure to fetch dependency leads to error event on script.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, ["script"]);
+    }));
+</script>
+<script type="module" src="./fetch-error-2.js" onerror="log.push('script')"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js
new file mode 100644
index 0000000..20c0ea6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/fetch-error-2.js
@@ -0,0 +1,2 @@
+import "./no-such-file.js"
+import "./this.js";
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js
new file mode 100644
index 0000000..32d90287
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/import-something-namespace.js
@@ -0,0 +1,5 @@
+log.push("import-something-namespace");
+log.push(m.foo);
+m.set_foo(43);
+log.push(m.foo);
+import * as m from "./export-something.js";
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
index a9556b1a..efdf587 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html
@@ -1,22 +1,33 @@
-<!--
-This test case caught a crash bug in Chrome, due to a logic bug in the spec when
-a second <script> element tried to load a previously-failed module. As such, the
-test passes as long as nothing crashes.
-
-See https://github.com/whatwg/html/pull/2559 for background.
--->
-
 <!DOCTYPE html>
-<html>
-<head>
-    <title>html-script-module-instantiation-error-1</title>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-    <h1>html-script-module-instantiation-error-1</h1>
-    <script> setup({allow_uncaught_exception: true}) </script>
-    <script type="module" src="./instantiation-error-1.js"></script>
-    <script type="module" src="./instantiation-error-1.js"></script>
-</body>
-</html>
+<title>Handling of instantiation errors, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that missing exports lead to SyntaxError events on window and " +
+        "load events on script, and that exceptions are remembered");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[0];
+      assert_array_equals(log, [exn, 1, exn, 2, exn, 3, exn, 4, exn, 5]);
+      assert_equals(exn.constructor, SyntaxError);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./missing-export.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./missing-export.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./missing-export-nested.js"
+    onerror="unreachable()" onload="log.push(3)"></script>
+<script type="module" src="./missing-export.js"
+    onerror="unreachable()" onload="log.push(4)"></script>
+<script type="module" src="./missing-export-nested.js"
+    onerror="unreachable()" onload="log.push(5)"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
new file mode 100644
index 0000000..3d50ce6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-2.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that missing exports lead to SyntaxError events on window and " +
+        "load events on script, and that exceptions are remembered");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[0];
+      assert_array_equals(log, [exn, 1, exn, 2, exn, 3, exn, 4, exn, 5]);
+      assert_equals(exn.constructor, SyntaxError);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./missing-export-nested.js"
+    onerror="unreachable()" onload="log.push(1)"></script>
+<script type="module" src="./missing-export-nested.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./missing-export.js"
+    onerror="unreachable()" onload="log.push(3)"></script>
+<script type="module" src="./missing-export-nested.js"
+    onerror="unreachable()" onload="log.push(4)"></script>
+<script type="module" src="./missing-export.js"
+    onerror="unreachable()" onload="log.push(5)"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
new file mode 100644
index 0000000..ab510c675
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Handling of instantiation errors, 3</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that unresolvable cycles lead to SyntaxError events on window " +
+        "and load events on script, and that exceptions are remembered");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      const exn = log[0];
+      assert_array_equals(log, [exn, 1, exn, 2, exn, 3]);
+      assert_equals(exn.constructor, SyntaxError);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./cycle-unresolvable.js"
+    onerror="unreachable()" onload="log.push(1)" nomodule></script>
+<script type="module" src="./cycle-unresolvable-a.js"
+    onerror="unreachable()" onload="log.push(2)"></script>
+<script type="module" src="./cycle-unresolvable.js"
+    onerror="unreachable()" onload="log.push(3)" async></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html
new file mode 100644
index 0000000..00269ef
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-namespace-request.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Late namespace request</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test the situation where a module is instantiated without the " +
+        "need for a namespace object, but later on a different module " +
+        "requests the namespace.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log,
+          ["export-something",
+           "import-something-namespace", 42, 43]);
+    }));
+</script>
+<script type="module" src="export-something.js"></script>
+<script type="module" src="import-something-namespace.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html
new file mode 100644
index 0000000..d40bb0ac
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/late-star-export-request.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Late star-export request</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test the situation where a module is instantiated without a use of " +
+        "its star-exports, but later on a different module requests them.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [
+          "export-something", "export-something-nested",
+          "import-something-namespace", 42, 43]);
+    }));
+</script>
+<script type="module" src="export-something-nested.js"></script>
+<script type="module">
+    log.push("import-something-namespace");
+    log.push(foo);
+    set_foo(43);
+    log.push(foo);
+    import {foo, set_foo} from "./export-something-nested.js";
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js
new file mode 100644
index 0000000..860d2bf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export-nested.js
@@ -0,0 +1,2 @@
+import "./missing-export.js";
+log.push("nested-missing-export");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export.js
new file mode 100644
index 0000000..e6f5746e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/missing-export.js
@@ -0,0 +1,2 @@
+import something from "./missing-export.js";
+log.push("missing-export");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html
new file mode 100644
index 0000000..ae82e13
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Once as module script, once as classic script</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that evaluating something as classic script does not prevent " +
+        "it from being evaluated as module script.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [window, undefined]);
+    }));
+</script>
+<script type="module" src="this.js"></script>
+<script src="this.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html
new file mode 100644
index 0000000..2a879f3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/module-vs-script-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Once as classic script, once as module script</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that evaluating something as classic script does not prevent " +
+        "it from being evaluated as module script.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [window, undefined]);
+    }));
+</script>
+<script type="module" src="this.js"></script>
+<script src="this.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js
new file mode 100644
index 0000000..3801ae8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nested-missing-export.js
@@ -0,0 +1,2 @@
+import "./missing-export.js";
+log.push("missing-export-nested");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html
new file mode 100644
index 0000000..656c99b2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/nomodule-attribute.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>The 'nomodule' attribute</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that 'nomodule' has the desired effect on classic scripts, but " +
+        "no effect on module scripts.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [undefined]);
+    }));
+
+</script>
+<script type="module" src="this.js" nomodule></script>
+<script src="this.js" nomodule></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html
new file mode 100644
index 0000000..cc4e2d6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Single evaluation, 1</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that a module is evaluated only once, and that 'this' is " +
+        "undefined (because of strict mode).");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [undefined, "this-nested"]);
+    }));
+</script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html
new file mode 100644
index 0000000..790e2fa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/single-evaluation-2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Single evaluation, 2</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    const test_load = async_test(
+        "Test that a module is evaluated only once, and that 'this' is " +
+        "undefined (because of strict mode).");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_array_equals(log, [undefined, "this-nested"]);
+    }));
+</script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
+<script type="module" src="this-nested.js"></script>
+<script type="module" src="this.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/specifier-error.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/specifier-error.html
new file mode 100644
index 0000000..2cc393e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/specifier-error.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Handling of invalid specifiers</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    window.log = [];
+
+    window.addEventListener("error", ev => log.push(ev.error));
+
+    const test_load = async_test(
+        "Test that invalid module specifier leads to TypeError on window.");
+    window.addEventListener("load", test_load.step_func_done(ev => {
+      assert_equals(log.length, 1);
+      assert_equals(log[0].constructor, TypeError);
+    }));
+
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script type="module" src="./bad-module-specifier.js" onerror="unreachable()"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js
new file mode 100644
index 0000000..de1b053c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror-nested.js
@@ -0,0 +1,2 @@
+import "./syntaxerror.js";
+log.push("nested-syntaxerror");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror.js
new file mode 100644
index 0000000..31a9e2c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/syntaxerror.js
@@ -0,0 +1,2 @@
+log.push("syntaxerror");
+%!#$@#$@#$@
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this-nested.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this-nested.js
new file mode 100644
index 0000000..f204812f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this-nested.js
@@ -0,0 +1,2 @@
+import "./this.js";
+log.push("this-nested");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this.js
new file mode 100644
index 0000000..996a439
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/this.js
@@ -0,0 +1 @@
+log.push(this);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw-nested.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw-nested.js
new file mode 100644
index 0000000..f1801ea
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw-nested.js
@@ -0,0 +1,2 @@
+import "./throw.js";
+log.push("throw-nested");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw.js b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw.js
new file mode 100644
index 0000000..cef79182
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/throw.js
@@ -0,0 +1,2 @@
+log.push("throw");
+throw {foo: true}
diff --git a/third_party/WebKit/LayoutTests/fast/block/hr-with-float.html b/third_party/WebKit/LayoutTests/fast/block/hr-with-float.html
new file mode 100644
index 0000000..9f36470
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/block/hr-with-float.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<style>
+    .child { float:left; width:100%; height:100px; }
+</style>
+<p>There should be a blue square below.</p>
+<div style="position:relative; width:100px;">
+    <div style="float:left; width:50px; height:100px; background:blue;"></div>
+    <hr id="hr" style="width:50px; border:none; background:blue;">
+</div>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+    // The HTML parser auto-terminates HR elements, so we need to add a child
+    // like this:
+    var child = document.createElement("div");
+    child.className = "child";
+    document.getElementById("hr").appendChild(child);
+
+    test(() => {
+	var hr = document.getElementById("hr");
+        assert_equals(hr.offsetLeft, 50);
+        assert_equals(hr.offsetHeight, 100);
+    }, "HR establishes a block formatting context");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/ruby/float-overhang-from-ruby-text-expected.txt b/third_party/WebKit/LayoutTests/fast/ruby/float-overhang-from-ruby-text-expected.txt
index c887524..4ffbd81 100644
--- a/third_party/WebKit/LayoutTests/fast/ruby/float-overhang-from-ruby-text-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/ruby/float-overhang-from-ruby-text-expected.txt
@@ -6,7 +6,7 @@
       LayoutBlockFlow {DIV} at (0,0) size 784x75
         LayoutRuby (inline) {RUBY} at (0,0) size 100x50
           LayoutRubyRun (anonymous) at (0,25) size 100x50
-            LayoutRubyText {RT} at (0,-25) size 100x25
+            LayoutRubyText {RT} at (0,-25) size 100x50
               LayoutText {#text} at (50,0) size 25x25
                 text run at (50,0) width 25: "a"
               LayoutBlockFlow (floating) {DIV} at (0,0) size 50x50 [bgcolor=#ADD8E6]
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view-expected.txt
new file mode 100644
index 0000000..f374ce0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view-expected.txt
@@ -0,0 +1,33 @@
+Tests refreshing the database information and data views.
+
+Dumping IndexedDB tree:
+    (empty)
+Created database.
+Dumping IndexedDB tree:
+    database: testDatabase - http://127.0.0.1:8000
+        (no object stores)
+Created first objectstore.
+Dumping IndexedDB tree:
+    database: testDatabase - http://127.0.0.1:8000
+        Object store: testObjectStore1
+            Index: testIndex
+Created second objectstore.
+Dumping IndexedDB tree:
+    database: testDatabase - http://127.0.0.1:8000
+        Object store: testObjectStore1
+            Index: testIndex
+        Object store: testObjectStore2
+            Index: testIndex
+Added testObjectStore1 entry.
+Dumping ObjectStore data:
+    Object store: testObjectStore1
+            (no entries)
+    Object store: testObjectStore2
+            (no entries)
+Refreshed database.
+Dumping ObjectStore data:
+    Object store: testObjectStore1
+            Key = testKey, value = [object Object]
+    Object store: testObjectStore2
+            (no entries)
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view.html b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view.html
new file mode 100644
index 0000000..d9c3bb0d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/database-refresh-view.html
@@ -0,0 +1,187 @@
+<html>
+<head>
+<script src="../inspector-test.js"></script>
+<script src="../resources-test.js"></script>
+<script src="../console-test.js"></script>
+<script src="indexeddb-test.js"></script>
+<script>
+
+function onIndexedDBError(e) {
+    console.error("IndexedDB error: " + e);
+}
+
+function createDatabase(databaseName) {
+    var callback;
+    var promise = new Promise((fulfill) => callback = fulfill);
+    var request = indexedDB.open(databaseName);
+    request.onerror = onIndexedDBError;
+    request.onsuccess = function(event) {
+        request.result.close();
+        callback();
+    }
+    return promise;
+}
+
+function createObjectStore(databaseName, objectStoreName, indexName, keyPath) {
+    var callback;
+    var promise = new Promise((fulfill) => callback = fulfill);
+    var request = indexedDB.open(databaseName);
+    request.onerror = onIndexedDBError;
+    request.onsuccess = function(event) {
+        var db = request.result;
+        var version = db.version;
+        db.close();
+
+        var upgradeRequest = indexedDB.open(databaseName, version + 1);
+
+        upgradeRequest.onerror = onIndexedDBError;
+        upgradeRequest.onupgradeneeded = function(e) {
+            var upgradeDb = e.target.result;
+            var store = upgradeDb.createObjectStore(objectStoreName, { keyPath: "test", autoIncrement: false });
+            store.createIndex(indexName, "test", { unique: false, multiEntry: false });
+            callback();
+        }
+        upgradeRequest.onsuccess = function(e) {
+            var upgradeDb = e.target.result;
+            upgradeDb.close();
+            callback();
+        }
+    }
+    return promise;
+}
+
+function addIDBValue(databaseName, objectStoreName, key, value) {
+    var callback;
+    var promise = new Promise((fulfill) => callback = fulfill);
+    var request = indexedDB.open(databaseName);
+    request.onerror = onIndexedDBError;
+    request.onsuccess = function(event) {
+        var db = request.result;
+        var transaction = db.transaction(objectStoreName, "readwrite");
+        var store = transaction.objectStore(objectStoreName);
+        store.put({ test: key, testValue: value });
+
+        transaction.onerror = onIndexedDBError;
+        transaction.oncomplete = function() {
+            db.close();
+            callback();
+        };
+    }
+    return promise;
+}
+
+async function test()
+{
+    var databaseName = "testDatabase";
+    var objectStoreName1 = "testObjectStore1";
+    var objectStoreName2 = "testObjectStore2";
+    var indexName = "testIndex";
+    var keyPath = "testKey";
+
+    var indexedDBModel = InspectorTest.mainTarget.model(Resources.IndexedDBModel);
+    var databaseId;
+
+    function waitRefreshDatabase(callback) {
+        var view = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0]._view;
+        InspectorTest.addSniffer(Resources.IDBDatabaseView.prototype, "_updatedForTests", callback, false);
+        view._refreshDatabaseButtonClicked();
+    }
+
+    function waitUpdateDataView(callback) {
+        InspectorTest.addSniffer(Resources.IDBDataView.prototype, "_updatedDataForTests", callback, false);
+    }
+
+    function waitDatabaseLoaded(callback) {
+        var event = indexedDBModel.addEventListener(Resources.IndexedDBModel.Events.DatabaseLoaded, () => {
+            Common.EventTarget.removeEventListeners([event]);
+            callback();
+        });
+    }
+
+    function waitDatabaseAdded(callback) {
+        var event = indexedDBModel.addEventListener(Resources.IndexedDBModel.Events.DatabaseAdded, () => {
+            Common.EventTarget.removeEventListeners([event]);
+            callback();
+        });
+        UI.panels.resources._sidebar.indexedDBListTreeElement.refreshIndexedDB();
+    }
+
+    function dumpObjectStores() {
+        InspectorTest.addResult("Dumping ObjectStore data:");
+
+        var idbDatabaseTreeElement = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0];
+        for (var i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
+            var objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
+            InspectorTest.addResult("    Object store: " + objectStoreTreeElement.title);
+            var entries = objectStoreTreeElement._view._entries;
+            if (!entries.length) {
+                InspectorTest.addResult("            (no entries)");
+                continue;
+            }
+            for (var j = 0; j < entries.length; ++j) {
+                InspectorTest.addResult("            Key = " + entries[j].key._value + ", value = " + entries[j].value);
+            }
+        }
+    }
+
+    // Initial tree
+    InspectorTest.dumpIndexedDBTree();
+
+    // Create database
+    await InspectorTest.evaluateInPageAsync("createDatabase('" + databaseName + "')");
+    await new Promise(waitDatabaseAdded);
+    var idbDatabaseTreeElement = UI.panels.resources._sidebar.indexedDBListTreeElement._idbDatabaseTreeElements[0];
+    databaseId = idbDatabaseTreeElement._databaseId;
+    InspectorTest.addResult("Created database.");
+    InspectorTest.dumpIndexedDBTree();
+
+    // Load indexedDb database view
+    indexedDBModel.refreshDatabase(databaseId); // Initial database refresh.
+    await new Promise(waitDatabaseLoaded);      // Needed to initialize database view, otherwise
+    idbDatabaseTreeElement.onselect(false);     // IDBDatabaseTreeElement.database would be undefined.
+    var databaseView = idbDatabaseTreeElement._view;
+
+    // Create first objectstore
+    await InspectorTest.evaluateInPageAsync("createObjectStore('" + databaseName + "', '" + objectStoreName1 + "', '" + indexName + "', '" + keyPath + "')");
+    await new Promise(waitRefreshDatabase);
+    InspectorTest.addResult("Created first objectstore.");
+    InspectorTest.dumpIndexedDBTree();
+
+    // Create second objectstore
+    await InspectorTest.evaluateInPageAsync("createObjectStore('" + databaseName + "', '" + objectStoreName2 + "', '" + indexName + "', '" + keyPath + "')");
+    await new Promise(waitRefreshDatabase);
+    InspectorTest.addResult("Created second objectstore.");
+    InspectorTest.dumpIndexedDBTree();
+
+    // Load objectstore data views
+    for (var i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
+        var objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
+        objectStoreTreeElement.onselect(false);
+    }
+
+    // Add entries
+    await InspectorTest.evaluateInPageAsync("addIDBValue('" + databaseName + "', '" + objectStoreName1 + "', 'testKey', 'testValue')");
+    InspectorTest.addResult("Added " + objectStoreName1 + " entry.");
+    dumpObjectStores();
+
+    // Refresh database view
+    await new Promise(waitRefreshDatabase);
+    for (var i = 0; i < idbDatabaseTreeElement.childCount(); ++i) {
+        var objectStoreTreeElement = idbDatabaseTreeElement.childAt(i);
+        if (objectStoreTreeElement._objectStore.name === objectStoreName1) {
+            objectStoreTreeElement.onselect(false);
+            break;
+        }
+    }
+    await new Promise(waitUpdateDataView);      // Wait for objectstore data to load on page.
+    InspectorTest.addResult("Refreshed database.");
+    dumpObjectStores();
+
+    InspectorTest.completeTest();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests refreshing the database information and data views.</p>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/indexeddb-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/indexeddb-test.js
index 9547f4a..cf36c59a 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/indexeddb-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/indexeddb/indexeddb-test.js
@@ -23,8 +23,8 @@
                 InspectorTest.addResult("            (no indexes)");
                 continue;
             }
-            for (var j = 0; j < objectStoreTreeElement.childCount(); ++j) {
-                var indexTreeElement = objectStoreTreeElement.childAt(j);
+            for (var k = 0; k < objectStoreTreeElement.childCount(); ++k) {
+                var indexTreeElement = objectStoreTreeElement.childAt(k);
                 InspectorTest.addResult("            Index: " + indexTreeElement.title);
             }
         }
diff --git a/third_party/WebKit/LayoutTests/inspector/quick-open/command-menu-expected.txt b/third_party/WebKit/LayoutTests/inspector/quick-open/command-menu-expected.txt
index 812da80..234966bf 100644
--- a/third_party/WebKit/LayoutTests/inspector/quick-open/command-menu-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/quick-open/command-menu-expected.txt
@@ -10,6 +10,7 @@
 Has category: Mobile
 Has category: Network
 Has category: Panel
+Has category: Rendering
 Has category: Settings
 Has category: Sources
 
diff --git a/third_party/WebKit/LayoutTests/webaudio/tools/README.md b/third_party/WebKit/LayoutTests/webaudio/tools/README.md
index 12ed3699..0c3baa6 100644
--- a/third_party/WebKit/LayoutTests/webaudio/tools/README.md
+++ b/third_party/WebKit/LayoutTests/webaudio/tools/README.md
@@ -19,6 +19,8 @@
 
 ## Installation
 
+The `run-webkit-tests` script process all the files in the `webaudio/` directory, so files created by the node package installation will pollute the test result. To avoid this, copy `tools/` directory to another directory. Then run the following in that directory.
+
 ```
 cd ${WHERE_PACKAGE_JSON_IS} npm install
 ```
@@ -29,29 +31,22 @@
 ## Usage
 
 ```
-node layout-test-tidy ${TARGET_PATH}
+node layout-test-tidy [-[viR]] ${TARGET_PATH}
 ```
 
-When the target path is a directory, it scans all the files in the subdirectory.
-Specifying a single file is also valid operation.
+Available options:
+ * `-v` or `--verbose`: verbose output. Useful when collecting warnings and notes generated by the tool.
+ * `-i` or `--inplace`: in-place processing. Write directly into the original file.
+ * `-R` or `--recursive`: Expand the given path recursively.
+
+The result will be written to stdout, and only HTML and JS files with proper file extension (.html, .js) will be processed.
+
+### Examples
 
 ```
-node layout-test-tidy ${TARGET_PATH} -v > result.txt
-```
+# To collect the result with warning/notes to a file.
+node layout-test-tidy -v ${TARGET_PATH} > result.txt
 
-By default, the result will be written to stdout, so you can pipe the result to
-a file as shown above. `-v` or `--verbose` option is useful when collecting
-warnings and notes generated by the tool.
-
+# Apply in-place tidy to the entire layout test files for WebAudio.
+node layout-test-tidy -i -R ${CHROME_SRC}/third_party/WebKit/LayoutTests/webaudio
 ```
-node layout-test-tidy ${TARGET_PATH} -i
-```
-
-For in-place processing, use `-i` or `--inplace` switch.
-
-```
-node layout-test-tidy ${CHROME_SRC}/third_party/WebKit/LayoutTests/webaudio
-```
-
-  - If there is a file to be skipped, simply create `skip-tidy` file and add
-    absolute file paths of files to be skipped.
diff --git a/third_party/WebKit/LayoutTests/webaudio/tools/layout-test-tidy.js b/third_party/WebKit/LayoutTests/webaudio/tools/layout-test-tidy.js
index 4f3bbbd..f27a72b 100644
--- a/third_party/WebKit/LayoutTests/webaudio/tools/layout-test-tidy.js
+++ b/third_party/WebKit/LayoutTests/webaudio/tools/layout-test-tidy.js
@@ -21,8 +21,13 @@
  */
 const OPTIONS = {
 
-  HTMLTidy:
-      {'indent': 'yes', 'indent-spaces': '2', 'wrap': '80', 'tidy-mark': 'no'},
+  HTMLTidy: {
+    'indent': 'yes',
+    'indent-spaces': '2',
+    'wrap': '80',
+    'tidy-mark': 'no',
+    'doctype': 'html5'
+  },
 
   ClangFormat: ['-style=Chromium', '-assume-filename=a.js'],
 
@@ -511,44 +516,33 @@
 
   args = args.filter(arg => arg);
 
-  // A single target (a file or a directory) is allowed.
-  if (args.length !== 1) {
-    Util.logAndExit('main', 'Please specify a single target. (' + args + ')');
-  }
-
   // Populate options flags.
   let options = {
-    inPlace: optionArgs.includes('-i') || optionArgs.includes('--in-place'),
-    verbose: optionArgs.includes('-v') || optionArgs.includes('--verbose')
+    inplace: optionArgs.includes('-i') || optionArgs.includes('--inplace'),
+    recursive: optionArgs.includes('-R') || optionArgs.includes('--recursive'),
+    verbose: optionArgs.includes('-v') || optionArgs.includes('--verbose'),
   };
 
   // Collect target file(s) from the file system.
-  let targetPath = args[0];
   let files = [];
-  if (targetPath) {
+  args.forEach((targetPath) => {
     try {
       let stat = fs.lstatSync(targetPath);
       if (stat.isFile()) {
-        files.push(targetPath);
-      } else if (stat.isDirectory()) {
-        files = glob.sync(
-            targetPath + '/**/*.{html,js}', {ignore: ['**/node_modules/**/*']});
+        let fileType = path.extname(targetPath);
+        if (fileType === '.html' || fileType === '.js') {
+          files.push(targetPath);
+        }
+      } else if (
+          stat.isDirectory() && options.recursive &&
+          !targetPath.includes('node_modules')) {
+        files = files.concat(glob.sync(targetPath + '/**/*.{html,js}'));
       }
     } catch (error) {
       let errorMessage = 'Invalid file path. (' + targetPath + ')\n' +
           '  > ' + error.toString();
       Util.logAndExit('main', errorMessage);
     }
-  }
-
-  // Files to be skipped.
-  let filesToBeSkipped =
-      Util.loadFileToStringSync('skip-tidy').toString().split('\n');
-  filesToBeSkipped.forEach((fileSkipped) => {
-    let index = files.indexOf(fileSkipped);
-    if (index > -1) {
-      files.splice(index, 1);
-    }
   });
 
   if (files.length > 0) {
diff --git a/third_party/WebKit/Source/core/css/html.css b/third_party/WebKit/Source/core/css/html.css
index 57e64fe..854abfa4 100644
--- a/third_party/WebKit/Source/core/css/html.css
+++ b/third_party/WebKit/Source/core/css/html.css
@@ -805,6 +805,8 @@
 
 select:-internal-list-box hr {
     border-style: none;
+    -webkit-margin-before: 0.5em;
+    -webkit-margin-after: 0;
 }
 
 output {
diff --git a/third_party/WebKit/Source/core/editing/Editor.cpp b/third_party/WebKit/Source/core/editing/Editor.cpp
index 1fb0a01f..ca84712 100644
--- a/third_party/WebKit/Source/core/editing/Editor.cpp
+++ b/third_party/WebKit/Source/core/editing/Editor.cpp
@@ -1499,7 +1499,11 @@
   // Ranges for selections that are no longer valid
   bool selection_did_not_change_dom_position =
       new_selection == GetFrame().Selection().GetSelectionInDOMTree();
-  GetFrame().Selection().SetSelection(new_selection, options);
+  GetFrame().Selection().SetSelection(
+      SelectionInDOMTree::Builder(new_selection)
+          .SetIsHandleVisible(GetFrame().Selection().IsHandleVisible())
+          .Build(),
+      options);
 
   // Some editing operations change the selection visually without affecting its
   // position within the DOM. For example when you press return in the following
diff --git a/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp b/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
index 7744dfb4..d2f1961 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
+++ b/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
@@ -71,7 +71,7 @@
   const EphemeralRange& range =
       FirstEphemeralRangeOf(Selection().ComputeVisibleSelectionInDOMTree());
   EXPECT_EQ(Position(sample->nextSibling(), 0), range.StartPosition())
-      << "firstRagne() should return current selection value";
+      << "firstRange() should return current selection value";
   EXPECT_EQ(Position(sample->nextSibling(), 0), range.EndPosition());
 }
 
@@ -282,8 +282,8 @@
   EXPECT_FALSE(Selection().IsHandleVisible());
   Selection().SelectAll();
   EXPECT_FALSE(Selection().IsHandleVisible())
-      << "If handles weren't present before"
-         "selectAll. Then they shouldn't be present"
+      << "If handles weren't present before "
+         "selectAll. Then they shouldn't be present "
          "after it.";
 
   Selection().SetSelection(SelectionInDOMTree::Builder()
@@ -293,11 +293,40 @@
   EXPECT_TRUE(Selection().IsHandleVisible());
   Selection().SelectAll();
   EXPECT_TRUE(Selection().IsHandleVisible())
-      << "If handles were present before"
-         "selectAll. Then they should be present"
+      << "If handles were present before "
+         "selectAll. Then they should be present "
          "after it.";
 }
 
+TEST_F(FrameSelectionTest, BoldCommandPreservesHandle) {
+  SetBodyContent("<div id=sample>abc</div>");
+  Element* sample = GetDocument().getElementById("sample");
+  const Position end_of_text(sample->firstChild(), 3);
+  Selection().SetSelection(SelectionInDOMTree::Builder()
+                               .Collapse(end_of_text)
+                               .SetIsHandleVisible(false)
+                               .Build());
+  EXPECT_FALSE(Selection().IsHandleVisible());
+  Selection().SelectAll();
+  GetDocument().execCommand("bold", false, "", ASSERT_NO_EXCEPTION);
+  EXPECT_FALSE(Selection().IsHandleVisible())
+      << "If handles weren't present before "
+         "bold command. Then they shouldn't "
+         "be present after it.";
+
+  Selection().SetSelection(SelectionInDOMTree::Builder()
+                               .Collapse(end_of_text)
+                               .SetIsHandleVisible(true)
+                               .Build());
+  EXPECT_TRUE(Selection().IsHandleVisible());
+  Selection().SelectAll();
+  GetDocument().execCommand("bold", false, "", ASSERT_NO_EXCEPTION);
+  EXPECT_TRUE(Selection().IsHandleVisible())
+      << "If handles were present before "
+         "bold command. Then they should "
+         "be present after it.";
+}
+
 TEST_F(FrameSelectionTest, SelectionOnRangeHidesHandles) {
   Text* text = AppendTextNode("Hello, World!");
   GetDocument().View()->UpdateAllLifecyclePhases();
diff --git a/third_party/WebKit/Source/core/editing/SelectionController.cpp b/third_party/WebKit/Source/core/editing/SelectionController.cpp
index ec1a74b..5c0399a 100644
--- a/third_party/WebKit/Source/core/editing/SelectionController.cpp
+++ b/third_party/WebKit/Source/core/editing/SelectionController.cpp
@@ -215,10 +215,18 @@
       // Shift+Click deselects when selection was created right-to-left
       const PositionInFlatTree& start = selection.Start();
       const PositionInFlatTree& end = selection.end();
-      const int distance_to_start = TextDistance(start, pos);
-      const int distance_to_end = TextDistance(pos, end);
-      builder.SetBaseAndExtent(
-          distance_to_start <= distance_to_end ? end : start, pos);
+      if (pos < start) {
+        // |distance_to_start < distance_to_end|.
+        builder.SetBaseAndExtent(end, pos);
+      } else if (end < pos) {
+        // |distance_to_start > distance_to_end|.
+        builder.SetBaseAndExtent(start, pos);
+      } else {
+        const int distance_to_start = TextDistance(start, pos);
+        const int distance_to_end = TextDistance(pos, end);
+        builder.SetBaseAndExtent(
+            distance_to_start <= distance_to_end ? end : start, pos);
+      }
     }
 
     UpdateSelectionForMouseDownDispatchingSelectStart(
diff --git a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
index a5c9762..4f3e990c 100644
--- a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
+++ b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
@@ -194,6 +194,7 @@
     return;
   selection_ = SelectionInDOMTree::Builder()
                    .SetBaseAndExtent(new_base, new_extent)
+                   .SetIsHandleVisible(selection_.IsHandleVisible())
                    .Build();
   MarkCacheDirty();
 }
@@ -211,6 +212,7 @@
     return;
   selection_ = SelectionInDOMTree::Builder()
                    .SetBaseAndExtent(new_base, new_extent)
+                   .SetIsHandleVisible(selection_.IsHandleVisible())
                    .Build();
   MarkCacheDirty();
 }
diff --git a/third_party/WebKit/Source/core/editing/iterators/TextIterator.cpp b/third_party/WebKit/Source/core/editing/iterators/TextIterator.cpp
index 2368499c4..91854ed 100644
--- a/third_party/WebKit/Source/core/editing/iterators/TextIterator.cpp
+++ b/third_party/WebKit/Source/core/editing/iterators/TextIterator.cpp
@@ -182,6 +182,14 @@
 }
 
 template <typename Strategy>
+TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm(
+    const EphemeralRangeTemplate<Strategy>& range,
+    const TextIteratorBehavior& behavior)
+    : TextIteratorAlgorithm(range.StartPosition(),
+                            range.EndPosition(),
+                            behavior) {}
+
+template <typename Strategy>
 void TextIteratorAlgorithm<Strategy>::Initialize(Node* start_container,
                                                  int start_offset,
                                                  Node* end_container,
@@ -254,18 +262,8 @@
 }
 
 template <typename Strategy>
-void TextIteratorAlgorithm<Strategy>::Advance() {
-  if (should_stop_)
-    return;
-
-  if (node_)
-    DCHECK(!node_->GetDocument().NeedsLayoutTreeUpdate()) << node_;
-
-  text_state_.ResetRunInformation();
-
-  // TODO(xiaochengh): Wrap the following code into HandleRememberedProgress().
-
-  // handle remembered node that needed a newline after the text node's newline
+bool TextIteratorAlgorithm<Strategy>::HandleRememberedProgress() {
+  // Handle remembered node that needed a newline after the text node's newline
   if (needs_another_newline_) {
     // Emit the extra newline, and position it *inside* m_node, after m_node's
     // contents, in case it's a block, in the same way that we position the
@@ -277,10 +275,30 @@
     Node* base_node = last_child ? last_child : node_.Get();
     SpliceBuffer('\n', Strategy::Parent(*base_node), base_node, 1, 1);
     needs_another_newline_ = false;
-    return;
+    return true;
   }
 
-  if (text_node_handler_.HandleRemainingTextRuns())
+  // TODO(xiaochengh): When multiple text runs should be emitted from a replaced
+  // element or non-text node, we should handle it directly from here, instead
+  // of going into the main iteration of Advance() for multiple times. In this
+  // way, we can also remove the return values of HandleReplaceElement() and
+  // HandleNonTextNode(), and make the control flow cleaner.
+
+  // Try to emit more text runs if we are handling a text node.
+  return text_node_handler_.HandleRemainingTextRuns();
+}
+
+template <typename Strategy>
+void TextIteratorAlgorithm<Strategy>::Advance() {
+  if (should_stop_)
+    return;
+
+  if (node_)
+    DCHECK(!node_->GetDocument().NeedsLayoutTreeUpdate()) << node_;
+
+  text_state_.ResetRunInformation();
+
+  if (HandleRememberedProgress())
     return;
 
   while (node_ && (node_ != past_end_node_ || shadow_depth_ > 0)) {
diff --git a/third_party/WebKit/Source/core/editing/iterators/TextIterator.h b/third_party/WebKit/Source/core/editing/iterators/TextIterator.h
index fd420e0..9e72c11c 100644
--- a/third_party/WebKit/Source/core/editing/iterators/TextIterator.h
+++ b/third_party/WebKit/Source/core/editing/iterators/TextIterator.h
@@ -59,6 +59,12 @@
   TextIteratorAlgorithm(const PositionTemplate<Strategy>& start,
                         const PositionTemplate<Strategy>& end,
                         const TextIteratorBehavior& = TextIteratorBehavior());
+
+  // Same behavior as previous constructor but takes an EphemeralRange instead
+  // of two Positions
+  TextIteratorAlgorithm(const EphemeralRangeTemplate<Strategy>&,
+                        const TextIteratorBehavior& = TextIteratorBehavior());
+
   ~TextIteratorAlgorithm();
 
   bool AtEnd() const { return !text_state_.PositionNode() || should_stop_; }
@@ -131,6 +137,9 @@
   bool ShouldEmitSpaceBeforeAndAfterNode(Node*);
   void RepresentNodeOffsetZero();
 
+  // Returns true if text is emitted from the remembered progress (if any).
+  bool HandleRememberedProgress();
+
   // Return true if the iteration progress should advance to |kHandledNode|
   // after calling a |HandleXXX| function.
   // TODO(xiaochengh): The meaning of the return values is unclear, and they do
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
index 0171ea10..6d76290 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
@@ -136,7 +136,6 @@
                  unsigned start_offset,
                  unsigned end_offset,
                  const String& description);
-  DocumentMarker(unsigned start_offset, unsigned end_offset, MatchStatus);
   DocumentMarker(unsigned start_offset,
                  unsigned end_offset,
                  Color underline_color,
@@ -176,6 +175,9 @@
 
   DECLARE_TRACE();
 
+ protected:
+  DocumentMarker(unsigned start_offset, unsigned end_offset, MatchStatus);
+
  private:
   MarkerType type_;
   unsigned start_offset_;
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
index c0e1f718..83cfe300 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
@@ -159,9 +159,9 @@
   for (TextIterator marked_text(range.StartPosition(), range.EndPosition());
        !marked_text.AtEnd(); marked_text.Advance()) {
     AddMarker(marked_text.CurrentContainer(),
-              new DocumentMarker(marked_text.StartOffsetInCurrentContainer(),
-                                 marked_text.EndOffsetInCurrentContainer(),
-                                 match_status));
+              new TextMatchMarker(marked_text.StartOffsetInCurrentContainer(),
+                                  marked_text.EndOffsetInCurrentContainer(),
+                                  match_status));
   }
   // Don't invalidate tickmarks here. TextFinder invalidates tickmarks using a
   // throttling algorithm. crbug.com/6819.
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerListEditorTest.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerListEditorTest.cpp
index 87a5818..a1976a15 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerListEditorTest.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerListEditorTest.cpp
@@ -4,6 +4,7 @@
 
 #include "core/editing/markers/DocumentMarkerListEditor.h"
 
+#include "core/editing/markers/TextMatchMarker.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace blink {
@@ -11,8 +12,8 @@
 class DocumentMarkerListEditorTest : public ::testing::Test {
  protected:
   DocumentMarker* CreateMarker(unsigned startOffset, unsigned endOffset) {
-    return new DocumentMarker(startOffset, endOffset,
-                              DocumentMarker::MatchStatus::kInactive);
+    return new TextMatchMarker(startOffset, endOffset,
+                               DocumentMarker::MatchStatus::kInactive);
   }
 };
 
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerTest.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerTest.cpp
index aa9d87f..1291ad2 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerTest.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerTest.cpp
@@ -4,6 +4,7 @@
 
 #include "core/editing/markers/DocumentMarker.h"
 
+#include "core/editing/markers/TextMatchMarker.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace blink {
@@ -13,8 +14,8 @@
 class DocumentMarkerTest : public ::testing::Test {
  protected:
   DocumentMarker* CreateMarker(unsigned startOffset, unsigned endOffset) {
-    return new DocumentMarker(startOffset, endOffset,
-                              DocumentMarker::MatchStatus::kInactive);
+    return new TextMatchMarker(startOffset, endOffset,
+                               DocumentMarker::MatchStatus::kInactive);
   }
 };
 
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
index 74c69dd4..c6c87b1 100644
--- a/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
@@ -37,9 +37,11 @@
   enum class State { kInvalid, kValidNull, kValidNotNull };
 
  public:
-  static TextMatchMarker* Create(const DocumentMarker& marker) {
-    return new TextMatchMarker(marker);
-  }
+  TextMatchMarker(unsigned start_offset,
+                  unsigned end_offset,
+                  MatchStatus status)
+      : DocumentMarker(start_offset, end_offset, status),
+        state_(State::kInvalid) {}
 
   bool IsRendered() const { return state_ == State::kValidNotNull; }
   bool Contains(const LayoutPoint& point) const {
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
index 7cd3231f..18a5d75a 100644
--- a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImpl.cpp
@@ -22,8 +22,8 @@
 }
 
 void TextMatchMarkerListImpl::Add(DocumentMarker* marker) {
-  DocumentMarkerListEditor::AddMarkerWithoutMergingOverlapping(
-      &markers_, TextMatchMarker::Create(*marker));
+  DocumentMarkerListEditor::AddMarkerWithoutMergingOverlapping(&markers_,
+                                                               marker);
 }
 
 void TextMatchMarkerListImpl::Clear() {
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImplTest.cpp b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImplTest.cpp
index 2731906a..ad26bac 100644
--- a/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImplTest.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarkerListImplTest.cpp
@@ -5,6 +5,7 @@
 #include "core/editing/markers/TextMatchMarkerListImpl.h"
 
 #include "core/editing/EditingTestBase.h"
+#include "core/editing/markers/TextMatchMarker.h"
 
 namespace blink {
 
@@ -13,8 +14,8 @@
   TextMatchMarkerListImplTest() : marker_list_(new TextMatchMarkerListImpl()) {}
 
   DocumentMarker* CreateMarker(unsigned start_offset, unsigned end_offset) {
-    return new DocumentMarker(start_offset, end_offset,
-                              DocumentMarker::MatchStatus::kInactive);
+    return new TextMatchMarker(start_offset, end_offset,
+                               DocumentMarker::MatchStatus::kInactive);
   }
 
   Persistent<TextMatchMarkerListImpl> marker_list_;
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
index 051fd2e..a72600ab 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
@@ -600,7 +600,6 @@
   // and should be rewritten.
   // Expand the range to encompass entire paragraphs, since text checking needs
   // that much context.
-  int selection_offset = 0;
   int ambiguous_boundary_offset = -1;
 
   if (GetFrame().Selection().ComputeVisibleSelectionInDOMTree().IsCaret()) {
@@ -609,7 +608,11 @@
     // Attempt to save the caret position so we can restore it later if needed
     const Position& caret_position =
         GetFrame().Selection().ComputeVisibleSelectionInDOMTree().end();
-    selection_offset = paragraph.OffsetTo(caret_position);
+    const Position& paragraph_start = checking_range.StartPosition();
+    const int selection_offset =
+        paragraph_start < caret_position
+            ? TextIterator::RangeLength(paragraph_start, caret_position)
+            : 0;
     if (selection_offset > 0 &&
         static_cast<unsigned>(selection_offset) <=
             paragraph.GetText().length() &&
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.cpp b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.cpp
index 6f8f18d..b9aeb06c 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.cpp
@@ -99,11 +99,6 @@
                                     character_count);
 }
 
-int TextCheckingParagraph::OffsetTo(const Position& position) const {
-  DCHECK(checking_range_.IsNotNull());
-  return TextIterator::RangeLength(OffsetAsRange().StartPosition(), position);
-}
-
 bool TextCheckingParagraph::IsEmpty() const {
   // Both predicates should have same result, but we check both just to be sure.
   // We need to investigate to remove this redundancy.
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.h b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.h
index 24d9cae0..dc0670e 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.h
+++ b/third_party/WebKit/Source/core/editing/spellcheck/TextCheckingParagraph.h
@@ -41,7 +41,6 @@
 
   int RangeLength() const;
   EphemeralRange Subrange(int character_offset, int character_count) const;
-  int OffsetTo(const Position&) const;
   void ExpandRangeToNextEnd();
 
   const String& GetText() const;
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.cpp b/third_party/WebKit/Source/core/frame/LocalFrame.cpp
index 3eefa4f..7cafbe02 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.cpp
@@ -1201,4 +1201,13 @@
   return nullptr;
 }
 
+void LocalFrame::SetViewportIntersectionFromParent(
+    const IntRect& viewport_intersection) {
+  if (remote_viewport_intersection_ != viewport_intersection) {
+    remote_viewport_intersection_ = viewport_intersection;
+    if (View())
+      View()->ScheduleAnimation();
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.h b/third_party/WebKit/Source/core/frame/LocalFrame.h
index d29aea93..f49e7d09 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.h
@@ -255,6 +255,12 @@
   // focused element or passed node into explicit methods.
   WebPluginContainerBase* GetWebPluginContainerBase(Node* = nullptr) const;
 
+  // Called on a view for a LocalFrame with a RemoteFrame parent. This makes
+  // viewport intersection available that accounts for remote ancestor frames
+  // and their respective scroll positions, clips, etc.
+  void SetViewportIntersectionFromParent(const IntRect&);
+  IntRect RemoteViewportIntersection() { return remote_viewport_intersection_; }
+
  private:
   friend class FrameNavigationDisabler;
 
@@ -305,6 +311,8 @@
 
   InterfaceProvider* const interface_provider_;
   InterfaceRegistry* const interface_registry_;
+
+  IntRect remote_viewport_intersection_;
 };
 
 inline FrameLoader& LocalFrame::Loader() const {
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
index eb769944..aaa69d2 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
@@ -1060,32 +1060,38 @@
   // functions so that a single human could understand what layout() is actually
   // doing.
 
+  // FIXME: ForceLayoutParentViewIfNeeded can cause this document's lifecycle
+  // to change, which should not happen.
   ForceLayoutParentViewIfNeeded();
-  // TODO(szager): Remove this after checking crash reports.
-  CHECK(IsInPerformLayout());
+  CHECK(IsInPerformLayout() ||
+        Lifecycle().GetState() >= DocumentLifecycle::kLayoutClean);
 
-  if (in_subtree_layout) {
-    if (analyzer_) {
-      analyzer_->Increment(LayoutAnalyzer::kPerformLayoutRootLayoutObjects,
-                           layout_subtree_root_list_.size());
-    }
-    for (auto& root : layout_subtree_root_list_.Ordered()) {
-      if (!root->NeedsLayout())
-        continue;
-      LayoutFromRootObject(*root);
+  if (IsInPerformLayout()) {
+    if (in_subtree_layout) {
+      if (analyzer_) {
+        analyzer_->Increment(LayoutAnalyzer::kPerformLayoutRootLayoutObjects,
+                             layout_subtree_root_list_.size());
+      }
+      for (auto& root : layout_subtree_root_list_.Ordered()) {
+        if (!root->NeedsLayout())
+          continue;
+        LayoutFromRootObject(*root);
 
-      // We need to ensure that we mark up all layoutObjects up to the
-      // LayoutView for paint invalidation. This simplifies our code as we just
-      // always do a full tree walk.
-      if (LayoutItem container = LayoutItem(root->Container()))
-        container.SetMayNeedPaintInvalidation();
+        // We need to ensure that we mark up all layoutObjects up to the
+        // LayoutView for paint invalidation. This simplifies our code as we
+        // just always do a full tree walk.
+        if (LayoutItem container = LayoutItem(root->Container()))
+          container.SetMayNeedPaintInvalidation();
+      }
+      layout_subtree_root_list_.Clear();
+    } else {
+      if (HasOrthogonalWritingModeRoots() &&
+          !RuntimeEnabledFeatures::layoutNGEnabled())
+        LayoutOrthogonalWritingModeRoots();
+      GetLayoutView()->UpdateLayout();
     }
-    layout_subtree_root_list_.Clear();
   } else {
-    if (HasOrthogonalWritingModeRoots() &&
-        !RuntimeEnabledFeatures::layoutNGEnabled())
-      LayoutOrthogonalWritingModeRoots();
-    GetLayoutView()->UpdateLayout();
+    DCHECK(!NeedsLayout());
   }
 
   frame_->GetDocument()->Fetcher()->UpdateAllImageResourcePriorities();
@@ -5319,16 +5325,8 @@
   return result;
 }
 
-void LocalFrameView::SetViewportIntersectionFromParent(
-    const IntRect& viewport_intersection) {
-  if (remote_viewport_intersection_ != viewport_intersection) {
-    remote_viewport_intersection_ = viewport_intersection;
-    ScheduleAnimation();
-  }
-}
-
 IntRect LocalFrameView::RemoteViewportIntersection() {
-  IntRect intersection(remote_viewport_intersection_);
+  IntRect intersection(GetFrame().RemoteViewportIntersection());
   intersection.Move(ScrollOffsetInt());
   return intersection;
 }
diff --git a/third_party/WebKit/Source/core/inspector/BUILD.gn b/third_party/WebKit/Source/core/inspector/BUILD.gn
index 66150a0..1d9705b 100644
--- a/third_party/WebKit/Source/core/inspector/BUILD.gn
+++ b/third_party/WebKit/Source/core/inspector/BUILD.gn
@@ -20,6 +20,8 @@
     "DOMEditor.h",
     "DOMPatchSupport.cpp",
     "DOMPatchSupport.h",
+    "DevToolsEmulator.cpp",
+    "DevToolsEmulator.h",
     "DevToolsHost.cpp",
     "DevToolsHost.h",
     "IdentifiersFactory.cpp",
diff --git a/third_party/WebKit/Source/web/DevToolsEmulator.cpp b/third_party/WebKit/Source/core/inspector/DevToolsEmulator.cpp
similarity index 72%
rename from third_party/WebKit/Source/web/DevToolsEmulator.cpp
rename to third_party/WebKit/Source/core/inspector/DevToolsEmulator.cpp
index c3688af..95ef80d 100644
--- a/third_party/WebKit/Source/web/DevToolsEmulator.cpp
+++ b/third_party/WebKit/Source/core/inspector/DevToolsEmulator.cpp
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "web/DevToolsEmulator.h"
+#include "core/inspector/DevToolsEmulator.h"
 
 #include "core/events/WebInputEventConversion.h"
 #include "core/exported/WebViewBase.h"
@@ -22,7 +22,7 @@
 #include "platform/loader/fetch/MemoryCache.h"
 #include "platform/wtf/PtrUtil.h"
 #include "public/platform/WebLayerTreeView.h"
-#include "web/WebSettingsImpl.h"
+#include "public/web/WebSettings.h"
 
 namespace {
 
@@ -59,8 +59,8 @@
 
 namespace blink {
 
-DevToolsEmulator::DevToolsEmulator(WebViewBase* web_view_impl)
-    : web_view_impl_(web_view_impl),
+DevToolsEmulator::DevToolsEmulator(WebViewBase* web_view)
+    : web_view_(web_view),
       device_metrics_enabled_(false),
       emulate_mobile_enabled_(false),
       is_overlay_scrollbars_enabled_(false),
@@ -69,27 +69,27 @@
       original_default_minimum_page_scale_factor_(0),
       original_default_maximum_page_scale_factor_(0),
       embedder_text_autosizing_enabled_(
-          web_view_impl->GetPage()->GetSettings().TextAutosizingEnabled()),
+          web_view->GetPage()->GetSettings().TextAutosizingEnabled()),
       embedder_device_scale_adjustment_(
-          web_view_impl->GetPage()->GetSettings().GetDeviceScaleAdjustment()),
+          web_view->GetPage()->GetSettings().GetDeviceScaleAdjustment()),
       embedder_prefer_compositing_to_lcd_text_enabled_(
-          web_view_impl->GetPage()
+          web_view->GetPage()
               ->GetSettings()
               .GetPreferCompositingToLCDTextEnabled()),
       embedder_viewport_style_(
-          web_view_impl->GetPage()->GetSettings().GetViewportStyle()),
+          web_view->GetPage()->GetSettings().GetViewportStyle()),
       embedder_plugins_enabled_(
-          web_view_impl->GetPage()->GetSettings().GetPluginsEnabled()),
+          web_view->GetPage()->GetSettings().GetPluginsEnabled()),
       embedder_available_pointer_types_(
-          web_view_impl->GetPage()->GetSettings().GetAvailablePointerTypes()),
+          web_view->GetPage()->GetSettings().GetAvailablePointerTypes()),
       embedder_primary_pointer_type_(
-          web_view_impl->GetPage()->GetSettings().GetPrimaryPointerType()),
+          web_view->GetPage()->GetSettings().GetPrimaryPointerType()),
       embedder_available_hover_types_(
-          web_view_impl->GetPage()->GetSettings().GetAvailableHoverTypes()),
+          web_view->GetPage()->GetSettings().GetAvailableHoverTypes()),
       embedder_primary_hover_type_(
-          web_view_impl->GetPage()->GetSettings().GetPrimaryHoverType()),
+          web_view->GetPage()->GetSettings().GetPrimaryHoverType()),
       embedder_main_frame_resizes_are_orientation_changes_(
-          web_view_impl->GetPage()
+          web_view->GetPage()
               ->GetSettings()
               .GetMainFrameResizesAreOrientationChanges()),
       touch_event_emulation_enabled_(false),
@@ -98,7 +98,7 @@
       original_device_supports_touch_(false),
       original_max_touch_points_(0),
       embedder_script_enabled_(
-          web_view_impl->GetPage()->GetSettings().GetScriptEnabled()),
+          web_view->GetPage()->GetSettings().GetScriptEnabled()),
       script_execution_disabled_(false) {}
 
 DevToolsEmulator::~DevToolsEmulator() {}
@@ -114,26 +114,27 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetTextAutosizingEnabled(enabled);
+    web_view_->GetPage()->GetSettings().SetTextAutosizingEnabled(enabled);
 }
 
 void DevToolsEmulator::SetDeviceScaleAdjustment(float device_scale_adjustment) {
   embedder_device_scale_adjustment_ = device_scale_adjustment;
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
-  if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
+  if (!emulate_mobile_enabled) {
+    web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
         device_scale_adjustment);
+  }
 }
 
 void DevToolsEmulator::SetPreferCompositingToLCDTextEnabled(bool enabled) {
   embedder_prefer_compositing_to_lcd_text_enabled_ = enabled;
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
-  if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()
-        ->GetSettings()
-        .SetPreferCompositingToLCDTextEnabled(enabled);
+  if (!emulate_mobile_enabled) {
+    web_view_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+        enabled);
+  }
 }
 
 void DevToolsEmulator::SetViewportStyle(WebViewportStyle style) {
@@ -141,7 +142,7 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetViewportStyle(style);
+    web_view_->GetPage()->GetSettings().SetViewportStyle(style);
 }
 
 void DevToolsEmulator::SetPluginsEnabled(bool enabled) {
@@ -149,13 +150,13 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetPluginsEnabled(enabled);
+    web_view_->GetPage()->GetSettings().SetPluginsEnabled(enabled);
 }
 
 void DevToolsEmulator::SetScriptEnabled(bool enabled) {
   embedder_script_enabled_ = enabled;
   if (!script_execution_disabled_)
-    web_view_impl_->GetPage()->GetSettings().SetScriptEnabled(enabled);
+    web_view_->GetPage()->GetSettings().SetScriptEnabled(enabled);
 }
 
 void DevToolsEmulator::SetDoubleTapToZoomEnabled(bool enabled) {
@@ -170,10 +171,11 @@
   embedder_main_frame_resizes_are_orientation_changes_ = value;
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
-  if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()
+  if (!emulate_mobile_enabled) {
+    web_view_->GetPage()
         ->GetSettings()
         .SetMainFrameResizesAreOrientationChanges(value);
+  }
 }
 
 void DevToolsEmulator::SetAvailablePointerTypes(int types) {
@@ -181,7 +183,7 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetAvailablePointerTypes(types);
+    web_view_->GetPage()->GetSettings().SetAvailablePointerTypes(types);
 }
 
 void DevToolsEmulator::SetPrimaryPointerType(PointerType pointer_type) {
@@ -189,8 +191,7 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetPrimaryPointerType(
-        pointer_type);
+    web_view_->GetPage()->GetSettings().SetPrimaryPointerType(pointer_type);
 }
 
 void DevToolsEmulator::SetAvailableHoverTypes(int types) {
@@ -198,7 +199,7 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetAvailableHoverTypes(types);
+    web_view_->GetPage()->GetSettings().SetAvailableHoverTypes(types);
 }
 
 void DevToolsEmulator::SetPrimaryHoverType(HoverType hover_type) {
@@ -206,7 +207,7 @@
   bool emulate_mobile_enabled =
       device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
-    web_view_impl_->GetPage()->GetSettings().SetPrimaryHoverType(hover_type);
+    web_view_->GetPage()->GetSettings().SetPrimaryHoverType(hover_type);
 }
 
 void DevToolsEmulator::EnableDeviceEmulation(
@@ -226,7 +227,7 @@
   emulation_params_ = params;
   device_metrics_enabled_ = true;
 
-  web_view_impl_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
+  web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
       calculateDeviceScaleAdjustment(params.view_size.width,
                                      params.view_size.height,
                                      params.device_scale_factor));
@@ -236,14 +237,13 @@
   else
     DisableMobileEmulation();
 
-  web_view_impl_->SetCompositorDeviceScaleFactorOverride(
-      params.device_scale_factor);
+  web_view_->SetCompositorDeviceScaleFactorOverride(params.device_scale_factor);
   UpdateRootLayerTransform();
   // TODO(dgozman): mainFrameImpl() is null when it's remote. Figure out how
   // we end up with enabling emulation in this case.
-  if (web_view_impl_->MainFrameImpl()) {
+  if (web_view_->MainFrameImpl()) {
     if (Document* document =
-            web_view_impl_->MainFrameImpl()->GetFrame()->GetDocument())
+            web_view_->MainFrameImpl()->GetFrame()->GetDocument())
       document->MediaQueryAffectingValueChanged();
   }
 }
@@ -254,16 +254,16 @@
 
   GetMemoryCache()->EvictResources();
   device_metrics_enabled_ = false;
-  web_view_impl_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
+  web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
       embedder_device_scale_adjustment_);
   DisableMobileEmulation();
-  web_view_impl_->SetCompositorDeviceScaleFactorOverride(0.f);
-  web_view_impl_->SetPageScaleFactor(1.f);
+  web_view_->SetCompositorDeviceScaleFactorOverride(0.f);
+  web_view_->SetPageScaleFactor(1.f);
   UpdateRootLayerTransform();
   // mainFrameImpl() could be null during cleanup or remote <-> local swap.
-  if (web_view_impl_->MainFrameImpl()) {
+  if (web_view_->MainFrameImpl()) {
     if (Document* document =
-            web_view_impl_->MainFrameImpl()->GetFrame()->GetDocument())
+            web_view_->MainFrameImpl()->GetFrame()->GetDocument())
       document->MediaQueryAffectingValueChanged();
   }
 }
@@ -282,37 +282,34 @@
       RuntimeEnabledFeatures::mobileLayoutThemeEnabled();
   RuntimeEnabledFeatures::setMobileLayoutThemeEnabled(true);
   ComputedStyle::InvalidateInitialStyle();
-  web_view_impl_->GetPage()->GetSettings().SetViewportStyle(
+  web_view_->GetPage()->GetSettings().SetViewportStyle(
       WebViewportStyle::kMobile);
-  web_view_impl_->GetPage()->GetSettings().SetViewportEnabled(true);
-  web_view_impl_->GetPage()->GetSettings().SetViewportMetaEnabled(true);
-  web_view_impl_->GetPage()->GetVisualViewport().InitializeScrollbars();
-  web_view_impl_->GetSettings()->SetShrinksViewportContentToFit(true);
-  web_view_impl_->GetPage()->GetSettings().SetTextAutosizingEnabled(true);
-  web_view_impl_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+  web_view_->GetPage()->GetSettings().SetViewportEnabled(true);
+  web_view_->GetPage()->GetSettings().SetViewportMetaEnabled(true);
+  web_view_->GetPage()->GetVisualViewport().InitializeScrollbars();
+  web_view_->GetSettings()->SetShrinksViewportContentToFit(true);
+  web_view_->GetPage()->GetSettings().SetTextAutosizingEnabled(true);
+  web_view_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
       true);
-  web_view_impl_->GetPage()->GetSettings().SetPluginsEnabled(false);
-  web_view_impl_->GetPage()->GetSettings().SetAvailablePointerTypes(
+  web_view_->GetPage()->GetSettings().SetPluginsEnabled(false);
+  web_view_->GetPage()->GetSettings().SetAvailablePointerTypes(
       kPointerTypeCoarse);
-  web_view_impl_->GetPage()->GetSettings().SetPrimaryPointerType(
-      kPointerTypeCoarse);
-  web_view_impl_->GetPage()->GetSettings().SetAvailableHoverTypes(
-      kHoverTypeNone);
-  web_view_impl_->GetPage()->GetSettings().SetPrimaryHoverType(kHoverTypeNone);
-  web_view_impl_->GetPage()
-      ->GetSettings()
-      .SetMainFrameResizesAreOrientationChanges(true);
-  web_view_impl_->SetZoomFactorOverride(1);
+  web_view_->GetPage()->GetSettings().SetPrimaryPointerType(kPointerTypeCoarse);
+  web_view_->GetPage()->GetSettings().SetAvailableHoverTypes(kHoverTypeNone);
+  web_view_->GetPage()->GetSettings().SetPrimaryHoverType(kHoverTypeNone);
+  web_view_->GetPage()->GetSettings().SetMainFrameResizesAreOrientationChanges(
+      true);
+  web_view_->SetZoomFactorOverride(1);
 
   original_default_minimum_page_scale_factor_ =
-      web_view_impl_->DefaultMinimumPageScaleFactor();
+      web_view_->DefaultMinimumPageScaleFactor();
   original_default_maximum_page_scale_factor_ =
-      web_view_impl_->DefaultMaximumPageScaleFactor();
-  web_view_impl_->SetDefaultPageScaleLimits(0.25f, 5);
+      web_view_->DefaultMaximumPageScaleFactor();
+  web_view_->SetDefaultPageScaleLimits(0.25f, 5);
   // TODO(dgozman): mainFrameImpl() is null when it's remote. Figure out how
   // we end up with enabling emulation in this case.
-  if (web_view_impl_->MainFrameImpl())
-    web_view_impl_->MainFrameImpl()->GetFrameView()->UpdateLayout();
+  if (web_view_->MainFrameImpl())
+    web_view_->MainFrameImpl()->GetFrameView()->UpdateLayout();
 }
 
 void DevToolsEmulator::DisableMobileEmulation() {
@@ -325,50 +322,48 @@
   RuntimeEnabledFeatures::setMobileLayoutThemeEnabled(
       is_mobile_layout_theme_enabled_);
   ComputedStyle::InvalidateInitialStyle();
-  web_view_impl_->GetPage()->GetSettings().SetViewportEnabled(false);
-  web_view_impl_->GetPage()->GetSettings().SetViewportMetaEnabled(false);
-  web_view_impl_->GetPage()->GetVisualViewport().InitializeScrollbars();
-  web_view_impl_->GetSettings()->SetShrinksViewportContentToFit(false);
-  web_view_impl_->GetPage()->GetSettings().SetTextAutosizingEnabled(
+  web_view_->GetPage()->GetSettings().SetViewportEnabled(false);
+  web_view_->GetPage()->GetSettings().SetViewportMetaEnabled(false);
+  web_view_->GetPage()->GetVisualViewport().InitializeScrollbars();
+  web_view_->GetSettings()->SetShrinksViewportContentToFit(false);
+  web_view_->GetPage()->GetSettings().SetTextAutosizingEnabled(
       embedder_text_autosizing_enabled_);
-  web_view_impl_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+  web_view_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
       embedder_prefer_compositing_to_lcd_text_enabled_);
-  web_view_impl_->GetPage()->GetSettings().SetViewportStyle(
+  web_view_->GetPage()->GetSettings().SetViewportStyle(
       embedder_viewport_style_);
-  web_view_impl_->GetPage()->GetSettings().SetPluginsEnabled(
+  web_view_->GetPage()->GetSettings().SetPluginsEnabled(
       embedder_plugins_enabled_);
-  web_view_impl_->GetPage()->GetSettings().SetAvailablePointerTypes(
+  web_view_->GetPage()->GetSettings().SetAvailablePointerTypes(
       embedder_available_pointer_types_);
-  web_view_impl_->GetPage()->GetSettings().SetPrimaryPointerType(
+  web_view_->GetPage()->GetSettings().SetPrimaryPointerType(
       embedder_primary_pointer_type_);
-  web_view_impl_->GetPage()->GetSettings().SetAvailableHoverTypes(
+  web_view_->GetPage()->GetSettings().SetAvailableHoverTypes(
       embedder_available_hover_types_);
-  web_view_impl_->GetPage()->GetSettings().SetPrimaryHoverType(
+  web_view_->GetPage()->GetSettings().SetPrimaryHoverType(
       embedder_primary_hover_type_);
-  web_view_impl_->GetPage()
-      ->GetSettings()
-      .SetMainFrameResizesAreOrientationChanges(
-          embedder_main_frame_resizes_are_orientation_changes_);
-  web_view_impl_->SetZoomFactorOverride(0);
+  web_view_->GetPage()->GetSettings().SetMainFrameResizesAreOrientationChanges(
+      embedder_main_frame_resizes_are_orientation_changes_);
+  web_view_->SetZoomFactorOverride(0);
   emulate_mobile_enabled_ = false;
-  web_view_impl_->SetDefaultPageScaleLimits(
+  web_view_->SetDefaultPageScaleLimits(
       original_default_minimum_page_scale_factor_,
       original_default_maximum_page_scale_factor_);
   // mainFrameImpl() could be null during cleanup or remote <-> local swap.
-  if (web_view_impl_->MainFrameImpl())
-    web_view_impl_->MainFrameImpl()->GetFrameView()->UpdateLayout();
+  if (web_view_->MainFrameImpl())
+    web_view_->MainFrameImpl()->GetFrameView()->UpdateLayout();
 }
 
 float DevToolsEmulator::CompositorDeviceScaleFactor() const {
   if (device_metrics_enabled_)
     return emulation_params_.device_scale_factor;
-  return web_view_impl_->GetPage()->DeviceScaleFactorDeprecated();
+  return web_view_->GetPage()->DeviceScaleFactorDeprecated();
 }
 
 void DevToolsEmulator::ForceViewport(const WebFloatPoint& position,
                                      float scale) {
   GraphicsLayer* container_layer =
-      web_view_impl_->GetPage()->GetVisualViewport().ContainerLayer();
+      web_view_->GetPage()->GetVisualViewport().ContainerLayer();
   if (!viewport_override_) {
     viewport_override_ = ViewportOverride();
 
@@ -397,7 +392,7 @@
   viewport_override_ = WTF::nullopt;
 
   GraphicsLayer* container_layer =
-      web_view_impl_->GetPage()->GetVisualViewport().ContainerLayer();
+      web_view_->GetPage()->GetVisualViewport().ContainerLayer();
   if (container_layer)
     container_layer->SetMasksToBounds(original_masking);
   UpdateRootLayerTransform();
@@ -417,13 +412,15 @@
     // Scale first, so that translation is unaffected.
     transform->Translate(offset.width, offset.height);
     transform->Scale(emulation_params_.scale);
-    if (web_view_impl_->MainFrameImpl())
-      web_view_impl_->MainFrameImpl()->SetInputEventsTransformForEmulation(
+    if (web_view_->MainFrameImpl()) {
+      web_view_->MainFrameImpl()->SetInputEventsTransformForEmulation(
           offset, emulation_params_.scale);
+    }
   } else {
-    if (web_view_impl_->MainFrameImpl())
-      web_view_impl_->MainFrameImpl()->SetInputEventsTransformForEmulation(
+    if (web_view_->MainFrameImpl()) {
+      web_view_->MainFrameImpl()->SetInputEventsTransformForEmulation(
           WebSize(0, 0), 1.0);
+    }
   }
 }
 
@@ -436,8 +433,8 @@
   transform->Scale(viewport_override_->scale);
 
   // Translate while taking into account current scroll offset.
-  WebSize scroll_offset = web_view_impl_->MainFrame()->GetScrollOffset();
-  WebFloatPoint visual_offset = web_view_impl_->VisualViewportOffset();
+  WebSize scroll_offset = web_view_->MainFrame()->GetScrollOffset();
+  WebFloatPoint visual_offset = web_view_->VisualViewportOffset();
   float scroll_x = scroll_offset.width + visual_offset.x;
   float scroll_y = scroll_offset.height + visual_offset.y;
   transform->Translate(-viewport_override_->position.x + scroll_x,
@@ -445,7 +442,7 @@
 
   // First, reverse page scale, so we don't have to take it into account for
   // calculation of the translation.
-  transform->Scale(1. / web_view_impl_->PageScaleFactor());
+  transform->Scale(1. / web_view_->PageScaleFactor());
 }
 
 void DevToolsEmulator::UpdateRootLayerTransform() {
@@ -455,13 +452,13 @@
   // viewport override.
   ApplyViewportOverride(&transform);
   ApplyDeviceEmulationTransform(&transform);
-  web_view_impl_->SetDeviceEmulationTransform(transform);
+  web_view_->SetDeviceEmulationTransform(transform);
 }
 
 WTF::Optional<IntRect> DevToolsEmulator::VisibleContentRectForPainting() const {
   if (!viewport_override_)
     return WTF::nullopt;
-  FloatSize viewport_size(web_view_impl_->LayerTreeView()->GetViewportSize());
+  FloatSize viewport_size(web_view_->LayerTreeView()->GetViewportSize());
   viewport_size.Scale(1. / CompositorDeviceScaleFactor());
   viewport_size.Scale(1. / viewport_override_->scale);
   return EnclosingIntRect(
@@ -476,23 +473,23 @@
     original_touch_event_feature_detection_enabled_ =
         RuntimeEnabledFeatures::touchEventFeatureDetectionEnabled();
     original_device_supports_touch_ =
-        web_view_impl_->GetPage()->GetSettings().GetDeviceSupportsTouch();
+        web_view_->GetPage()->GetSettings().GetDeviceSupportsTouch();
     original_max_touch_points_ =
-        web_view_impl_->GetPage()->GetSettings().GetMaxTouchPoints();
+        web_view_->GetPage()->GetSettings().GetMaxTouchPoints();
   }
   RuntimeEnabledFeatures::setTouchEventFeatureDetectionEnabled(
       enabled ? true : original_touch_event_feature_detection_enabled_);
   if (!original_device_supports_touch_) {
-    if (enabled && web_view_impl_->MainFrameImpl()) {
-      web_view_impl_->MainFrameImpl()
+    if (enabled && web_view_->MainFrameImpl()) {
+      web_view_->MainFrameImpl()
           ->GetFrame()
           ->GetEventHandler()
           .ClearMouseEventManager();
     }
-    web_view_impl_->GetPage()->GetSettings().SetDeviceSupportsTouch(
+    web_view_->GetPage()->GetSettings().SetDeviceSupportsTouch(
         enabled ? true : original_device_supports_touch_);
     // Currently emulation does not provide multiple touch points.
-    web_view_impl_->GetPage()->GetSettings().SetMaxTouchPoints(
+    web_view_->GetPage()->GetSettings().SetMaxTouchPoints(
         enabled ? 1 : original_max_touch_points_);
   }
   touch_event_emulation_enabled_ = enabled;
@@ -502,19 +499,19 @@
   // fails during remote -> local main frame transition.
   // We should instead route emulation from browser through the WebViewImpl
   // to the local main frame, and remove InspectorEmulationAgent entirely.
-  if (web_view_impl_->MainFrameImpl())
-    web_view_impl_->MainFrameImpl()->GetFrameView()->UpdateLayout();
+  if (web_view_->MainFrameImpl())
+    web_view_->MainFrameImpl()->GetFrameView()->UpdateLayout();
 }
 
 void DevToolsEmulator::SetScriptExecutionDisabled(
     bool script_execution_disabled) {
   script_execution_disabled_ = script_execution_disabled;
-  web_view_impl_->GetPage()->GetSettings().SetScriptEnabled(
+  web_view_->GetPage()->GetSettings().SetScriptEnabled(
       script_execution_disabled_ ? false : embedder_script_enabled_);
 }
 
 bool DevToolsEmulator::HandleInputEvent(const WebInputEvent& input_event) {
-  Page* page = web_view_impl_->GetPage();
+  Page* page = web_view_->GetPage();
   if (!page)
     return false;
 
@@ -542,8 +539,8 @@
     float new_page_scale_factor = page_scale_factor * scaled_event.PinchScale();
     IntPoint anchor_css(*last_pinch_anchor_dip_.get());
     anchor_css.Scale(1.f / new_page_scale_factor, 1.f / new_page_scale_factor);
-    web_view_impl_->SetPageScaleFactor(new_page_scale_factor);
-    web_view_impl_->MainFrame()->SetScrollOffset(
+    web_view_->SetPageScaleFactor(new_page_scale_factor);
+    web_view_->MainFrame()->SetScrollOffset(
         ToIntSize(*last_pinch_anchor_css_.get() - ToIntSize(anchor_css)));
   }
   if (scaled_event.GetType() == WebInputEvent::kGesturePinchEnd) {
diff --git a/third_party/WebKit/Source/web/DevToolsEmulator.h b/third_party/WebKit/Source/core/inspector/DevToolsEmulator.h
similarity index 97%
rename from third_party/WebKit/Source/web/DevToolsEmulator.h
rename to third_party/WebKit/Source/core/inspector/DevToolsEmulator.h
index b8c738f5..f7120a0 100644
--- a/third_party/WebKit/Source/web/DevToolsEmulator.h
+++ b/third_party/WebKit/Source/core/inspector/DevToolsEmulator.h
@@ -6,6 +6,7 @@
 #define DevToolsEmulator_h
 
 #include <memory>
+#include "core/CoreExport.h"
 #include "platform/heap/Handle.h"
 #include "platform/wtf/Forward.h"
 #include "platform/wtf/Optional.h"
@@ -13,7 +14,6 @@
 #include "public/platform/WebFloatPoint.h"
 #include "public/platform/WebViewportStyle.h"
 #include "public/web/WebDeviceEmulationParams.h"
-#include "web/WebExport.h"
 
 namespace blink {
 
@@ -23,7 +23,7 @@
 class WebInputEvent;
 class WebViewBase;
 
-class WEB_EXPORT DevToolsEmulator final
+class CORE_EXPORT DevToolsEmulator final
     : public GarbageCollectedFinalized<DevToolsEmulator> {
  public:
   ~DevToolsEmulator();
@@ -78,7 +78,7 @@
   void ApplyViewportOverride(TransformationMatrix*);
   void UpdateRootLayerTransform();
 
-  WebViewBase* web_view_impl_;
+  WebViewBase* web_view_;
 
   bool device_metrics_enabled_;
   bool emulate_mobile_enabled_;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
index 3b785c0..e1f1693 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
@@ -556,18 +556,6 @@
   AddSelfVisualOverflow(LayoutRect(inflated_rect));
 }
 
-DISABLE_CFI_PERF
-bool LayoutBlock::CreatesNewFormattingContext() const {
-  return IsInlineBlockOrInlineTable() || IsFloatingOrOutOfFlowPositioned() ||
-         HasOverflowClip() || IsFlexItemIncludingDeprecated() ||
-         Style()->SpecifiesColumns() || IsLayoutFlowThread() || IsTableCell() ||
-         IsTableCaption() || IsFieldset() || IsWritingModeRoot() ||
-         IsDocumentElement() || IsGridItem() ||
-         Style()->GetColumnSpan() == kColumnSpanAll ||
-         Style()->ContainsPaint() || Style()->ContainsLayout() ||
-         IsSVGForeignObject() || Style()->Display() == EDisplay::kFlowRoot;
-}
-
 static inline bool ChangeInAvailableLogicalHeightAffectsChild(
     LayoutBlock* parent,
     LayoutBox& child) {
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlock.h b/third_party/WebKit/Source/core/layout/LayoutBlock.h
index e15f132..50fe207 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlock.h
+++ b/third_party/WebKit/Source/core/layout/LayoutBlock.h
@@ -137,7 +137,7 @@
   LayoutUnit MinLineHeightForReplacedObject(bool is_first_line,
                                             LayoutUnit replaced_height) const;
 
-  bool CreatesNewFormattingContext() const;
+  virtual bool CreatesNewFormattingContext() const { return true; }
 
   const char* GetName() const override;
 
@@ -471,8 +471,6 @@
   // Returns true if the positioned movement-only layout succeeded.
   bool TryLayoutDoingPositionedMovementOnly();
 
-  bool AvoidsFloats() const override { return true; }
-
   bool IsInSelfHitTestingPhase(HitTestAction hit_test_action) const final {
     return hit_test_action == kHitTestBlockBackground ||
            hit_test_action == kHitTestChildBlockBackground;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
index c8863f9..b649fd8 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
@@ -4246,13 +4246,72 @@
   DetermineLogicalLeftPositionForChild(spanner);
 }
 
+DISABLE_CFI_PERF
+bool LayoutBlockFlow::CreatesNewFormattingContext() const {
+  if (IsInline() || IsFloatingOrOutOfFlowPositioned() || HasOverflowClip() ||
+      IsFlexItemIncludingDeprecated() || IsTableCell() || IsTableCaption() ||
+      IsFieldset() || IsDocumentElement() || IsGridItem() ||
+      IsWritingModeRoot() || Style()->Display() == EDisplay::kFlowRoot ||
+      Style()->ContainsPaint() || Style()->ContainsLayout() ||
+      Style()->SpecifiesColumns() ||
+      Style()->GetColumnSpan() == kColumnSpanAll) {
+    // The specs require this object to establish a new formatting context.
+    return true;
+  }
+
+  // The remaining checks here are not covered by any spec, but we still need to
+  // establish new formatting contexts in some cases, for various reasons.
+
+  if (IsRubyText()) {
+    // Ruby text objects are pushed around after layout, to become flush with
+    // the associated ruby base. As such, we cannot let floats leak out from
+    // ruby text objects.
+    return true;
+  }
+
+  if (IsLayoutFlowThread()) {
+    // The spec requires multicol containers to establish new formatting
+    // contexts. Blink uses an anonymous flow thread child of the multicol
+    // container to actually perform layout inside. Therefore we need to
+    // propagate the BFCness down to the flow thread, so that floats are fully
+    // contained by the flow thread, and thereby the multicol container.
+    return true;
+  }
+
+  if (IsHR()) {
+    // Not mentioned in the spec, but we want HR elements to be pushed to the
+    // side by floats (and all engines seem to do that), since we use borders to
+    // render HR (and it would just ugly to let those borders be painted under
+    // the float).
+    return true;
+  }
+
+  if (IsLegend()) {
+    // This is wrong; see crbug.com/727378 . It may be that our current
+    // implementation requires the rendered legend inside a FIELDSET to create a
+    // new formatting context. That should probably be fixed too, but more
+    // importantly: We should never create a new formatting context for LEGEND
+    // elements that aren't associated with a FIELDSET.
+    return true;
+  }
+
+  if (IsTextControl()) {
+    // INPUT and other replaced elements rendered by Blink itself should be
+    // completely contained.
+    return true;
+  }
+
+  if (IsSVGForeignObject()) {
+    // This is the root of a foreign object. Don't let anything inside it escape
+    // to our ancestors.
+    return true;
+  }
+
+  return false;
+}
+
 bool LayoutBlockFlow::AvoidsFloats() const {
-  // Floats can't intrude into our box if we have a non-auto column count or
-  // width.
-  // Note: we need to use LayoutBox::avoidsFloats here since
-  // LayoutBlock::avoidsFloats is always true.
-  return LayoutBox::AvoidsFloats() || !Style()->HasAutoColumnCount() ||
-         !Style()->HasAutoColumnWidth();
+  return ShouldBeConsideredAsReplaced() || CreatesNewFormattingContext();
 }
 
 void LayoutBlockFlow::MoveChildrenTo(LayoutBoxModelObject* to_box_model_object,
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h
index 3950925..4c055f9 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h
+++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h
@@ -321,7 +321,8 @@
 
   void PositionSpannerDescendant(LayoutMultiColumnSpannerPlaceholder& child);
 
-  bool AvoidsFloats() const override;
+  bool CreatesNewFormattingContext() const override;
+  bool AvoidsFloats() const final;
 
   using LayoutBoxModelObject::MoveChildrenTo;
   void MoveChildrenTo(LayoutBoxModelObject* to_box_model_object,
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index 8cbc40d..a082d693 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -4907,14 +4907,8 @@
           isHTMLImageElement(ToElement(node)));
 }
 
-DISABLE_CFI_PERF
 bool LayoutBox::AvoidsFloats() const {
-  // crbug.com/460704: This should be merged with createsNewFormattingContext().
-  return ShouldBeConsideredAsReplaced() || HasOverflowClip() || IsHR() ||
-         IsLegend() || IsWritingModeRoot() || IsFlexItemIncludingDeprecated() ||
-         Style()->GetColumnSpan() == kColumnSpanAll ||
-         Style()->ContainsPaint() || Style()->ContainsLayout() ||
-         Style()->Display() == EDisplay::kFlowRoot;
+  return true;
 }
 
 bool LayoutBox::HasNonCompositedScrollbars() const {
diff --git a/third_party/WebKit/Source/core/layout/LayoutFieldset.h b/third_party/WebKit/Source/core/layout/LayoutFieldset.h
index f5a33c2e..26b243f 100644
--- a/third_party/WebKit/Source/core/layout/LayoutFieldset.h
+++ b/third_party/WebKit/Source/core/layout/LayoutFieldset.h
@@ -45,7 +45,6 @@
                                            SubtreeLayoutScope&) override;
 
   void ComputePreferredLogicalWidths() override;
-  bool AvoidsFloats() const override { return true; }
 
   void PaintBoxDecorationBackground(const PaintInfo&,
                                     const LayoutPoint&) const override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutRubyText.cpp b/third_party/WebKit/Source/core/layout/LayoutRubyText.cpp
index 508d3cb..6d259a8 100644
--- a/third_party/WebKit/Source/core/layout/LayoutRubyText.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutRubyText.cpp
@@ -79,8 +79,4 @@
   logical_width -= inset;
 }
 
-bool LayoutRubyText::AvoidsFloats() const {
-  return true;
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/LayoutRubyText.h b/third_party/WebKit/Source/core/layout/LayoutRubyText.h
index c6dbaed..db5de6b 100644
--- a/third_party/WebKit/Source/core/layout/LayoutRubyText.h
+++ b/third_party/WebKit/Source/core/layout/LayoutRubyText.h
@@ -49,8 +49,6 @@
   bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const override;
 
  private:
-  bool AvoidsFloats() const override;
-
   ETextAlign TextAlignmentForLine(bool ends_with_soft_break) const override;
   void AdjustInlineDirectionLineBounds(
       unsigned expansion_opportunity_count,
diff --git a/third_party/WebKit/Source/core/layout/LayoutTextControl.h b/third_party/WebKit/Source/core/layout/LayoutTextControl.h
index 0a19aa4..aec8b8e 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTextControl.h
+++ b/third_party/WebKit/Source/core/layout/LayoutTextControl.h
@@ -95,7 +95,6 @@
                                      LayoutUnit& max_logical_width) const final;
   void ComputePreferredLogicalWidths() final;
   void RemoveLeftoverAnonymousBlock(LayoutBlock*) final {}
-  bool AvoidsFloats() const final { return true; }
 
   void AddOutlineRects(Vector<LayoutRect>&,
                        const LayoutPoint& additional_offset,
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index f9b7422..fb30678 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -232,8 +232,7 @@
 
   // Place items from line-left to line-right along with the baseline.
   // Items are already bidi-reordered to the visual order.
-  LayoutUnit line_left_position = LogicalLeftOffset();
-  LayoutUnit position = line_left_position;
+  LayoutUnit position;
 
   for (auto& item_result : *line_items) {
     const NGInlineItem& item = items[item_result.item_index];
@@ -327,19 +326,14 @@
   // the line box to the line top.
   line_box.MoveChildrenInBlockDirection(baseline);
 
-  DCHECK_EQ(line_left_position, LogicalLeftOffset());
-  LayoutUnit inline_size = position - line_left_position;
+  LayoutUnit inline_size = position;
+  NGLogicalOffset offset(LogicalLeftOffset(),
+                         baseline - box_states_.LineBoxState().metrics.ascent);
+  ApplyTextAlign(&offset.inline_offset, inline_size,
+                 current_opportunity_.size.inline_size);
+
   line_box.SetInlineSize(inline_size);
-
-  // Account for text align property.
-  if (Node()->Style().GetTextAlign() == ETextAlign::kRight) {
-    line_box.MoveChildrenInInlineDirection(
-        current_opportunity_.size.inline_size - inline_size);
-  }
-
-  container_builder_.AddChild(
-      line_box.ToLineBoxFragment(),
-      {LayoutUnit(), baseline - box_states_.LineBoxState().metrics.ascent});
+  container_builder_.AddChild(line_box.ToLineBoxFragment(), offset);
 
   max_inline_size_ = std::max(max_inline_size_, inline_size);
   content_size_ = line_bottom;
@@ -397,6 +391,29 @@
                                 LayoutUnit(0));
 }
 
+void NGInlineLayoutAlgorithm::ApplyTextAlign(LayoutUnit* line_left,
+                                             LayoutUnit inline_size,
+                                             LayoutUnit available_width) {
+  // TODO(kojii): Implement text-align-last.
+  ETextAlign text_align = LineStyle().GetTextAlign();
+  switch (text_align) {
+    case ETextAlign::kRight:
+    case ETextAlign::kWebkitRight:
+      // Wide lines spill out of the block based off direction.
+      // So even if text-align is right, if direction is LTR, wide lines should
+      // overflow out of the right side of the block.
+      // TODO(kojii): Investigate how to handle trailing spaces.
+      if (inline_size < available_width ||
+          !LineStyle().IsLeftToRightDirection())
+        *line_left += available_width - inline_size;
+      break;
+    default:
+      // TODO(layout-dev): Implement.
+      // Refer to LayoutBlockFlow::UpdateLogicalWidthForAlignment().
+      break;
+  }
+}
+
 void NGInlineLayoutAlgorithm::FindNextLayoutOpportunity() {
   NGLogicalOffset iter_offset = ConstraintSpace().BfcOffset();
   if (container_builder_.BfcOffset()) {
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
index 7e9e601c..6e211718 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.h
@@ -75,6 +75,10 @@
                                       NGLineBoxFragmentBuilder*,
                                       NGTextFragmentBuilder*);
 
+  void ApplyTextAlign(LayoutUnit* line_left,
+                      LayoutUnit inline_size,
+                      LayoutUnit available_width);
+
   // Finds the next layout opportunity for the next text fragment.
   void FindNextLayoutOpportunity();
 
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
index d194c89..45b6776 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
@@ -155,12 +155,9 @@
       ToNGPhysicalBoxFragment(container_fragment->Children()[0].Get());
   auto* line_box_fragments_wrapper =
       ToNGPhysicalBoxFragment(span_box_fragments_wrapper->Children()[0].Get());
-  Vector<NGPhysicalTextFragment*> text_fragments;
+  Vector<NGPhysicalLineBoxFragment*> line_boxes;
   for (const auto& child : line_box_fragments_wrapper->Children()) {
-    auto* line_box = ToNGPhysicalLineBoxFragment(child.Get());
-    EXPECT_EQ(1u, line_box->Children().size());
-    for (const auto& text : line_box->Children())
-      text_fragments.push_back(ToNGPhysicalTextFragment(text.Get()));
+    line_boxes.push_back(ToNGPhysicalLineBoxFragment(child.Get()));
   }
 
   LayoutText* layout_text =
@@ -170,22 +167,22 @@
   // Line break points may vary by minor differences in fonts.
   // The test is valid as long as we have 3 or more lines and their positions
   // are correct.
-  EXPECT_GE(text_fragments.size(), 3UL);
+  EXPECT_GE(line_boxes.size(), 3UL);
 
-  auto* text_fragment1 = text_fragments[0];
+  auto* line_box1 = line_boxes[0];
   // 40 = #left-float1' width 30 + #left-float2 10
-  EXPECT_EQ(LayoutUnit(40), text_fragment1->Offset().left);
+  EXPECT_EQ(LayoutUnit(40), line_box1->Offset().left);
   InlineTextBox* inline_text_box1 = layout_text->FirstTextBox();
   EXPECT_EQ(LayoutUnit(40), inline_text_box1->X());
 
-  auto* text_fragment2 = text_fragments[1];
+  auto* line_box2 = line_boxes[1];
   // 40 = #left-float1' width 30
-  EXPECT_EQ(LayoutUnit(30), text_fragment2->Offset().left);
+  EXPECT_EQ(LayoutUnit(30), line_box2->Offset().left);
   InlineTextBox* inline_text_box2 = inline_text_box1->NextTextBox();
   EXPECT_EQ(LayoutUnit(30), inline_text_box2->X());
 
-  auto* text_fragment3 = text_fragments[2];
-  EXPECT_EQ(LayoutUnit(), text_fragment3->Offset().left);
+  auto* line_box3 = line_boxes[2];
+  EXPECT_EQ(LayoutUnit(), line_box3->Offset().left);
   InlineTextBox* inline_text_box3 = inline_text_box2->NextTextBox();
   EXPECT_EQ(LayoutUnit(), inline_text_box3->X());
 }
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node.cc
index 31440806..50d4bd0 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_node.cc
@@ -425,11 +425,13 @@
   for (const auto& container_child : box_fragment->Children()) {
     NGPhysicalLineBoxFragment* physical_line_box =
         ToNGPhysicalLineBoxFragment(container_child.Get());
+    NGLineBoxFragment line_box(constraint_space.WritingMode(),
+                               physical_line_box);
 
     // Create a BidiRunList for this line.
     CreateBidiRuns(&bidi_runs, physical_line_box->Children(), constraint_space,
-                   NGLogicalOffset(), items, text_offsets,
-                   &positions_for_bidi_runs, &positions);
+                   {line_box.InlineOffset(), LayoutUnit(0)}, items,
+                   text_offsets, &positions_for_bidi_runs, &positions);
     // TODO(kojii): bidi needs to find the logical last run.
     bidi_runs.SetLogicallyLastRun(bidi_runs.LastRun());
 
@@ -444,8 +446,7 @@
     PlaceInlineBoxChildren(root_line_box, positions_for_bidi_runs, positions);
 
     // Copy to RootInlineBox.
-    NGLineBoxFragment line_box(constraint_space.WritingMode(),
-                               physical_line_box);
+    root_line_box->SetLogicalLeft(line_box.InlineOffset());
     root_line_box->SetLogicalWidth(line_box.InlineSize());
     LayoutUnit line_top = line_box.BlockOffset();
     NGLineHeightMetrics line_metrics(Style(), baseline_type);
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.cc
index b7da848..6def2c7 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.cc
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.cc
@@ -50,18 +50,6 @@
     offset.block_offset += delta;
 }
 
-void NGLineBoxFragmentBuilder::MoveChildrenInInlineDirection(LayoutUnit delta) {
-  NGWritingMode writing_mode(
-      FromPlatformWritingMode(node_->Style().GetWritingMode()));
-  LayoutUnit child_inline_size;
-  for (size_t i = 0; i < children_.size(); ++i) {
-    offsets_[i].inline_offset = delta + child_inline_size;
-    NGPhysicalFragment* child = children_[i].Get();
-    child_inline_size +=
-        child->Size().ConvertToLogical(writing_mode).inline_size;
-  }
-}
-
 void NGLineBoxFragmentBuilder::MoveChildrenInBlockDirection(LayoutUnit delta,
                                                             unsigned start,
                                                             unsigned end) {
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.h b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.h
index aa09de1..cc9ae0ca 100644
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.h
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_box_fragment_builder.h
@@ -34,8 +34,6 @@
   void MoveChildrenInBlockDirection(LayoutUnit);
   void MoveChildrenInBlockDirection(LayoutUnit, unsigned start, unsigned end);
 
-  void MoveChildrenInInlineDirection(LayoutUnit delta);
-
   const Vector<RefPtr<NGPhysicalFragment>>& Children() const {
     return children_;
   }
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index 3c0095486..328a072 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -77,6 +77,7 @@
 #include "platform/loader/fetch/ResourceTimingInfo.h"
 #include "platform/loader/fetch/UniqueIdentifier.h"
 #include "platform/mhtml/MHTMLArchive.h"
+#include "platform/scheduler/child/web_scheduler.h"
 #include "platform/scheduler/renderer/web_view_scheduler.h"
 #include "platform/weborigin/SchemeRegistry.h"
 #include "platform/wtf/Vector.h"
@@ -831,7 +832,17 @@
 std::unique_ptr<WebURLLoader> FrameFetchContext::CreateURLLoader(
     const ResourceRequest& request) {
   auto loader = GetFrame()->CreateURLLoader();
-  loader->SetLoadingTaskRunner(GetTaskRunner().Get());
+  RefPtr<WebTaskRunner> task_runner;
+
+  if (request.GetKeepalive()) {
+    // The loader should be able to work after the frame destruction, so we
+    // cannot use the task runner associated with the frame.
+    task_runner =
+        Platform::Current()->CurrentThread()->Scheduler()->LoadingTaskRunner();
+  } else {
+    task_runner = GetTaskRunner();
+  }
+  loader->SetLoadingTaskRunner(task_runner.Get());
   return loader;
 }
 
diff --git a/third_party/WebKit/Source/core/loader/PingLoader.cpp b/third_party/WebKit/Source/core/loader/PingLoader.cpp
index 132141c..293fe8b 100644
--- a/third_party/WebKit/Source/core/loader/PingLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/PingLoader.cpp
@@ -61,7 +61,6 @@
 #include "platform/loader/fetch/UniqueIdentifier.h"
 #include "platform/network/EncodedFormData.h"
 #include "platform/network/ParsedContentType.h"
-#include "platform/scheduler/child/web_scheduler.h"
 #include "platform/weborigin/SecurityOrigin.h"
 #include "platform/weborigin/SecurityPolicy.h"
 #include "platform/wtf/Compiler.h"
@@ -270,8 +269,6 @@
     frame->FrameScheduler()->DidStopLoading(identifier_);
 
   loader_ = fetch_context.CreateURLLoader(request);
-  loader_->SetLoadingTaskRunner(
-      Platform::Current()->CurrentThread()->Scheduler()->LoadingTaskRunner());
   DCHECK(loader_);
   WrappedResourceRequest wrapped_request(request);
   wrapped_request.SetAllowStoredCredentials(credentials_allowed ==
@@ -414,6 +411,7 @@
                     ResourceRequest& request,
                     const AtomicString& initiator,
                     StoredCredentials credentials_allowed) {
+  request.SetKeepalive(true);
   if (MixedContentChecker::ShouldBlockFetch(frame, request, request.Url()))
     return false;
 
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 98f59d6..54af3f8 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -615,6 +615,7 @@
   "front_end/timeline_model/TracingLayerTree.js",
   "front_end/timeline/CountersGraph.js",
   "front_end/timeline/EventsTimelineTreeView.js",
+  "front_end/timeline/historyToolbarButton.css",
   "front_end/timeline/invalidationsTree.css",
   "front_end/timeline/module.json",
   "front_end/timeline/PerformanceModel.js",
@@ -626,6 +627,8 @@
   "front_end/timeline/TimelineFlameChartNetworkDataProvider.js",
   "front_end/timeline/timelineFlamechartPopover.css",
   "front_end/timeline/TimelineFlameChartView.js",
+  "front_end/timeline/timelineHistoryManager.css",
+  "front_end/timeline/TimelineHistoryManager.js",
   "front_end/timeline/TimelineLayersView.js",
   "front_end/timeline/TimelineLoader.js",
   "front_end/timeline/timelinePaintProfiler.css",
diff --git a/third_party/WebKit/Source/devtools/front_end/audits/AuditRules.js b/third_party/WebKit/Source/devtools/front_end/audits/AuditRules.js
index 5a8ddfc..e1c2f58 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits/AuditRules.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits/AuditRules.js
@@ -1317,7 +1317,7 @@
     const nonDataUrls = requests.map(r => r.url()).filter(url => url && url.asParsedURL());
     var cookieModel = target.model(SDK.CookieModel);
     if (cookieModel)
-      cookieModel.getCookiesAsync(nonDataUrls, resultCallback);
+      cookieModel.getCookies(nonDataUrls).then(resultCallback);
     else
       callback(result);
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/common/Settings.js b/third_party/WebKit/Source/devtools/front_end/common/Settings.js
index 794630a2..2b9a115 100644
--- a/third_party/WebKit/Source/devtools/front_end/common/Settings.js
+++ b/third_party/WebKit/Source/devtools/front_end/common/Settings.js
@@ -75,6 +75,7 @@
                             this.createSetting(settingName, defaultValue, storageType);
     if (descriptor['title'])
       setting.setTitle(descriptor['title']);
+    setting._extension = extension;
     this._moduleSettings.set(settingName, setting);
   }
 
@@ -263,6 +264,7 @@
     this._storage = storage;
     /** @type {string} */
     this._title = '';
+    this._extension = null;
   }
 
   /**
@@ -342,6 +344,13 @@
   }
 
   /**
+   * @return {?Runtime.Extension}
+   */
+  extension() {
+    return this._extension;
+  }
+
+  /**
    * @param {string} message
    * @param {string} name
    * @param {string} value
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js b/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
index 673b08a..f6e003f 100644
--- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
+++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
@@ -24,7 +24,6 @@
     this._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane);
     this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems);
     this._list.element.classList.add('context-list');
-    this._list.element.tabIndex = -1;
     this._rowHeight = 36;
     UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'console/consoleContextSelector.css')
         .appendChild(this._list.element);
diff --git a/third_party/WebKit/Source/devtools/front_end/cookie_table/CookiesTable.js b/third_party/WebKit/Source/devtools/front_end/cookie_table/CookiesTable.js
index cf40902..9318cc9 100644
--- a/third_party/WebKit/Source/devtools/front_end/cookie_table/CookiesTable.js
+++ b/third_party/WebKit/Source/devtools/front_end/cookie_table/CookiesTable.js
@@ -33,7 +33,7 @@
  */
 CookieTable.CookiesTable = class extends UI.VBox {
   /**
-   * @param {function(!SDK.Cookie, ?SDK.Cookie, function(?string))=} saveCallback
+   * @param {function(!SDK.Cookie, ?SDK.Cookie): !Promise<boolean>=} saveCallback
    * @param {function()=} refreshCallback
    * @param {function()=} selectedCallback
    * @param {function(!SDK.Cookie, function())=} deleteCallback
@@ -414,8 +414,8 @@
     var oldCookie = node.cookie;
     var newCookie = this._createCookieFromData(node.data);
     node.cookie = newCookie;
-    this._saveCallback(newCookie, oldCookie, error => {
-      if (!error)
+    this._saveCallback(newCookie, oldCookie).then(success => {
+      if (success)
         this._refresh();
       else
         node.setDirty(true);
diff --git a/third_party/WebKit/Source/devtools/front_end/main/Main.js b/third_party/WebKit/Source/devtools/front_end/main/Main.js
index aa05ff9..770a33f 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/Main.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/Main.js
@@ -113,6 +113,7 @@
     Runtime.experiments.register('timelineEventInitiators', 'Timeline: event initiators');
     Runtime.experiments.register('timelineFlowEvents', 'Timeline: flow events', true);
     Runtime.experiments.register('timelineInvalidationTracking', 'Timeline: invalidation tracking', true);
+    Runtime.experiments.register('timelineKeepHistory', 'Timeline: keep recording history');
     Runtime.experiments.register('timelineMultipleMainViews', 'Timeline: multiple main views');
     Runtime.experiments.register('timelinePaintTimingMarkers', 'Timeline: paint timing markers', true);
     Runtime.experiments.register('timelinePerFrameTrack', 'Timeline: per-frame tracks', true);
diff --git a/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js b/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
index f3e8102..aa6b17b7 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/RenderingOptions.js
@@ -28,9 +28,6 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/**
- * @implements {SDK.SDKModelObserver<!SDK.EmulationModel>}
- */
 Main.RenderingOptionsView = class extends UI.VBox {
   constructor() {
     super(true);
@@ -54,20 +51,14 @@
         Common.moduleSetting('showScrollBottleneckRects'));
     this.contentElement.createChild('div').classList.add('panel-section-separator');
 
-    var cssMediaSubtitle = Common.UIString('Forces media type for testing print and screen styles');
-    var checkboxLabel = UI.CheckboxLabel.create(Common.UIString('Emulate CSS Media'), false, cssMediaSubtitle);
-    this._mediaCheckbox = checkboxLabel.checkboxElement;
-    this._mediaCheckbox.addEventListener('click', this._mediaToggled.bind(this), false);
-    this.contentElement.appendChild(checkboxLabel);
-
-    var mediaRow = this.contentElement.createChild('div', 'media-row');
-    this._mediaSelect = mediaRow.createChild('select', 'chrome-select');
-    this._mediaSelect.appendChild(new Option(Common.UIString('print'), 'print'));
-    this._mediaSelect.appendChild(new Option(Common.UIString('screen'), 'screen'));
-    this._mediaSelect.addEventListener('change', this._mediaToggled.bind(this), false);
-    this._mediaSelect.disabled = true;
-
-    SDK.targetManager.observeModels(SDK.EmulationModel, this);
+    var mediaSetting = Common.moduleSetting('emulatedCSSMedia');
+    var mediaSelect = UI.SettingsUI.createControlForSetting(mediaSetting);
+    if (mediaSelect) {
+      var mediaRow = this.contentElement.createChild('span', 'media-row');
+      mediaRow.createChild('label').textContent = Common.UIString('Emulate CSS Media');
+      mediaRow.createChild('p').textContent = Common.UIString('Forces media type for testing print and screen styles');
+      mediaRow.appendChild(mediaSelect);
+    }
   }
 
   /**
@@ -80,27 +71,4 @@
     UI.SettingsUI.bindCheckbox(checkboxLabel.checkboxElement, setting);
     this.contentElement.appendChild(checkboxLabel);
   }
-
-  /**
-   * @override
-   * @param {!SDK.EmulationModel} emulationModel
-   */
-  modelAdded(emulationModel) {
-    if (this._mediaCheckbox.checked)
-      emulationModel.emulateCSSMedia(this._mediaSelect.value);
-  }
-
-  _mediaToggled() {
-    this._mediaSelect.disabled = !this._mediaCheckbox.checked;
-    var media = this._mediaCheckbox.checked ? this._mediaSelect.value : null;
-    for (var emulationModel of SDK.targetManager.models(SDK.EmulationModel))
-      emulationModel.emulateCSSMedia(media);
-  }
-
-  /**
-   * @override
-   * @param {!SDK.EmulationModel} emulationModel
-   */
-  modelRemoved(emulationModel) {
-  }
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/main/renderingOptions.css b/third_party/WebKit/Source/devtools/front_end/main/renderingOptions.css
index 3c79830d..77eeb51 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/renderingOptions.css
+++ b/third_party/WebKit/Source/devtools/front_end/main/renderingOptions.css
@@ -14,7 +14,13 @@
 }
 
 .media-row {
-    margin-left: 25px;
+    margin-left: 22px;
+    flex: none;
+}
+
+.media-row p {
+    margin-top: 0;
+    color: gray;
 }
 
 .panel-section-separator {
diff --git a/third_party/WebKit/Source/devtools/front_end/perf_ui/TimelineOverviewPane.js b/third_party/WebKit/Source/devtools/front_end/perf_ui/TimelineOverviewPane.js
index b175fba..06d08c8 100644
--- a/third_party/WebKit/Source/devtools/front_end/perf_ui/TimelineOverviewPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/perf_ui/TimelineOverviewPane.js
@@ -159,7 +159,6 @@
     this._overviewCalculator.setDisplayWidth(this._overviewGrid.clientWidth());
     for (var i = 0; i < this._overviewControls.length; ++i)
       this._overviewControls[i].update();
-    this._overviewGrid.updateDividers(this._overviewCalculator);
     this._updateMarkers();
     this._updateWindow();
   }
@@ -169,7 +168,6 @@
    */
   setMarkers(markers) {
     this._markers = markers;
-    this._updateMarkers();
   }
 
   _updateMarkers() {
@@ -467,8 +465,17 @@
   }
 
   resetCanvas() {
-    this._canvas.width = this.element.clientWidth * window.devicePixelRatio;
-    this._canvas.height = this.element.clientHeight * window.devicePixelRatio;
+    if (this.element.clientWidth)
+      this.setCanvasSize(this.element.clientWidth, this.element.clientHeight);
+  }
+
+  /**
+   * @param {number} width
+   * @param {number} height
+   */
+  setCanvasSize(width, height) {
+    this._canvas.width = width * window.devicePixelRatio;
+    this._canvas.height = height * window.devicePixelRatio;
   }
 };
 
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/CookieItemsView.js b/third_party/WebKit/Source/devtools/front_end/resources/CookieItemsView.js
index 9fd25526..1735eb5 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/CookieItemsView.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/CookieItemsView.js
@@ -66,16 +66,14 @@
   /**
    * @param {!SDK.Cookie} newCookie
    * @param {?SDK.Cookie} oldCookie
-   * @param {function(?string)} callback
+   * @return {!Promise<boolean>}
    */
-  _saveCookie(newCookie, oldCookie, callback) {
-    if (!this._model) {
-      callback(Common.UIString('Unable to save the cookie'));
-      return;
-    }
+  _saveCookie(newCookie, oldCookie) {
+    if (!this._model)
+      return Promise.resolve(false);
     if (oldCookie && (newCookie.name() !== oldCookie.name() || newCookie.url() !== oldCookie.url()))
       this._model.deleteCookie(oldCookie);
-    this._model.saveCookie(newCookie, callback);
+    return this._model.saveCookie(newCookie);
   }
 
   /**
@@ -87,7 +85,7 @@
   }
 
   /**
-   * @param {!Array.<!SDK.Cookie>} allCookies
+   * @param {!Array<!SDK.Cookie>} allCookies
    */
   _updateWithCookies(allCookies) {
     this._totalSize = allCookies.reduce((size, cookie) => size + cookie.size(), 0);
@@ -132,7 +130,7 @@
    * @override
    */
   refreshItems() {
-    this._model.getCookiesForDomain(this._cookieDomain, cookies => this._updateWithCookies(cookies));
+    this._model.getCookiesForDomain(this._cookieDomain).then(this._updateWithCookies.bind(this));
   }
 
   _onResponseReceived() {
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseQueryView.js b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseQueryView.js
index 85ea436..40ff3b2 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseQueryView.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseQueryView.js
@@ -135,10 +135,10 @@
 
   _queryFinished(query, columnNames, values) {
     var dataGrid = DataGrid.SortableDataGrid.create(columnNames, values);
-    dataGrid.setStriped(true);
     var trimmedQuery = query.trim();
 
     if (dataGrid) {
+      dataGrid.setStriped(true);
       dataGrid.renderInline();
       this._appendViewQueryResult(trimmedQuery, dataGrid.asWidget());
       dataGrid.autoSizeColumns(5);
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseTableView.js b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseTableView.js
index 582edd67..733d539 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/DatabaseTableView.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/DatabaseTableView.js
@@ -77,13 +77,13 @@
     this.element.removeChildren();
 
     this._dataGrid = DataGrid.SortableDataGrid.create(columnNames, values);
-    this._dataGrid.setStriped(true);
     this._visibleColumnsInput.setVisible(!!this._dataGrid);
     if (!this._dataGrid) {
       this._emptyWidget = new UI.EmptyWidget(Common.UIString('The “%s”\ntable is empty.', this.tableName));
       this._emptyWidget.show(this.element);
       return;
     }
+    this._dataGrid.setStriped(true);
     this._dataGrid.asWidget().show(this.element);
     this._dataGrid.autoSizeColumns(5);
 
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
index 4fd9528..a421227 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
@@ -53,6 +53,11 @@
         Common.UIString('Delete database'), () => this._deleteDatabase(), Common.UIString('Delete database'));
     footer.appendChild(this._clearButton);
 
+    this._refreshButton = UI.createTextButton(
+        Common.UIString('Refresh database'), () => this._refreshDatabaseButtonClicked(),
+        Common.UIString('Refresh database'));
+    footer.appendChild(this._refreshButton);
+
     this.update(database);
   }
 
@@ -61,12 +66,21 @@
     this._versionElement.textContent = this._database.version;
   }
 
+  _refreshDatabaseButtonClicked() {
+    this._model.refreshDatabase(this._database.databaseId);
+  }
+
   /**
    * @param {!Resources.IndexedDBModel.Database} database
    */
   update(database) {
     this._database = database;
     this._refreshDatabase();
+    this._updatedForTests();
+  }
+
+  _updatedForTests() {
+    // Sniffed in tests.
   }
 
   _deleteDatabase() {
@@ -289,6 +303,7 @@
 
       this._pageBackButton.setEnabled(!!skipCount);
       this._pageForwardButton.setEnabled(hasMore);
+      this._updatedDataForTests();
     }
 
     var idbKeyRange = key ? window.IDBKeyRange.lowerBound(key) : null;
@@ -302,6 +317,10 @@
     }
   }
 
+  _updatedDataForTests() {
+    // Sniffed in tests.
+  }
+
   /**
    * @param {!Common.Event} event
    */
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/CookieModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/CookieModel.js
index d9d958d..c4e44da 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/CookieModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/CookieModel.js
@@ -59,17 +59,11 @@
 
   /**
    * @param {!Array<string>} urls
-   * @param {function(!Array<!SDK.Cookie>)} callback
+   * @return {!Promise<!Array<!SDK.Cookie>>}
    */
-  getCookiesAsync(urls, callback) {
-    this.target().networkAgent().getCookies(urls, (err, cookies) => {
-      if (err) {
-        console.error(err);
-        return callback([]);
-      }
-
-      callback(cookies.map(cookie => SDK.CookieModel._parseProtocolCookie(cookie)));
-    });
+  getCookies(urls) {
+    return this.target().networkAgent().getCookies(urls).then(
+        cookies => (cookies || []).map(cookie => SDK.CookieModel._parseProtocolCookie(cookie)));
   }
 
   /**
@@ -85,30 +79,33 @@
    * @param {function()=} callback
    */
   clear(domain, callback) {
-    this.getCookiesForDomain(domain || null, cookies => this._deleteAll(cookies, callback));
+    this.getCookiesForDomain(domain || null).then(cookies => this._deleteAll(cookies, callback));
   }
 
   /**
    * @param {!SDK.Cookie} cookie
-   * @param {function(?Protocol.Error, boolean)} callback
+   * @return {!Promise<boolean>}
    */
-  saveCookie(cookie, callback) {
+  saveCookie(cookie) {
     var domain = cookie.domain();
     if (!domain.startsWith('.'))
       domain = '';
     var expires = undefined;
     if (cookie.expires())
       expires = Math.floor(Date.parse(cookie.expires()) / 1000);
-    this.target().networkAgent().setCookie(
-        cookie.url(), cookie.name(), cookie.value(), domain, cookie.path(), cookie.secure(), cookie.httpOnly(),
-        cookie.sameSite(), expires, callback);
+    return this.target()
+        .networkAgent()
+        .setCookie(
+            cookie.url(), cookie.name(), cookie.value(), domain, cookie.path(), cookie.secure(), cookie.httpOnly(),
+            cookie.sameSite(), expires)
+        .then(success => !!success);
   }
 
   /**
    * @param {?string} domain
-   * @param {function(!Array<!SDK.Cookie>)} callback
+   * @return {!Promise<!Array<!SDK.Cookie>>}
    */
-  getCookiesForDomain(domain, callback) {
+  getCookiesForDomain(domain) {
     var resourceURLs = [];
     /**
      * @param {!SDK.Resource} resource
@@ -121,7 +118,7 @@
     var resourceTreeModel = this.target().model(SDK.ResourceTreeModel);
     if (resourceTreeModel)
       resourceTreeModel.forAllResources(populateResourceURLs);
-    this.getCookiesAsync(resourceURLs, callback);
+    return this.getCookies(resourceURLs);
   }
 
   /**
@@ -130,10 +127,8 @@
    */
   _deleteAll(cookies, callback) {
     var networkAgent = this.target().networkAgent();
-    function deleteCookie(cookie) {
-      return new Promise(resolve => networkAgent.deleteCookie(cookie.name(), cookie.url(), resolve));
-    }
-    Promise.all(cookies.map(deleteCookie)).then(callback || function() {});
+    Promise.all(cookies.map(cookie => networkAgent.deleteCookie(cookie.name(), cookie.url())))
+        .then(callback || function() {});
   }
 };
 
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/EmulationModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/EmulationModel.js
index d006386b..4bd1c4eb 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/EmulationModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/EmulationModel.js
@@ -22,6 +22,11 @@
     if (disableJavascriptSetting.get())
       this._emulationAgent.setScriptExecutionDisabled(true);
 
+    var mediaSetting = Common.moduleSetting('emulatedCSSMedia');
+    mediaSetting.addChangeListener(() => this._emulateCSSMedia(mediaSetting.get()));
+    if (mediaSetting.get())
+      this._emulateCSSMedia(mediaSetting.get());
+
     this._touchEnabled = false;
     this._touchMobile = false;
     this._customTouchEnabled = false;
@@ -110,10 +115,10 @@
   }
 
   /**
-   * @param {?string} media
+   * @param {string} media
    */
-  emulateCSSMedia(media) {
-    this._emulationAgent.setEmulatedMedia(media || '');
+  _emulateCSSMedia(media) {
+    this._emulationAgent.setEmulatedMedia(media);
     if (this._cssModel)
       this._cssModel.mediaQueryResultChanged();
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
index 1be45dc..1d0b90c 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
@@ -883,19 +883,11 @@
 
   /**
    * @param {string} origin
-   * @param {function(!Array<string>)} callback
+   * @return {!Promise<!Array<string>>}
    */
-  getCertificate(origin, callback) {
+  getCertificate(origin) {
     var target = SDK.targetManager.mainTarget();
-    target.networkAgent().getCertificate(origin, mycallback);
-
-    /**
-     * @param {?Protocol.Error} error
-     * @param {!Array<string>} certificate
-     */
-    function mycallback(error, certificate) {
-      callback(error ? [] : certificate);
-    }
+    return target.networkAgent().getCertificate(origin).then(certificate => certificate || []);
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js
index 4750c08..311f417 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js
@@ -1013,28 +1013,20 @@
     return Common.ContentProvider.contentAsDataURL(content, this.mimeType, true, charset);
   }
 
-  _innerRequestContent() {
+  async _innerRequestContent() {
     if (this._contentRequested)
       return;
     this._contentRequested = true;
 
-    /**
-     * @param {?Protocol.Error} error
-     * @param {string} content
-     * @param {boolean} contentEncoded
-     * @this {SDK.NetworkRequest}
-     */
-    function onResourceContent(error, content, contentEncoded) {
-      this._content = error ? null : content;
-      this._contentError = error;
-      this._contentEncoded = contentEncoded;
-      var callbacks = this._pendingContentCallbacks.slice();
-      for (var i = 0; i < callbacks.length; ++i)
-        callbacks[i](this._content);
-      this._pendingContentCallbacks.length = 0;
-      delete this._contentRequested;
-    }
-    this._networkManager.target().networkAgent().getResponseBody(this._requestId, onResourceContent.bind(this));
+    var response =
+        await this._networkManager.target().networkAgent().invoke_getResponseBody({requestId: this._requestId});
+
+    this._content = response[Protocol.Error] ? null : response.body;
+    this._contentError = response[Protocol.Error];
+    this._contentEncoded = response.base64Encoded;
+    for (var callback of this._pendingContentCallbacks.splice(0))
+      callback(this._content);
+    delete this._contentRequested;
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/module.json b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
index 1965218..f6eec53b 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
@@ -106,34 +106,104 @@
         },
         {
             "type": "setting",
+            "category": "Rendering",
             "settingName": "showPaintRects",
             "settingType": "boolean",
             "storageType": "session",
+            "options": [
+                {
+                    "value": true,
+                    "title": "Show paint flashing rectangles"
+                },
+                {
+                    "value": false,
+                    "title": "Hide paint flashing rectangles"
+                }
+            ],
             "defaultValue": false
         },
         {
             "type": "setting",
+            "category": "Rendering",
             "settingName": "showDebugBorders",
             "settingType": "boolean",
             "storageType": "session",
+            "options": [
+                {
+                    "value": true,
+                    "title": "Show layer borders"
+                },
+                {
+                    "value": false,
+                    "title": "Hide layer borders"
+                }
+            ],
             "defaultValue": false
         },
         {
             "type": "setting",
+            "category": "Rendering",
             "settingName": "showFPSCounter",
             "settingType": "boolean",
             "storageType": "session",
+            "options": [
+                {
+                    "value": true,
+                    "title": "Show frames per second (FPS) meter"
+                },
+                {
+                    "value": false,
+                    "title": "Hide frames per second (FPS) meter"
+                }
+            ],
             "defaultValue": false
         },
         {
             "type": "setting",
+            "category": "Rendering",
             "settingName": "showScrollBottleneckRects",
             "settingType": "boolean",
             "storageType": "session",
+            "options": [
+                {
+                    "value": true,
+                    "title": "Show scroll performance bottlenecks"
+                },
+                {
+                    "value": false,
+                    "title": "Hide scroll performance bottlenecks"
+                }
+            ],
             "defaultValue": false
         },
         {
             "type": "setting",
+            "category": "Rendering",
+            "settingName": "emulatedCSSMedia",
+            "settingType": "enum",
+            "storageType": "session",
+            "defaultValue": "",
+            "options": [
+                {
+                    "title": "Do not emulate CSS media type",
+                    "text": "No emulation",
+                    "value": ""
+                },
+                {
+                    "title": "Emulate CSS print media type",
+                    "text": "print",
+                    "value": "print"
+                },
+                {
+                    "title": "Emulate CSS screen media type",
+                    "text": "screen",
+                    "value": "screen"
+                }
+            ],
+            "tags": "query"
+        },
+        {
+            "type": "setting",
             "category": "Console",
             "title": "Enable custom formatters",
             "settingName": "customFormatters",
diff --git a/third_party/WebKit/Source/devtools/front_end/security/SecurityPanel.js b/third_party/WebKit/Source/devtools/front_end/security/SecurityPanel.js
index 4db6bdc..1025b2a 100644
--- a/third_party/WebKit/Source/devtools/front_end/security/SecurityPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/security/SecurityPanel.js
@@ -63,12 +63,9 @@
      * @param {!Event} e
      */
     function showCertificateViewer(e) {
-      function certificateCallback(names) {
-        InspectorFrontendHost.showCertificateViewer(names);
-      }
-
       e.consume();
-      SDK.multitargetNetworkManager.getCertificate(origin, certificateCallback);
+      SDK.multitargetNetworkManager.getCertificate(origin).then(
+          names => InspectorFrontendHost.showCertificateViewer(names));
     }
 
     return UI.createTextButton(text, showCertificateViewer, 'security-certificate-button');
diff --git a/third_party/WebKit/Source/devtools/front_end/settings/SettingsScreen.js b/third_party/WebKit/Source/devtools/front_end/settings/SettingsScreen.js
index 098edceb..75c9e9a0 100644
--- a/third_party/WebKit/Source/devtools/front_end/settings/SettingsScreen.js
+++ b/third_party/WebKit/Source/devtools/front_end/settings/SettingsScreen.js
@@ -146,8 +146,6 @@
         ['', 'Appearance', 'Elements', 'Sources', 'Network', 'Profiler', 'Console', 'Extensions'];
     /** @type {!Map<string, !Element>} */
     this._nameToSection = new Map();
-    /** @type {!Map<string, !Element>} */
-    this._nameToSettingElement = new Map();
     for (var sectionName of explicitSectionOrder)
       this._sectionElement(sectionName);
     self.runtime.extensions('setting').forEach(this._addSetting.bind(this));
@@ -181,33 +179,11 @@
   _addSetting(extension) {
     if (!Settings.GenericSettingsTab.isSettingVisible(extension))
       return;
-    var descriptor = extension.descriptor();
-    var sectionName = descriptor['category'];
-    var settingName = descriptor['settingName'];
-    var setting = Common.moduleSetting(settingName);
-    var uiTitle = Common.UIString(extension.title());
-
-    var sectionElement = this._sectionElement(sectionName);
-    var settingControl;
-
-    switch (descriptor['settingType']) {
-      case 'boolean':
-        settingControl = UI.SettingsUI.createSettingCheckbox(uiTitle, setting);
-        break;
-      case 'enum':
-        if (Array.isArray(descriptor['options']))
-          settingControl = UI.SettingsUI.createSettingSelect(uiTitle, descriptor['options'], setting);
-        else
-          console.error('Enum setting defined without options');
-        break;
-      default:
-        console.error('Invalid setting type: ' + descriptor['settingType']);
-        return;
-    }
-    if (settingControl) {
-      this._nameToSettingElement.set(settingName, settingControl);
+    var sectionElement = this._sectionElement(extension.descriptor()['category']);
+    var setting = Common.moduleSetting(extension.descriptor()['settingName']);
+    var settingControl = UI.SettingsUI.createControlForSetting(setting);
+    if (settingControl)
       sectionElement.appendChild(settingControl);
-    }
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/PerformanceModel.js b/third_party/WebKit/Source/devtools/front_end/timeline/PerformanceModel.js
index c1471f7..90bffef 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/PerformanceModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/PerformanceModel.js
@@ -39,6 +39,13 @@
   }
 
   /**
+   * @return {number|undefined}
+   */
+  recordStartTime() {
+    return this._recordStartTime;
+  }
+
+  /**
    * @param {!SDK.TracingModel} model
    */
   setTracingModel(model) {
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js
new file mode 100644
index 0000000..68ffb79
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js
@@ -0,0 +1,421 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Timeline.TimelineHistoryManager = class {
+  constructor() {
+    /** @type {!Array<!Timeline.PerformanceModel>} */
+    this._recordings = [];
+    this._action = UI.actionRegistry.action('timeline.show-history');
+    this._action.setEnabled(false);
+    /** @type {!Map<string, number>} */
+    this._nextNumberByDomain = new Map();
+    this._button = new Timeline.TimelineHistoryManager.ToolbarButton(this._action);
+
+    this._allOverviews = [
+      {constructor: Timeline.TimelineEventOverviewResponsiveness, height: 3},
+      {constructor: Timeline.TimelineEventOverviewFrames, height: 16},
+      {constructor: Timeline.TimelineEventOverviewCPUActivity, height: 20},
+      {constructor: Timeline.TimelineEventOverviewNetwork, height: 8}
+    ];
+    this._totalHeight = this._allOverviews.reduce((acc, entry) => acc + entry.height, 0);
+    this._enabled = true;
+    /** @type {?Timeline.PerformanceModel} */
+    this._lastActiveModel = null;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   */
+  addRecording(performanceModel) {
+    this._lastActiveModel = performanceModel;
+    this._recordings.unshift(performanceModel);
+    this._buildPreview(performanceModel);
+    this._button.setText(this._title(performanceModel));
+    this._updateState();
+    if (this._recordings.length <= Timeline.TimelineHistoryManager._maxRecordings)
+      return;
+    var lruModel = this._recordings.reduce((a, b) => lastUsedTime(a) < lastUsedTime(b) ? a : b);
+    this._recordings.splice(this._recordings.indexOf(lruModel), 1);
+    lruModel.dispose();
+
+    /**
+     * @param {!Timeline.PerformanceModel} model
+     * @return {number}
+     */
+    function lastUsedTime(model) {
+      return Timeline.TimelineHistoryManager._dataForModel(model).lastUsed;
+    }
+  }
+
+  /**
+   * @param {boolean} enabled
+   */
+  setEnabled(enabled) {
+    this._enabled = enabled;
+    this._updateState();
+  }
+
+  button() {
+    return this._button;
+  }
+
+  clear() {
+    this._recordings.forEach(model => model.dispose());
+    this._recordings = [];
+    this._lastActiveModel = null;
+    this._updateState();
+    this._nextNumberByDomain.clear();
+  }
+
+  /**
+   * @return {!Promise<?Timeline.PerformanceModel>}
+   */
+  async showHistoryDropDown() {
+    if (this._recordings.length < 2 || !this._enabled)
+      return null;
+
+    var model = await Timeline.TimelineHistoryManager.DropDown.show(
+        this._recordings, /** @type {!Timeline.PerformanceModel} */ (this._lastActiveModel), this._button.element);
+    if (!model)
+      return null;
+    var index = this._recordings.indexOf(model);
+    if (index < 0) {
+      console.assert(false, `selected recording not found`);
+      return null;
+    }
+    Timeline.TimelineHistoryManager._dataForModel(model).lastUsed = Date.now();
+    this._lastActiveModel = model;
+    this._button.setText(this._title(model));
+    return model;
+  }
+
+  cancelIfShowing() {
+    Timeline.TimelineHistoryManager.DropDown.cancelIfShowing();
+  }
+
+  _updateState() {
+    this._action.setEnabled(this._recordings.length > 1 && this._enabled);
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   * @return {!Element}
+   */
+  static _previewElement(performanceModel) {
+    var data = Timeline.TimelineHistoryManager._dataForModel(performanceModel);
+    var startedAt = performanceModel.recordStartTime();
+    data.time.textContent =
+        startedAt ? Common.UIString('(%s ago)', Timeline.TimelineHistoryManager._coarseAge(startedAt)) : '';
+    return data.preview;
+  }
+
+  /**
+   * @param {number} time
+   * @return {string}
+   */
+  static _coarseAge(time) {
+    var seconds = Math.round((Date.now() - time) / 1000);
+    if (seconds < 50)
+      return Common.UIString('moments');
+    var minutes = Math.round(seconds / 60);
+    if (minutes < 50)
+      return Common.UIString('%s m', minutes);
+    var hours = Math.round(minutes / 60);
+    return Common.UIString('%s h', hours);
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   * @return {string}
+   */
+  _title(performanceModel) {
+    return Timeline.TimelineHistoryManager._dataForModel(performanceModel).title;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   */
+  _buildPreview(performanceModel) {
+    var parsedURL = performanceModel.timelineModel().pageURL().asParsedURL();
+    var domain = parsedURL ? parsedURL.host : '';
+    var sequenceNumber = this._nextNumberByDomain.get(domain) || 1;
+    var title = Common.UIString('%s #%d', domain, sequenceNumber);
+    this._nextNumberByDomain.set(domain, sequenceNumber + 1);
+    var timeElement = createElement('span');
+
+    var preview = createElementWithClass('div', 'preview-item vbox');
+    var data = {preview: preview, title: title, time: timeElement, lastUsed: Date.now()};
+    performanceModel[Timeline.TimelineHistoryManager._previewDataSymbol] = data;
+
+    preview.appendChild(this._buildTextDetails(performanceModel, title, timeElement));
+    var screenshotAndOverview = preview.createChild('div', 'hbox');
+    screenshotAndOverview.appendChild(this._buildScreenshotThumbnail(performanceModel));
+    screenshotAndOverview.appendChild(this._buildOverview(performanceModel));
+    return data.preview;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   * @param {string} title
+   * @param {!Element} timeElement
+   * @return {!Element}
+   */
+  _buildTextDetails(performanceModel, title, timeElement) {
+    var container = createElementWithClass('div', 'text-details hbox');
+    container.createChild('span', 'name').textContent = title;
+    var tracingModel = performanceModel.tracingModel();
+    var duration = Number.millisToString(tracingModel.maximumRecordTime() - tracingModel.minimumRecordTime(), false);
+    var timeContainer = container.createChild('span', 'time');
+    timeContainer.appendChild(createTextNode(duration));
+    timeContainer.appendChild(timeElement);
+    return container;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   * @return {!Element}
+   */
+  _buildScreenshotThumbnail(performanceModel) {
+    var container = createElementWithClass('div', 'screenshot-thumb');
+    var thumbnailAspectRatio = 3 / 2;
+    container.style.width = this._totalHeight * thumbnailAspectRatio + 'px';
+    container.style.height = this._totalHeight + 'px';
+    var filmStripModel = performanceModel.filmStripModel();
+    var lastFrame = filmStripModel.frames().peekLast();
+    if (!lastFrame)
+      return container;
+    lastFrame.imageDataPromise()
+        .then(data => UI.loadImageFromData(data))
+        .then(image => image && container.appendChild(image));
+    return container;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} performanceModel
+   * @return {!Element}
+   */
+  _buildOverview(performanceModel) {
+    var container = createElement('div');
+
+    container.style.width = Timeline.TimelineHistoryManager._previewWidth + 'px';
+    container.style.height = this._totalHeight + 'px';
+    var canvas = container.createChild('canvas');
+    canvas.width = window.devicePixelRatio * Timeline.TimelineHistoryManager._previewWidth;
+    canvas.height = window.devicePixelRatio * this._totalHeight;
+
+    var ctx = canvas.getContext('2d');
+    var yOffset = 0;
+    for (var overview of this._allOverviews) {
+      var timelineOverview = new overview.constructor();
+      timelineOverview.setCanvasSize(Timeline.TimelineHistoryManager._previewWidth, overview.height);
+      timelineOverview.setModel(performanceModel);
+      timelineOverview.update();
+      var sourceContext = timelineOverview.context();
+      var imageData = sourceContext.getImageData(0, 0, sourceContext.canvas.width, sourceContext.canvas.height);
+      ctx.putImageData(imageData, 0, yOffset);
+      yOffset += overview.height;
+    }
+    return container;
+  }
+
+  /**
+   * @param {!Timeline.PerformanceModel} model
+   * @return {?Timeline.TimelineHistoryManager.PreviewData}
+   */
+  static _dataForModel(model) {
+    return model[Timeline.TimelineHistoryManager._previewDataSymbol] || null;
+  }
+};
+
+/** @typedef {!{preview: !Element, time: !Element, lastUsed: number, title: string}} */
+Timeline.TimelineHistoryManager.PreviewData;
+
+Timeline.TimelineHistoryManager._maxRecordings = 5;
+Timeline.TimelineHistoryManager._previewWidth = 450;
+Timeline.TimelineHistoryManager._previewDataSymbol = Symbol('previewData');
+
+/**
+ * @implements {UI.ListDelegate<!Timeline.PerformanceModel>}
+ */
+Timeline.TimelineHistoryManager.DropDown = class {
+  /**
+   * @param {!Array<!Timeline.PerformanceModel>} models
+   */
+  constructor(models) {
+    this._glassPane = new UI.GlassPane();
+    this._glassPane.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent);
+    this._glassPane.setOutsideClickCallback(() => this._close(null));
+    this._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane);
+    this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom);
+
+    var shadowRoot =
+        UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'timeline/timelineHistoryManager.css');
+    var contentElement = shadowRoot.createChild('div', 'drop-down');
+
+    this._listControl = new UI.ListControl(this, UI.ListMode.NonViewport);
+    this._listControl.element.addEventListener('mousemove', this._onMouseMove.bind(this), false);
+    this._listControl.replaceAllItems(models);
+
+    contentElement.appendChild(this._listControl.element);
+    contentElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
+    contentElement.addEventListener('click', this._onClick.bind(this), false);
+
+    /** @type {?function(?Timeline.PerformanceModel)} */
+    this._selectionDone = null;
+  }
+
+  /**
+   * @param {!Array<!Timeline.PerformanceModel>} models
+   * @param {!Timeline.PerformanceModel} currentModel
+   * @param {!Element} anchor
+   * @return {!Promise<?Timeline.PerformanceModel>}
+   */
+  static show(models, currentModel, anchor) {
+    if (Timeline.TimelineHistoryManager.DropDown._instance)
+      return Promise.resolve(/** @type {?Timeline.PerformanceModel} */ (null));
+    var instance = new Timeline.TimelineHistoryManager.DropDown(models);
+    return instance._show(anchor, currentModel);
+  }
+
+  static cancelIfShowing() {
+    if (!Timeline.TimelineHistoryManager.DropDown._instance)
+      return;
+    Timeline.TimelineHistoryManager.DropDown._instance._close(null);
+  }
+
+  /**
+   * @param {!Element} anchor
+   * @param {!Timeline.PerformanceModel} currentModel
+   * @return {!Promise<?Timeline.PerformanceModel>}
+   */
+  _show(anchor, currentModel) {
+    Timeline.TimelineHistoryManager.DropDown._instance = this;
+    this._glassPane.setContentAnchorBox(anchor.boxInWindow());
+    this._glassPane.show(/** @type {!Document} */ (this._glassPane.contentElement.ownerDocument));
+    this._listControl.element.focus();
+    this._listControl.selectItem(currentModel);
+
+    return new Promise(fulfill => this._selectionDone = fulfill);
+  }
+
+  /**
+   * @param {!Event} event
+   */
+  _onMouseMove(event) {
+    var node = event.target.enclosingNodeOrSelfWithClass('preview-item');
+    var listItem = node && this._listControl.itemForNode(node);
+    if (!listItem)
+      return;
+    this._listControl.selectItem(listItem);
+  }
+
+  /**
+   * @param {!Event} event
+   */
+  _onClick(event) {
+    if (!event.target.enclosingNodeOrSelfWithClass('preview-item'))
+      return;
+    this._close(this._listControl.selectedItem());
+  }
+
+  /**
+   * @param {!Event} event
+   */
+  _onKeyDown(event) {
+    switch (event.key) {
+      case 'Escape':
+        this._close(null);
+        break;
+      case 'Enter':
+        this._close(this._listControl.selectedItem());
+        break;
+      default:
+        return;
+    }
+    event.consume(true);
+  }
+
+  /**
+   * @param {?Timeline.PerformanceModel} model
+   */
+  _close(model) {
+    this._selectionDone(model);
+    this._glassPane.hide();
+    Timeline.TimelineHistoryManager.DropDown._instance = null;
+  }
+
+  /**
+   * @override
+   * @param {!Timeline.PerformanceModel} item
+   * @return {!Element}
+   */
+  createElementForItem(item) {
+    var element = Timeline.TimelineHistoryManager._previewElement(item);
+    element.classList.remove('selected');
+    return element;
+  }
+
+  /**
+   * @override
+   * @param {!Timeline.PerformanceModel} item
+   * @return {number}
+   */
+  heightForItem(item) {
+    console.assert(false, 'Should not be called');
+    return 0;
+  }
+
+  /**
+   * @override
+   * @param {!Timeline.PerformanceModel} item
+   * @return {boolean}
+   */
+  isItemSelectable(item) {
+    return true;
+  }
+
+  /**
+   * @override
+   * @param {?Timeline.PerformanceModel} from
+   * @param {?Timeline.PerformanceModel} to
+   * @param {?Element} fromElement
+   * @param {?Element} toElement
+   */
+  selectedItemChanged(from, to, fromElement, toElement) {
+    if (fromElement)
+      fromElement.classList.remove('selected');
+    if (toElement)
+      toElement.classList.add('selected');
+  }
+};
+
+/**
+ * @type {?Timeline.TimelineHistoryManager.DropDown}
+ */
+Timeline.TimelineHistoryManager.DropDown._instance = null;
+
+
+Timeline.TimelineHistoryManager.ToolbarButton = class extends UI.ToolbarItem {
+  /**
+   * @param {!UI.Action} action
+   */
+  constructor(action) {
+    super(createElementWithClass('button', 'dropdown-button'));
+    var shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'timeline/historyToolbarButton.css');
+
+    this._contentElement = shadowRoot.createChild('span', 'content');
+    var dropdownArrowIcon = UI.Icon.create('smallicon-triangle-down');
+    shadowRoot.appendChild(dropdownArrowIcon);
+    this.element.addEventListener('click', () => void action.execute(), false);
+    this.setEnabled(action.enabled());
+    action.addEventListener(UI.Action.Events.Enabled, data => this.setEnabled(/** @type {boolean} */ (data)));
+  }
+
+  /**
+   * @param {string} text
+   */
+  setText(text) {
+    this._contentElement.textContent = text;
+  }
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
index ab801d7..faa7317b 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
@@ -52,6 +52,10 @@
     this._millisecondsToRecordAfterLoadEvent = 3000;
     this._toggleRecordAction =
         /** @type {!UI.Action }*/ (UI.actionRegistry.action('timeline.toggle-recording'));
+    this._recordReloadAction =
+        /** @type {!UI.Action }*/ (UI.actionRegistry.action('timeline.record-reload'));
+    this._historyManager =
+        Runtime.experiments.isEnabled('timelineKeepHistory') ? new Timeline.TimelineHistoryManager() : null;
 
     /** @type {!Array<!TimelineModel.TimelineModelFilter>} */
     this._filters = [];
@@ -158,6 +162,8 @@
    */
   willHide() {
     UI.context.setFlavor(Timeline.TimelinePanel, null);
+    if (this._historyManager)
+      this._historyManager.cancelIfShowing();
   }
 
   /**
@@ -216,10 +222,15 @@
   _populateToolbar() {
     // Record
     this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButton(this._toggleRecordAction));
-    this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('timeline.record-reload'));
+    this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButton(this._recordReloadAction));
     this._clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear');
-    this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clear());
+    this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._onClearButton());
     this._panelToolbar.appendToolbarItem(this._clearButton);
+    // History
+    if (this._historyManager) {
+      this._panelToolbar.appendSeparator();
+      this._panelToolbar.appendToolbarItem(this._historyManager.button());
+    }
     this._panelToolbar.appendSeparator();
 
     // View
@@ -366,6 +377,12 @@
     return true;
   }
 
+  async _showHistory() {
+    var model = await this._historyManager.showHistoryDropDown();
+    if (model && model !== this._performanceModel)
+      this._setModel(model);
+  }
+
   /**
    * @return {boolean}
    */
@@ -553,6 +570,9 @@
     var state = Timeline.TimelinePanel.State;
     this._toggleRecordAction.setToggled(this._state === state.Recording);
     this._toggleRecordAction.setEnabled(this._state === state.Recording || this._state === state.Idle);
+    this._recordReloadAction.setEnabled(this._state === state.Idle);
+    if (this._historyManager)
+      this._historyManager.setEnabled(this._state === state.Idle);
     this._clearButton.setEnabled(this._state === state.Idle);
     this._panelToolbar.setEnabled(this._state !== state.Loading);
     this._dropTarget.setEnabled(this._state === state.Idle);
@@ -574,6 +594,12 @@
     this._startRecording();
   }
 
+  _onClearButton() {
+    if (this._historyManager)
+      this._historyManager.clear();
+    this._clear();
+  }
+
   _clear() {
     this._showLandingPage();
     this._reset();
@@ -589,7 +615,7 @@
    * @param {?Timeline.PerformanceModel} model
    */
   _setModel(model) {
-    if (this._performanceModel)
+    if (this._performanceModel && !this._historyManager)
       this._performanceModel.dispose();
     this._performanceModel = model;
     this._currentView.setModel(model);
@@ -611,7 +637,6 @@
     } else {
       this.requestWindowTimes(0, Infinity);
     }
-    this._overviewPane.scheduleUpdate();
 
     this.select(null);
     if (this._flameChart)
@@ -769,6 +794,8 @@
     performanceModel.setTracingModel(tracingModel);
     this._backingStorage = backingStorage;
     this._setModel(performanceModel);
+    if (this._historyManager)
+      this._historyManager.addRecording(performanceModel);
   }
 
   _showRecordingStarted() {
@@ -1304,6 +1331,9 @@
       case 'timeline.jump-to-next-frame':
         panel._jumpToFrame(1);
         return true;
+      case 'timeline.show-history':
+        panel._showHistory();
+        return true;
     }
     return false;
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/historyToolbarButton.css b/third_party/WebKit/Source/devtools/front_end/timeline/historyToolbarButton.css
new file mode 100644
index 0000000..f66188b
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/historyToolbarButton.css
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+:host {
+  width: 160px;
+  height: 26px;
+  text-align: left;
+  display: flex;
+}
+
+:host([disabled]) {
+  opacity: .5;
+}
+
+.content {
+  padding-right: 5px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  flex: 1 1;
+  min-width: 35px;
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/module.json b/third_party/WebKit/Source/devtools/front_end/timeline/module.json
index b65c7ba6..eabd0a9 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/module.json
@@ -149,6 +149,27 @@
                     "shortcut": "]"
                 }
             ]
+        },
+        {
+            "type": "@UI.ActionDelegate",
+            "actionId": "timeline.show-history",
+            "className": "Timeline.TimelinePanel.ActionDelegate",
+            "category": "Performance",
+            "title": "Show recent timeline sessions",
+            "experiment": "timelineKeepHistory",
+            "contextTypes": [
+                "Timeline.TimelinePanel"
+            ],
+            "bindings": [
+                {
+                    "platform": "windows,linux",
+                    "shortcut": "Ctrl+H"
+                },
+                {
+                    "platform": "mac",
+                    "shortcut": "Meta+Y"
+                }
+            ]
         }
     ],
     "dependencies": [
@@ -173,6 +194,7 @@
         "TimelineFlameChartDataProvider.js",
         "TimelineFlameChartNetworkDataProvider.js",
         "TimelineFlameChartView.js",
+        "TimelineHistoryManager.js",
         "TimelineTreeModeView.js",
         "TimelineTreeView.js",
         "EventsTimelineTreeView.js",
@@ -182,8 +204,10 @@
         "TimelinePanel.js"
     ],
     "resources": [
+        "historyToolbarButton.css",
         "invalidationsTree.css",
         "timelineFlamechartPopover.css",
+        "timelineHistoryManager.css",
         "timelinePanel.css",
         "timelinePaintProfiler.css",
         "timelineStatusDialog.css"
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/timelineHistoryManager.css b/third_party/WebKit/Source/devtools/front_end/timeline/timelineHistoryManager.css
new file mode 100644
index 0000000..b9b69b63
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/timelineHistoryManager.css
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.drop-down {
+  padding: 1px;
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.1);
+  background: white;
+}
+
+.preview-item {
+  border-color: transparent;
+  border-style: solid;
+  border-width: 1px 5px;
+  padding: 2px 0px;
+  margin: 2px 1px;
+}
+
+.preview-item.selected {
+  border-color: rgb(56, 121, 217);
+}
+
+.text-details {
+  font-size: 11px;
+  padding: 3px;
+}
+
+.text-details span {
+  flex: 1 0;
+  padding-left: 8px;
+  padding-right: 8px;
+}
+
+.text-details .name {
+  font-weight: bold;
+}
+
+.text-details span.time {
+  color: #555;
+  text-align: right;
+}
+
+.screenshot-thumb {
+  display: flex;
+  border: 1px solid #ccc;
+  margin: 2px 4px;
+}
+
+.screenshot-thumb img {
+  margin: auto;
+  flex: 1 1;
+  max-width: 100%;
+  max-height: 100%;
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline_model/TimelineModel.js b/third_party/WebKit/Source/devtools/front_end/timeline_model/TimelineModel.js
index bcdfdf2c..b5e3377 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline_model/TimelineModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline_model/TimelineModel.js
@@ -33,7 +33,7 @@
  */
 TimelineModel.TimelineModel = class {
   constructor() {
-    this.reset();
+    this._reset();
   }
 
   /**
@@ -168,7 +168,7 @@
    * @param {boolean=} produceTraceStartedInPage
    */
   setEvents(tracingModel, produceTraceStartedInPage) {
-    this.reset();
+    this._reset();
     this._resetProcessingState();
 
     this._minimumRecordTime = tracingModel.minimumRecordTime();
@@ -225,6 +225,9 @@
         pageDevToolsMetadataEvents.push(event);
         var frames = ((event.args['data'] && event.args['data']['frames']) || []);
         frames.forEach(payload => this._addPageFrame(event, payload));
+        var rootFrame = this.rootFrames()[0];
+        if (rootFrame && rootFrame.url)
+          this._pageURL = rootFrame.url;
       } else if (event.name === TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingSessionIdForWorker) {
         workersDevToolsMetadataEvents.push(event);
       } else if (event.name === TimelineModel.TimelineModel.DevToolsMetadataEvent.TracingStartedInBrowser) {
@@ -767,6 +770,8 @@
           return false;
         if (!eventData['isMainFrame'])
           break;
+        if (eventData.url)
+          this._pageURL = eventData.url;
         this._hadCommitLoad = true;
         this._firstCompositeLayers = null;
         break;
@@ -871,7 +876,7 @@
       parent.addChild(pageFrame);
   }
 
-  reset() {
+  _reset() {
     this._virtualThreads = [];
     /** @type {!Array<!SDK.TracingModel.Event>} */
     this._mainThreadEvents = [];
@@ -897,6 +902,7 @@
     this._pageFrames = new Map();
     /** @type {!Map<string, !Array<!SDK.TracingModel.Event>>} */
     this._eventsByFrame = new Map();
+    this._pageURL = '';
 
     this._minimumRecordTime = 0;
     this._maximumRecordTime = 0;
@@ -980,6 +986,13 @@
   }
 
   /**
+   * @return {string}
+   */
+  pageURL() {
+    return this._pageURL;
+  }
+
+  /**
    * @param {string} frameId
    * @return {?TimelineModel.TimelineModel.PageFrame}
    */
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
index 9e3da79b..a2b92c00 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
@@ -70,6 +70,7 @@
     this._itemToElement = new Map();
     this._selectedIndex = -1;
 
+    this.element.tabIndex = -1;
     this.element.addEventListener('click', this._onClick.bind(this), false);
     this.element.addEventListener('keydown', this._onKeyDown.bind(this), false);
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js b/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
index c16b5458..76b306fe 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
@@ -123,6 +123,29 @@
 };
 
 /**
+ * @param {!Common.Setting} setting
+ * @return {?Element}
+ */
+UI.SettingsUI.createControlForSetting = function(setting) {
+  if (!setting.extension())
+    return null;
+  var descriptor = setting.extension().descriptor();
+  var uiTitle = Common.UIString(setting.title() || '');
+  switch (descriptor['settingType']) {
+    case 'boolean':
+      return UI.SettingsUI.createSettingCheckbox(uiTitle, setting);
+    case 'enum':
+      if (Array.isArray(descriptor['options']))
+        return UI.SettingsUI.createSettingSelect(uiTitle, descriptor['options'], setting);
+      console.error('Enum setting defined without options');
+      return null;
+    default:
+      console.error('Invalid setting type: ' + descriptor['settingType']);
+      return null;
+  }
+};
+
+/**
  * @interface
  */
 UI.SettingUI = function() {};
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js b/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js
index dea0e9b..909d19a 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js
@@ -68,6 +68,7 @@
     /** @type {?Element} */
     var longClickGlyph = null;
     toggled();
+    button.setEnabled(action.enabled());
     return button;
 
     /**
diff --git a/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py b/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
index cd5a41732..308171f 100755
--- a/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
+++ b/third_party/WebKit/Source/devtools/scripts/build/generate_protocol_externs.py
@@ -55,7 +55,6 @@
     "DOMDebugger",
     "IndexedDB",
     "LayerTree",
-    "Network",
 ])
 
 
diff --git a/third_party/WebKit/Source/modules/accessibility/AXMenuList.h b/third_party/WebKit/Source/modules/accessibility/AXMenuList.h
index 88d1d786..856dbd1 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXMenuList.h
+++ b/third_party/WebKit/Source/modules/accessibility/AXMenuList.h
@@ -49,6 +49,8 @@
   void DidHidePopup();
 
  private:
+  friend class AXMenuListOption;
+
   AXMenuList(LayoutMenuList*, AXObjectCacheImpl&);
 
   bool IsMenuList() const override { return true; }
diff --git a/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.cpp b/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.cpp
index af8b879f..f8c4eec 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.cpp
@@ -28,6 +28,7 @@
 #include "SkMatrix44.h"
 #include "core/dom/AccessibleNode.h"
 #include "core/html/HTMLSelectElement.h"
+#include "modules/accessibility/AXMenuList.h"
 #include "modules/accessibility/AXMenuListPopup.h"
 #include "modules/accessibility/AXObjectCacheImpl.h"
 
@@ -48,6 +49,12 @@
   AXMockObject::Detach();
 }
 
+LocalFrameView* AXMenuListOption::DocumentFrameView() const {
+  if (IsDetached())
+    return nullptr;
+  return element_->GetDocument().View();
+}
+
 AccessibilityRole AXMenuListOption::RoleValue() const {
   const AtomicString& aria_role =
       GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole);
@@ -72,14 +79,21 @@
   if (!select)
     return nullptr;
   AXObjectImpl* select_ax_object = AxObjectCache().GetOrCreate(select);
-  if (select_ax_object->HasChildren()) {
-    const auto& child_objects = select_ax_object->Children();
-    DCHECK(!child_objects.IsEmpty());
+
+  // This happens if the <select> is not rendered. Return it and move on.
+  if (!select_ax_object->IsMenuList())
+    return select_ax_object;
+
+  AXMenuList* menu_list = ToAXMenuList(select_ax_object);
+  if (menu_list->HasChildren()) {
+    const auto& child_objects = menu_list->Children();
+    if (child_objects.IsEmpty())
+      return nullptr;
     DCHECK_EQ(child_objects.size(), 1UL);
     DCHECK(child_objects[0]->IsMenuListPopup());
     ToAXMenuListPopup(child_objects[0].Get())->UpdateChildrenIfNecessary();
   } else {
-    select_ax_object->UpdateChildrenIfNecessary();
+    menu_list->UpdateChildrenIfNecessary();
   }
   return parent_.Get();
 }
diff --git a/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.h b/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.h
index ced0f73..32f42e9 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.h
+++ b/third_party/WebKit/Source/modules/accessibility/AXMenuListOption.h
@@ -52,6 +52,7 @@
   Node* GetNode() const override { return element_; }
   void Detach() override;
   bool IsDetached() const override { return !element_; }
+  LocalFrameView* DocumentFrameView() const override;
   AccessibilityRole RoleValue() const override;
   bool CanHaveChildren() const override { return false; }
   AXObjectImpl* ComputeParent() const override;
diff --git a/third_party/WebKit/Source/platform/exported/WebURLRequest.cpp b/third_party/WebKit/Source/platform/exported/WebURLRequest.cpp
index 1d95bc7..85e95e73 100644
--- a/third_party/WebKit/Source/platform/exported/WebURLRequest.cpp
+++ b/third_party/WebKit/Source/platform/exported/WebURLRequest.cpp
@@ -302,6 +302,14 @@
   resource_request_->SetUseStreamOnResponse(use_stream_on_response);
 }
 
+bool WebURLRequest::GetKeepalive() const {
+  return resource_request_->GetKeepalive();
+}
+
+void WebURLRequest::SetKeepalive(bool keepalive) {
+  resource_request_->SetKeepalive(keepalive);
+}
+
 WebURLRequest::ServiceWorkerMode WebURLRequest::GetServiceWorkerMode() const {
   return resource_request_->GetServiceWorkerMode();
 }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h
index 31807a1..6c692aad 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h
@@ -96,14 +96,14 @@
   void ReportResourceTimingToClients(const ResourceTimingInfo&) override;
 };
 
-#if ENABLE(SECURITY_ASSERT)
+// TODO(yhirano): Recover #if ENABLE(SECURITY_ASSERT) when we stop adding
+// RawResources to MemoryCache.
 inline bool IsRawResource(const Resource& resource) {
   Resource::Type type = resource.GetType();
   return type == Resource::kMainResource || type == Resource::kRaw ||
          type == Resource::kTextTrack || type == Resource::kMedia ||
          type == Resource::kManifest || type == Resource::kImportResource;
 }
-#endif
 inline RawResource* ToRawResource(Resource* resource) {
   SECURITY_DCHECK(!resource || IsRawResource(*resource));
   return static_cast<RawResource*>(resource);
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp
index 5cedba9..c2b85c7 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp
@@ -35,6 +35,7 @@
 #include "platform/loader/fetch/FetchContext.h"
 #include "platform/loader/fetch/FetchInitiatorTypeNames.h"
 #include "platform/loader/fetch/MemoryCache.h"
+#include "platform/loader/fetch/RawResource.h"
 #include "platform/loader/fetch/ResourceLoader.h"
 #include "platform/loader/fetch/ResourceLoadingLog.h"
 #include "platform/loader/fetch/ResourceTimingInfo.h"
@@ -152,6 +153,23 @@
   return kResourceLoadPriorityUnresolved;
 }
 
+bool ShouldResourceBeAddedToMemoryCache(const FetchParameters& params,
+                                        Resource* resource) {
+  if (!IsMainThread())
+    return false;
+  if (params.Options().data_buffering_policy == kDoNotBufferData)
+    return false;
+
+  // TODO(yhirano): Stop adding RawResources to MemoryCache completely.
+  if (resource->GetType() == Resource::kMainResource)
+    return false;
+  if (IsRawResource(*resource) &&
+      (params.IsSpeculativePreload() || params.IsLinkPreload())) {
+    return false;
+  }
+  return true;
+}
+
 }  // namespace
 
 ResourceLoadPriority ResourceFetcher::ComputeLoadPriority(
@@ -440,8 +458,10 @@
   resource->SetCacheIdentifier(cache_identifier);
   resource->Finish();
 
-  if (!substitute_data.IsValid())
+  if (ShouldResourceBeAddedToMemoryCache(params, resource) &&
+      !substitute_data.IsValid()) {
     GetMemoryCache()->Add(resource);
+  }
 
   return resource;
 }
@@ -795,12 +815,8 @@
   }
   resource->SetCacheIdentifier(cache_identifier);
 
-  // - Don't add main resource to cache to prevent reuse.
-  // - Don't add the resource if its body will not be stored.
-  if (IsMainThread() && factory.GetType() != Resource::kMainResource &&
-      params.Options().data_buffering_policy != kDoNotBufferData) {
+  if (ShouldResourceBeAddedToMemoryCache(params, resource))
     GetMemoryCache()->Add(resource);
-  }
   return resource;
 }
 
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.cpp
index d6a19cca..52c9e86 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.cpp
@@ -57,6 +57,7 @@
       has_user_gesture_(false),
       download_to_file_(false),
       use_stream_on_response_(false),
+      keepalive_(false),
       should_reset_app_cache_(false),
       service_worker_mode_(WebURLRequest::ServiceWorkerMode::kAll),
       priority_(kResourceLoadPriorityLowest),
@@ -101,6 +102,7 @@
   SetHasUserGesture(data->has_user_gesture_);
   SetDownloadToFile(data->download_to_file_);
   SetUseStreamOnResponse(data->use_stream_on_response_);
+  SetKeepalive(data->keepalive_);
   SetServiceWorkerMode(data->service_worker_mode_);
   SetShouldResetAppCache(data->should_reset_app_cache_);
   SetRequestorID(data->requestor_id_);
@@ -150,6 +152,7 @@
   data->has_user_gesture_ = has_user_gesture_;
   data->download_to_file_ = download_to_file_;
   data->use_stream_on_response_ = use_stream_on_response_;
+  data->keepalive_ = keepalive_;
   data->service_worker_mode_ = service_worker_mode_;
   data->should_reset_app_cache_ = should_reset_app_cache_;
   data->requestor_id_ = requestor_id_;
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.h b/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.h
index 9964ecd..31ae7068 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceRequest.h
@@ -217,6 +217,10 @@
     use_stream_on_response_ = use_stream_on_response;
   }
 
+  // True if the request can work after the fetch group is terminated.
+  bool GetKeepalive() const { return keepalive_; }
+  void SetKeepalive(bool keepalive) { keepalive_ = keepalive; }
+
   // The service worker mode indicating which service workers should get events
   // for this request.
   WebURLRequest::ServiceWorkerMode GetServiceWorkerMode() const {
@@ -346,6 +350,7 @@
   bool has_user_gesture_ : 1;
   bool download_to_file_ : 1;
   bool use_stream_on_response_ : 1;
+  bool keepalive_ : 1;
   bool should_reset_app_cache_ : 1;
   WebURLRequest::ServiceWorkerMode service_worker_mode_;
   ResourceLoadPriority priority_;
@@ -401,6 +406,7 @@
   bool download_to_file_;
   WebURLRequest::ServiceWorkerMode service_worker_mode_;
   bool use_stream_on_response_;
+  bool keepalive_;
   bool should_reset_app_cache_;
   ResourceLoadPriority priority_;
   int intra_priority_value_;
diff --git a/third_party/WebKit/Source/web/BUILD.gn b/third_party/WebKit/Source/web/BUILD.gn
index c990fde..1d5f8ab 100644
--- a/third_party/WebKit/Source/web/BUILD.gn
+++ b/third_party/WebKit/Source/web/BUILD.gn
@@ -55,8 +55,6 @@
     "ContextMenuClientImpl.h",
     "DedicatedWorkerMessagingProxyProviderImpl.cpp",
     "DedicatedWorkerMessagingProxyProviderImpl.h",
-    "DevToolsEmulator.cpp",
-    "DevToolsEmulator.h",
     "EditorClientImpl.cpp",
     "EditorClientImpl.h",
     "ExternalDateTimeChooser.cpp",
diff --git a/third_party/WebKit/Source/web/ChromeClientImpl.cpp b/third_party/WebKit/Source/web/ChromeClientImpl.cpp
index 51d3615..ea57c9a 100644
--- a/third_party/WebKit/Source/web/ChromeClientImpl.cpp
+++ b/third_party/WebKit/Source/web/ChromeClientImpl.cpp
@@ -53,6 +53,7 @@
 #include "core/html/forms/DateTimeChooser.h"
 #include "core/html/forms/DateTimeChooserClient.h"
 #include "core/html/forms/DateTimeChooserImpl.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/layout/HitTestResult.h"
 #include "core/layout/LayoutPart.h"
 #include "core/layout/compositing/CompositedSelection.h"
@@ -116,7 +117,6 @@
 #include "web/AudioOutputDeviceClientImpl.h"
 #include "web/ColorChooserPopupUIController.h"
 #include "web/ColorChooserUIController.h"
-#include "web/DevToolsEmulator.h"
 #include "web/ExternalDateTimeChooser.h"
 #include "web/ExternalPopupMenu.h"
 #include "web/IndexedDBClientImpl.h"
diff --git a/third_party/WebKit/Source/web/InspectorEmulationAgent.cpp b/third_party/WebKit/Source/web/InspectorEmulationAgent.cpp
index 5b2c2ec..fdb8515 100644
--- a/third_party/WebKit/Source/web/InspectorEmulationAgent.cpp
+++ b/third_party/WebKit/Source/web/InspectorEmulationAgent.cpp
@@ -8,6 +8,7 @@
 #include "core/frame/LocalFrameView.h"
 #include "core/frame/Settings.h"
 #include "core/frame/WebLocalFrameBase.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/inspector/protocol/DOM.h"
 #include "core/page/Page.h"
 #include "platform/geometry/DoubleRect.h"
@@ -16,7 +17,6 @@
 #include "public/platform/Platform.h"
 #include "public/platform/WebFloatPoint.h"
 #include "public/platform/WebThread.h"
-#include "web/DevToolsEmulator.h"
 
 namespace blink {
 
diff --git a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
index 00c25a06..56b9734 100644
--- a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
+++ b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
@@ -51,6 +51,7 @@
 #include "core/html/HTMLMediaElement.h"
 #include "core/html/HTMLPlugInElement.h"
 #include "core/input/EventHandler.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/layout/HitTestResult.h"
 #include "core/loader/DocumentLoader.h"
 #include "core/loader/FrameLoadRequest.h"
@@ -105,7 +106,6 @@
 #include "public/web/WebPluginParams.h"
 #include "public/web/WebViewClient.h"
 #include "v8/include/v8.h"
-#include "web/DevToolsEmulator.h"
 #include "web/WebDevToolsAgentImpl.h"
 #include "web/WebDevToolsFrontendImpl.h"
 #include "web/WebPluginContainerImpl.h"
diff --git a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
index 6118ed2f..32b956d 100644
--- a/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
+++ b/third_party/WebKit/Source/web/WebDevToolsAgentImpl.cpp
@@ -42,6 +42,7 @@
 #include "core/frame/LocalFrameView.h"
 #include "core/frame/Settings.h"
 #include "core/frame/WebLocalFrameBase.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/inspector/InspectedFrames.h"
 #include "core/inspector/InspectorAnimationAgent.h"
 #include "core/inspector/InspectorApplicationCacheAgent.h"
@@ -85,7 +86,6 @@
 #include "public/platform/WebString.h"
 #include "public/web/WebDevToolsAgentClient.h"
 #include "public/web/WebSettings.h"
-#include "web/DevToolsEmulator.h"
 #include "web/InspectorEmulationAgent.h"
 #include "web/InspectorOverlayAgent.h"
 #include "web/WebFrameWidgetImpl.h"
diff --git a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
index 76d4b0b..a246c8c2 100644
--- a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
+++ b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
@@ -766,8 +766,8 @@
   // Remote viewports are only applicable to local frames with remote ancestors.
   DCHECK(local_root_->Parent() && local_root_->Parent()->IsWebRemoteFrame());
 
-  if (local_root_->GetFrameView()) {
-    local_root_->GetFrameView()->SetViewportIntersectionFromParent(
+  if (local_root_->GetFrame()) {
+    local_root_->GetFrame()->SetViewportIntersectionFromParent(
         viewport_intersection);
   }
 }
diff --git a/third_party/WebKit/Source/web/WebSettingsImpl.cpp b/third_party/WebKit/Source/web/WebSettingsImpl.cpp
index 134d15f6d..61794c9 100644
--- a/third_party/WebKit/Source/web/WebSettingsImpl.cpp
+++ b/third_party/WebKit/Source/web/WebSettingsImpl.cpp
@@ -33,9 +33,9 @@
 #include "core/frame/Settings.h"
 #include "platform/graphics/DeferredImageDecoder.h"
 
+#include "core/inspector/DevToolsEmulator.h"
 #include "public/platform/WebString.h"
 #include "public/platform/WebURL.h"
-#include "web/DevToolsEmulator.h"
 #include "web/WebDevToolsAgentImpl.h"
 
 namespace blink {
diff --git a/third_party/WebKit/Source/web/WebViewImpl.cpp b/third_party/WebKit/Source/web/WebViewImpl.cpp
index 9d68a677..9c3803e 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.cpp
+++ b/third_party/WebKit/Source/web/WebViewImpl.cpp
@@ -73,6 +73,7 @@
 #include "core/input/ContextMenuAllowedScope.h"
 #include "core/input/EventHandler.h"
 #include "core/input/TouchActionUtil.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/layout/LayoutPart.h"
 #include "core/layout/TextAutosizer.h"
 #include "core/layout/api/LayoutViewItem.h"
@@ -165,7 +166,6 @@
 #include "web/AnimationWorkletProxyClientImpl.h"
 #include "web/CompositorWorkerProxyClientImpl.h"
 #include "web/DedicatedWorkerMessagingProxyProviderImpl.h"
-#include "web/DevToolsEmulator.h"
 #include "web/FullscreenController.h"
 #include "web/LinkHighlightImpl.h"
 #include "web/PageOverlay.h"
diff --git a/third_party/WebKit/Source/web/tests/WebViewTest.cpp b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
index 7f4f3c7..1c33978 100644
--- a/third_party/WebKit/Source/web/tests/WebViewTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
@@ -51,6 +51,7 @@
 #include "core/html/HTMLIFrameElement.h"
 #include "core/html/HTMLInputElement.h"
 #include "core/html/HTMLTextAreaElement.h"
+#include "core/inspector/DevToolsEmulator.h"
 #include "core/layout/api/LayoutViewItem.h"
 #include "core/loader/DocumentLoader.h"
 #include "core/loader/FrameLoadRequest.h"
@@ -106,7 +107,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
-#include "web/DevToolsEmulator.h"
 #include "web/WebSettingsImpl.h"
 #include "web/tests/FrameTestHelpers.h"
 
diff --git a/third_party/WebKit/public/platform/WebURLRequest.h b/third_party/WebKit/public/platform/WebURLRequest.h
index ba9dcb8..99ba61d 100644
--- a/third_party/WebKit/public/platform/WebURLRequest.h
+++ b/third_party/WebKit/public/platform/WebURLRequest.h
@@ -290,6 +290,10 @@
   BLINK_PLATFORM_EXPORT bool UseStreamOnResponse() const;
   BLINK_PLATFORM_EXPORT void SetUseStreamOnResponse(bool);
 
+  // True if the request can work after the fetch group is terminated.
+  BLINK_PLATFORM_EXPORT bool GetKeepalive() const;
+  BLINK_PLATFORM_EXPORT void SetKeepalive(bool);
+
   // The service worker mode indicating which service workers should get events
   // for this request.
   BLINK_PLATFORM_EXPORT ServiceWorkerMode GetServiceWorkerMode() const;
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 29d30f0..cb0382b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -28729,6 +28729,11 @@
   <int value="5" label="Shortcut. Restart required."/>
 </enum>
 
+<enum name="PpdSource" type="int">
+  <int value="0" label="PPD provided by user"/>
+  <int value="1" label="PPD downloaded from SCS"/>
+</enum>
+
 <enum name="PrecacheEvents" type="int">
   <int value="0" label="PRECACHE_TASK_STARTED_PERIODIC"/>
   <int value="1" label="PRECACHE_TASK_STARTED_ONEOFF"/>
@@ -29203,6 +29208,17 @@
   <int value="4" label="Printer provider Web Store app launched"/>
 </enum>
 
+<enum name="PrinterSetupResult" type="int">
+  <int value="0" label="Fatal Error"/>
+  <int value="1" label="Success"/>
+  <int value="2" label="Printer Unreachable"/>
+  <int value="3" label="Could not contact debugd over dbus"/>
+  <int value="10" label="PPD exceeds size limit"/>
+  <int value="11" label="PPD Rejected by cupstestppd"/>
+  <int value="12" label="Could not find PPD"/>
+  <int value="13" label="Failed to download PPD"/>
+</enum>
+
 <enum name="PrintJobResult" type="int">
   <int value="0" label="Unknown"/>
   <int value="1" label="Successful Finish"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index bff0523..56a5e0f 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -57161,6 +57161,15 @@
   </summary>
 </histogram>
 
+<histogram name="Printing.CUPS.PpdSource" enum="PpdSource">
+  <owner>skau@chromium.org</owner>
+  <summary>
+    Records the source of PostScript Printer Description files used during
+    printer setup.  Entries are recorded for every attempted configuration. Only
+    recorded on Chrome OS.
+  </summary>
+</histogram>
+
 <histogram name="Printing.CUPS.PrintersDiscovered" units="printers">
   <owner>skau@chromium.org</owner>
   <summary>
@@ -57169,6 +57178,15 @@
   </summary>
 </histogram>
 
+<histogram name="Printing.CUPS.PrinterSetupResult" enum="PrinterSetupResult">
+  <owner>skau@chromium.org</owner>
+  <summary>
+    The success or error code for the setup of a CUPS printer.  Recorded when
+    setup is attempted through the settings dialogs.  Only recorded on Chrome
+    OS.
+  </summary>
+</histogram>
+
 <histogram name="Printing.CUPS.PrintJobsQueued" units="count">
   <owner>skau@chromium.org</owner>
   <summary>
@@ -91274,6 +91292,10 @@
   <affected-histogram
       name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram
+      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
+  <affected-histogram
+      name="PageLoad.Experimental.PaintTiming.ParseStartToFirstMeaningfulPaint"/>
+  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram
       name="PageLoad.PaintTiming.ParseStartToFirstContentfulPaint"/>
@@ -91288,11 +91310,16 @@
 <histogram_suffixes name="PageLoadMetricsClientsServiceWorkerSpecialApps"
     separator=".">
   <suffix name="inbox" label="Custom histogram for Inbox"/>
+  <suffix name="search" label="Custom histogram for Search"/>
   <affected-histogram
       name="PageLoad.Clients.ServiceWorker.DocumentTiming.NavigationToDOMContentLoadedEventFired"/>
   <affected-histogram
       name="PageLoad.Clients.ServiceWorker.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram
+      name="PageLoad.Clients.ServiceWorker.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
+  <affected-histogram
+      name="PageLoad.Clients.ServiceWorker.Experimental.PaintTiming.ParseStartToFirstMeaningfulPaint"/>
+  <affected-histogram
       name="PageLoad.Clients.ServiceWorker.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram
       name="PageLoad.Clients.ServiceWorker.PaintTiming.ParseStartToFirstContentfulPaint"/>
diff --git a/tools/perf/benchmarks/system_health_smoke_test.py b/tools/perf/benchmarks/system_health_smoke_test.py
index b7c9209..2918184 100644
--- a/tools/perf/benchmarks/system_health_smoke_test.py
+++ b/tools/perf/benchmarks/system_health_smoke_test.py
@@ -98,7 +98,7 @@
       self.skipTest('Benchmark %s is disabled' % SinglePageBenchmark.Name())
 
     if self.id() in _DISABLED_TESTS:
-      self.skipTest('Test is explictly disabled')
+      self.skipTest('Test is explicitly disabled')
 
     self.assertEqual(0, SinglePageBenchmark().Run(options),
                      msg='Failed: %s' % benchmark_class)
diff --git a/tools/win/DebugVisualizers/webkit.natvis b/tools/win/DebugVisualizers/webkit.natvis
index 2d78312..8180fc7 100644
--- a/tools/win/DebugVisualizers/webkit.natvis
+++ b/tools/win/DebugVisualizers/webkit.natvis
@@ -172,7 +172,7 @@
   </Type>
   <!-- Layout: LayoutObject -->
   <Type Name="blink::LayoutObject">
-    <DisplayString Condition="bitfields_.m_IsAnonymous">Anonymous</DisplayString>
+    <DisplayString Condition="bitfields_.is_anonymous_">Anonymous</DisplayString>
     <DisplayString>{node_}</DisplayString>
   </Type>
   <Type Name="blink::LayoutObjectChildList">
@@ -213,6 +213,17 @@
   <Type Name="blink::NGBlockNode">
     <DisplayString>{layout_box_}</DisplayString>
   </Type>
+  <Type Name="blink::NGInlineNode">
+    <DisplayString>{*start_inline_}</DisplayString>
+    <Expand>
+      <Item Name="inline_node_data">*block_->ng_inline_node_data_</Item>
+      <Item Name="text_content">block_->ng_inline_node_data_->text_content_</Item>
+      <Item Name="items">block_->ng_inline_node_data_->items_</Item>
+    </Expand>
+  </Type>
+  <Type Name="blink::NGInlineItem">
+    <DisplayString>{(NGInlineItem::NGInlineItemType)type_} {start_offset_}-{end_offset_} {*layout_object_}</DisplayString>
+  </Type>
   <Type Name="blink::NGFragment">
     <DisplayString>{physical_fragment_}</DisplayString>
   </Type>
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 6dcdc91e..44af7cd 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -499,6 +499,18 @@
     VARIANT var_id, VARIANT* role) {
   AXPlatformNodeWin* target;
   COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, role, target);
+
+  // For historical reasons, we return a string (typically
+  // containing the HTML tag name) as the MSAA role, rather
+  // than a int.
+  std::string role_string = target->StringOverrideForMSAARole();
+  if (!role_string.empty()) {
+    role->vt = VT_BSTR;
+    std::wstring wsTmp(role_string.begin(), role_string.end());
+    role->bstrVal = SysAllocString(wsTmp.c_str());
+    return S_OK;
+  }
+
   role->vt = VT_I4;
   role->lVal = target->MSAARole();
   return S_OK;
@@ -1029,77 +1041,379 @@
   switch (GetData().role) {
     case ui::AX_ROLE_ALERT:
       return ROLE_SYSTEM_ALERT;
+
+    case ui::AX_ROLE_ALERT_DIALOG:
+      return ROLE_SYSTEM_DIALOG;
+
+    case ui::AX_ROLE_ANCHOR:
+      return ROLE_SYSTEM_LINK;
+
     case ui::AX_ROLE_APPLICATION:
       return ROLE_SYSTEM_APPLICATION;
-    case ui::AX_ROLE_BUTTON_DROP_DOWN:
-      return ROLE_SYSTEM_BUTTONDROPDOWN;
-    case ui::AX_ROLE_POP_UP_BUTTON:
-      return ROLE_SYSTEM_BUTTONMENU;
-    case ui::AX_ROLE_CARET:
-      return ROLE_SYSTEM_CARET;
-    case ui::AX_ROLE_CHECK_BOX:
-      return ROLE_SYSTEM_CHECKBUTTON;
-    case ui::AX_ROLE_COMBO_BOX:
-      return ROLE_SYSTEM_COMBOBOX;
-    case ui::AX_ROLE_DIALOG:
-      return ROLE_SYSTEM_DIALOG;
-    case ui::AX_ROLE_GENERIC_CONTAINER:
+
+    case ui::AX_ROLE_ARTICLE:
+      return ROLE_SYSTEM_DOCUMENT;
+
+    case ui::AX_ROLE_AUDIO:
       return ROLE_SYSTEM_GROUPING;
-    case ui::AX_ROLE_GROUP:
+
+    case ui::AX_ROLE_BANNER:
       return ROLE_SYSTEM_GROUPING;
-    case ui::AX_ROLE_IMAGE:
-      return ROLE_SYSTEM_GRAPHIC;
-    case ui::AX_ROLE_LINK:
-      return ROLE_SYSTEM_LINK;
-    case ui::AX_ROLE_LOCATION_BAR:
-      return ROLE_SYSTEM_GROUPING;
-    case ui::AX_ROLE_MENU_BAR:
-      return ROLE_SYSTEM_MENUBAR;
-    case ui::AX_ROLE_MENU_ITEM:
-      return ROLE_SYSTEM_MENUITEM;
-    case ui::AX_ROLE_MENU_LIST_POPUP:
-      return ROLE_SYSTEM_MENUPOPUP;
-    case ui::AX_ROLE_TREE:
-      return ROLE_SYSTEM_OUTLINE;
-    case ui::AX_ROLE_TREE_ITEM:
-      return ROLE_SYSTEM_OUTLINEITEM;
-    case ui::AX_ROLE_TAB:
-      return ROLE_SYSTEM_PAGETAB;
-    case ui::AX_ROLE_TAB_LIST:
-      return ROLE_SYSTEM_PAGETABLIST;
-    case ui::AX_ROLE_PANE:
-      return ROLE_SYSTEM_PANE;
-    case ui::AX_ROLE_PROGRESS_INDICATOR:
-      return ROLE_SYSTEM_PROGRESSBAR;
+
+    case ui::AX_ROLE_BUSY_INDICATOR:
+      return ROLE_SYSTEM_ANIMATION;
+
     case ui::AX_ROLE_BUTTON:
       return ROLE_SYSTEM_PUSHBUTTON;
+
+    case ui::AX_ROLE_CANVAS:
+      return ROLE_SYSTEM_GRAPHIC;
+
+    case ui::AX_ROLE_CAPTION:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_CELL:
+      return ROLE_SYSTEM_CELL;
+
+    case ui::AX_ROLE_CHECK_BOX:
+      return ROLE_SYSTEM_CHECKBUTTON;
+
+    case ui::AX_ROLE_COLOR_WELL:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_COLUMN:
+      return ROLE_SYSTEM_COLUMN;
+
+    case ui::AX_ROLE_COLUMN_HEADER:
+      return ROLE_SYSTEM_COLUMNHEADER;
+
+    case ui::AX_ROLE_COMBO_BOX:
+      return ROLE_SYSTEM_COMBOBOX;
+
+    case ui::AX_ROLE_COMPLEMENTARY:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_CONTENT_INFO:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_DATE:
+    case ui::AX_ROLE_DATE_TIME:
+      return ROLE_SYSTEM_DROPLIST;
+
+    case ui::AX_ROLE_DESCRIPTION_LIST_DETAIL:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_DESCRIPTION_LIST:
+      return ROLE_SYSTEM_LIST;
+
+    case ui::AX_ROLE_DESCRIPTION_LIST_TERM:
+      return ROLE_SYSTEM_LISTITEM;
+
+    case ui::AX_ROLE_DETAILS:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_DIALOG:
+      return ROLE_SYSTEM_DIALOG;
+
+    case ui::AX_ROLE_DISCLOSURE_TRIANGLE:
+      return ROLE_SYSTEM_PUSHBUTTON;
+
+    case ui::AX_ROLE_DOCUMENT:
+    case ui::AX_ROLE_ROOT_WEB_AREA:
+    case ui::AX_ROLE_WEB_AREA:
+      return ROLE_SYSTEM_DOCUMENT;
+
+    case ui::AX_ROLE_EMBEDDED_OBJECT:
+      if (delegate_->GetChildCount()) {
+        return ROLE_SYSTEM_GROUPING;
+      } else {
+        return ROLE_SYSTEM_CLIENT;
+      }
+
+    case ui::AX_ROLE_FIGURE:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_FEED:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_GENERIC_CONTAINER:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_GRID:
+      return ROLE_SYSTEM_TABLE;
+
+    case ui::AX_ROLE_GROUP:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_HEADING:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_IFRAME:
+      return ROLE_SYSTEM_DOCUMENT;
+
+    case ui::AX_ROLE_IFRAME_PRESENTATIONAL:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_IMAGE:
+      return ROLE_SYSTEM_GRAPHIC;
+
+    case ui::AX_ROLE_IMAGE_MAP_LINK:
+      return ROLE_SYSTEM_LINK;
+
+    case ui::AX_ROLE_INPUT_TIME:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_LABEL_TEXT:
+    case ui::AX_ROLE_LEGEND:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_LINK:
+      return ROLE_SYSTEM_LINK;
+
+    case ui::AX_ROLE_LIST:
+      return ROLE_SYSTEM_LIST;
+
+    case ui::AX_ROLE_LIST_BOX:
+      return ROLE_SYSTEM_LIST;
+
+    case ui::AX_ROLE_LIST_BOX_OPTION:
+      return ROLE_SYSTEM_LISTITEM;
+
+    case ui::AX_ROLE_LIST_ITEM:
+      return ROLE_SYSTEM_LISTITEM;
+
+    case ui::AX_ROLE_MAIN:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_MARK:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_MARQUEE:
+      return ROLE_SYSTEM_ANIMATION;
+
+    case ui::AX_ROLE_MATH:
+      return ROLE_SYSTEM_EQUATION;
+
+    case ui::AX_ROLE_MENU:
+    case ui::AX_ROLE_MENU_BUTTON:
+      return ROLE_SYSTEM_MENUPOPUP;
+
+    case ui::AX_ROLE_MENU_BAR:
+      return ROLE_SYSTEM_MENUBAR;
+
+    case ui::AX_ROLE_MENU_ITEM:
+      return ROLE_SYSTEM_MENUITEM;
+
+    case ui::AX_ROLE_MENU_ITEM_CHECK_BOX:
+      return ROLE_SYSTEM_MENUITEM;
+
+    case ui::AX_ROLE_MENU_ITEM_RADIO:
+      return ROLE_SYSTEM_MENUITEM;
+
+    case ui::AX_ROLE_MENU_LIST_POPUP:
+      return ROLE_SYSTEM_LIST;
+
+    case ui::AX_ROLE_MENU_LIST_OPTION:
+      return ROLE_SYSTEM_LISTITEM;
+
+    case ui::AX_ROLE_NAVIGATION:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_NOTE:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_OUTLINE:
+      return ROLE_SYSTEM_OUTLINE;
+
+    case ui::AX_ROLE_POP_UP_BUTTON: {
+      std::string html_tag = GetData().GetStringAttribute(ui::AX_ATTR_HTML_TAG);
+      if (html_tag == "select")
+        return ROLE_SYSTEM_COMBOBOX;
+      return ROLE_SYSTEM_BUTTONMENU;
+    }
+    case ui::AX_ROLE_PRE:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_PROGRESS_INDICATOR:
+      return ROLE_SYSTEM_PROGRESSBAR;
+
     case ui::AX_ROLE_RADIO_BUTTON:
       return ROLE_SYSTEM_RADIOBUTTON;
+
+    case ui::AX_ROLE_RADIO_GROUP:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_REGION: {
+      std::string html_tag = GetData().GetStringAttribute(ui::AX_ATTR_HTML_TAG);
+      if (html_tag == "section")
+        return ROLE_SYSTEM_GROUPING;
+      return ROLE_SYSTEM_PANE;
+    }
+
+    case ui::AX_ROLE_ROW: {
+      // Role changes depending on whether row is inside a treegrid
+      // https://www.w3.org/TR/core-aam-1.1/#role-map-row
+      auto* container = FromNativeViewAccessible(GetParent());
+      if (container && container->GetData().role == ui::AX_ROLE_GROUP) {
+        // If parent was a rowgroup, we need to look at the grandparent
+        container = FromNativeViewAccessible(container->GetParent());
+      }
+
+      if (!container)
+        return ROLE_SYSTEM_ROW;
+
+      return ROLE_SYSTEM_OUTLINEITEM;
+    }
+
+    case ui::AX_ROLE_ROW_HEADER:
+      return ROLE_SYSTEM_ROWHEADER;
+
+    case ui::AX_ROLE_RUBY:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_RULER:
+      return ROLE_SYSTEM_CLIENT;
+
+    case ui::AX_ROLE_SCROLL_AREA:
+      return ROLE_SYSTEM_CLIENT;
+
     case ui::AX_ROLE_SCROLL_BAR:
       return ROLE_SYSTEM_SCROLLBAR;
-    case ui::AX_ROLE_SPLITTER:
-      return ROLE_SYSTEM_SEPARATOR;
+
+    case ui::AX_ROLE_SEARCH:
+      return ROLE_SYSTEM_GROUPING;
+
     case ui::AX_ROLE_SLIDER:
       return ROLE_SYSTEM_SLIDER;
+
+    case ui::AX_ROLE_SPIN_BUTTON:
+      return ROLE_SYSTEM_SPINBUTTON;
+
+    case ui::AX_ROLE_SPIN_BUTTON_PART:
+      return ROLE_SYSTEM_PUSHBUTTON;
+
+    case ui::AX_ROLE_ANNOTATION:
+    case ui::AX_ROLE_LIST_MARKER:
     case ui::AX_ROLE_STATIC_TEXT:
       return ROLE_SYSTEM_STATICTEXT;
+
+    case ui::AX_ROLE_STATUS:
+      return ROLE_SYSTEM_STATUSBAR;
+
+    case ui::AX_ROLE_SPLITTER:
+      return ROLE_SYSTEM_SEPARATOR;
+
+    case ui::AX_ROLE_SVG_ROOT:
+      return ROLE_SYSTEM_GRAPHIC;
+
+    case ui::AX_ROLE_TAB:
+      return ROLE_SYSTEM_PAGETAB;
+
+    case ui::AX_ROLE_TABLE:
+      return ROLE_SYSTEM_TABLE;
+
+    case ui::AX_ROLE_TABLE_HEADER_CONTAINER:
+      return ROLE_SYSTEM_GROUPING;
+
+    case ui::AX_ROLE_TAB_LIST:
+      return ROLE_SYSTEM_PAGETABLIST;
+
+    case ui::AX_ROLE_TAB_PANEL:
+      return ROLE_SYSTEM_PROPERTYPAGE;
+
+    case ui::AX_ROLE_TERM:
+      return ROLE_SYSTEM_LISTITEM;
+
+    case ui::AX_ROLE_TOGGLE_BUTTON:
+      return ROLE_SYSTEM_PUSHBUTTON;
+
     case ui::AX_ROLE_TEXT_FIELD:
+    case ui::AX_ROLE_SEARCH_BOX:
       return ROLE_SYSTEM_TEXT;
-    case ui::AX_ROLE_TITLE_BAR:
-      return ROLE_SYSTEM_TITLEBAR;
+
+    case ui::AX_ROLE_ABBR:
+    case ui::AX_ROLE_TIME:
+      return ROLE_SYSTEM_TEXT;
+
+    case ui::AX_ROLE_TIMER:
+      return ROLE_SYSTEM_CLOCK;
+
     case ui::AX_ROLE_TOOLBAR:
       return ROLE_SYSTEM_TOOLBAR;
-    case ui::AX_ROLE_WEB_VIEW:
+
+    case ui::AX_ROLE_TOOLTIP:
+      return ROLE_SYSTEM_TOOLTIP;
+
+    case ui::AX_ROLE_TREE:
+      return ROLE_SYSTEM_OUTLINE;
+
+    case ui::AX_ROLE_TREE_GRID:
+      return ROLE_SYSTEM_OUTLINE;
+
+    case ui::AX_ROLE_TREE_ITEM:
+      return ROLE_SYSTEM_OUTLINEITEM;
+
+    case ui::AX_ROLE_LINE_BREAK:
+      return ROLE_SYSTEM_WHITESPACE;
+
+    case ui::AX_ROLE_VIDEO:
       return ROLE_SYSTEM_GROUPING;
+
     case ui::AX_ROLE_WINDOW:
       return ROLE_SYSTEM_WINDOW;
-    case ui::AX_ROLE_CLIENT:
+
+    // TODO(dmazzoni): figure out the proper MSAA role for roles not called out
+    // here.
     default:
-      // This is the default role for MSAA.
       return ROLE_SYSTEM_CLIENT;
   }
 }
 
+std::string AXPlatformNodeWin::StringOverrideForMSAARole() {
+  std::string html_tag = GetData().GetStringAttribute(ui::AX_ATTR_HTML_TAG);
+
+  switch (GetData().role) {
+    case ui::AX_ROLE_BLOCKQUOTE:
+    case ui::AX_ROLE_DEFINITION:
+    case ui::AX_ROLE_IMAGE_MAP:
+      return html_tag;
+
+    case ui::AX_ROLE_CANVAS:
+      if (GetData().GetBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK)) {
+        // TODO(dougt) why not just use the html_tag?
+        return "canvas";
+      }
+      break;
+
+    case ui::AX_ROLE_FORM:
+      // TODO(dougt) why not just use the html_tag?
+      return "form";
+
+    case ui::AX_ROLE_HEADING:
+      if (!html_tag.empty())
+        return html_tag;
+      break;
+
+    case ui::AX_ROLE_PARAGRAPH:
+      // TODO(dougt) why not just use the html_tag and why upper case?
+      return "P";
+
+    case ui::AX_ROLE_GENERIC_CONTAINER:
+      // TODO(dougt) why can't we always use div in this case?
+      if (html_tag.empty())
+        return "div";
+      return html_tag;
+
+    case ui::AX_ROLE_SWITCH:
+      return "switch";
+
+    default:
+      return "";
+  }
+
+  return "";
+}
+
 int AXPlatformNodeWin::MSAAState() {
   const AXNodeData& data = GetData();
   const uint32_t state = data.state;
diff --git a/ui/accessibility/platform/ax_platform_node_win.h b/ui/accessibility/platform/ax_platform_node_win.h
index 8120b5e..645e686b 100644
--- a/ui/accessibility/platform/ax_platform_node_win.h
+++ b/ui/accessibility/platform/ax_platform_node_win.h
@@ -274,6 +274,8 @@
 
  private:
   int MSAARole();
+  std::string StringOverrideForMSAARole();
+
   int MSAAState();
   int MSAAEvent(ui::AXEvent event);
 
diff --git a/ui/app_list/BUILD.gn b/ui/app_list/BUILD.gn
index dd99897..ce45400 100644
--- a/ui/app_list/BUILD.gn
+++ b/ui/app_list/BUILD.gn
@@ -140,6 +140,8 @@
       "views/search_result_actions_view.cc",
       "views/search_result_actions_view.h",
       "views/search_result_actions_view_delegate.h",
+      "views/search_result_answer_card_view.cc",
+      "views/search_result_answer_card_view.h",
       "views/search_result_container_view.cc",
       "views/search_result_container_view.h",
       "views/search_result_list_view.cc",
diff --git a/ui/app_list/views/app_list_item_view.cc b/ui/app_list/views/app_list_item_view.cc
index 8df2df4..488aef3 100644
--- a/ui/app_list/views/app_list_item_view.cc
+++ b/ui/app_list/views/app_list_item_view.cc
@@ -227,58 +227,6 @@
   progress_bar_->SetValue(percent_downloaded / 100.0);
 }
 
-const char* AppListItemView::GetClassName() const {
-  return kViewClassName;
-}
-
-void AppListItemView::Layout() {
-  gfx::Rect rect(GetContentsBounds());
-
-  const int left_right_padding =
-      title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
-  rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
-  const int y = rect.y();
-
-  icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
-
-  const gfx::Size title_size = title_->GetPreferredSize();
-  gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
-                         y + kGridIconDimension + kIconTitleSpacing,
-                         title_size.width(),
-                         title_size.height());
-  title_bounds.Intersect(rect);
-  title_->SetBoundsRect(title_bounds);
-  SetTitleSubpixelAA();
-
-  gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
-  progress_bar_bounds.set_x(
-      (GetContentsBounds().width() - progress_bar_bounds.width()) / 2);
-  progress_bar_bounds.set_y(title_bounds.y());
-  progress_bar_->SetBoundsRect(progress_bar_bounds);
-}
-
-void AppListItemView::OnPaint(gfx::Canvas* canvas) {
-  if (apps_grid_view_->IsDraggedView(this))
-    return;
-
-  gfx::Rect rect(GetContentsBounds());
-  if (apps_grid_view_->IsSelectedView(this))
-    canvas->FillRect(rect, kSelectedColor);
-
-  if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
-    DCHECK(apps_grid_view_->model()->folders_enabled());
-
-    // Draw folder dropping preview circle.
-    gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
-                                   icon_->y() + icon_->size().height() / 2);
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setAntiAlias(true);
-    flags.setColor(kFolderBubbleColor);
-    canvas->DrawCircle(center, kFolderPreviewRadius, flags);
-  }
-}
-
 void AppListItemView::ShowContextMenuForView(views::View* source,
                                              const gfx::Point& point,
                                              ui::MenuSourceType source_type) {
@@ -321,6 +269,28 @@
   return views::CustomButton::ShouldEnterPushedState(event);
 }
 
+void AppListItemView::PaintButtonContents(gfx::Canvas* canvas) {
+  if (apps_grid_view_->IsDraggedView(this))
+    return;
+
+  gfx::Rect rect(GetContentsBounds());
+  if (apps_grid_view_->IsSelectedView(this))
+    canvas->FillRect(rect, kSelectedColor);
+
+  if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
+    DCHECK(apps_grid_view_->model()->folders_enabled());
+
+    // Draw folder dropping preview circle.
+    gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
+                                   icon_->y() + icon_->size().height() / 2);
+    cc::PaintFlags flags;
+    flags.setStyle(cc::PaintFlags::kFill_Style);
+    flags.setAntiAlias(true);
+    flags.setColor(kFolderBubbleColor);
+    canvas->DrawCircle(center, kFolderPreviewRadius, flags);
+  }
+}
+
 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
   CustomButton::OnMousePressed(event);
 
@@ -337,6 +307,35 @@
   return true;
 }
 
+const char* AppListItemView::GetClassName() const {
+  return kViewClassName;
+}
+
+void AppListItemView::Layout() {
+  gfx::Rect rect(GetContentsBounds());
+
+  const int left_right_padding =
+      title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
+  rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
+  const int y = rect.y();
+
+  icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
+
+  const gfx::Size title_size = title_->GetPreferredSize();
+  gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
+                         y + kGridIconDimension + kIconTitleSpacing,
+                         title_size.width(), title_size.height());
+  title_bounds.Intersect(rect);
+  title_->SetBoundsRect(title_bounds);
+  SetTitleSubpixelAA();
+
+  gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
+  progress_bar_bounds.set_x(
+      (GetContentsBounds().width() - progress_bar_bounds.width()) / 2);
+  progress_bar_bounds.set_y(title_bounds.y());
+  progress_bar_->SetBoundsRect(progress_bar_bounds);
+}
+
 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
   // Disable space key to press the button. The keyboard events received
   // by this view are forwarded from a Textfield (SearchBoxView) and key
diff --git a/ui/app_list/views/app_list_item_view.h b/ui/app_list/views/app_list_item_view.h
index 191f182..9670438 100644
--- a/ui/app_list/views/app_list_item_view.h
+++ b/ui/app_list/views/app_list_item_view.h
@@ -114,11 +114,6 @@
   // Invoked when |mouse_drag_timer_| fires to show dragging UI.
   void OnMouseDragTimer();
 
-  // views::View overrides:
-  const char* GetClassName() const override;
-  void Layout() override;
-  void OnPaint(gfx::Canvas* canvas) override;
-
   // views::ContextMenuController overrides:
   void ShowContextMenuForView(views::View* source,
                               const gfx::Point& point,
@@ -127,8 +122,11 @@
   // views::CustomButton overrides:
   void StateChanged(ButtonState old_state) override;
   bool ShouldEnterPushedState(const ui::Event& event) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // views::View overrides:
+  const char* GetClassName() const override;
+  void Layout() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
diff --git a/ui/app_list/views/contents_view.cc b/ui/app_list/views/contents_view.cc
index aad1125..bb8218b 100644
--- a/ui/app_list/views/contents_view.cc
+++ b/ui/app_list/views/contents_view.cc
@@ -17,56 +17,23 @@
 #include "ui/app_list/views/apps_grid_view.h"
 #include "ui/app_list/views/custom_launcher_page_view.h"
 #include "ui/app_list/views/search_box_view.h"
+#include "ui/app_list/views/search_result_answer_card_view.h"
 #include "ui/app_list/views/search_result_list_view.h"
 #include "ui/app_list/views/search_result_page_view.h"
 #include "ui/app_list/views/search_result_tile_item_list_view.h"
 #include "ui/app_list/views/start_page_view.h"
 #include "ui/events/event.h"
-#include "ui/views/layout/box_layout.h"
 #include "ui/views/view_model.h"
 #include "ui/views/widget/widget.h"
 
 namespace app_list {
 
-namespace {
-
-// Container of the search answer view.
-class SearchAnswerContainerView : public views::View {
- public:
-  explicit SearchAnswerContainerView(views::View* search_results_page_view)
-      : search_results_page_view_(search_results_page_view) {
-    views::BoxLayout* answer_container_layout =
-        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
-    answer_container_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
-    SetLayoutManager(answer_container_layout);
-  }
-
-  // views::View overrides:
-  void ChildPreferredSizeChanged(View* child) override {
-    if (visible())
-      search_results_page_view_->Layout();
-  }
-
-  const char* GetClassName() const override {
-    return "SearchAnswerContainerView";
-  }
-
- private:
-  views::View* const search_results_page_view_;
-
-  DISALLOW_COPY_AND_ASSIGN(SearchAnswerContainerView);
-};
-
-}  // namespace
-
 ContentsView::ContentsView(AppListMainView* app_list_main_view)
     : model_(nullptr),
       apps_container_view_(nullptr),
       search_results_page_view_(nullptr),
       start_page_view_(nullptr),
       custom_page_view_(nullptr),
-      search_answer_container_view_(nullptr),
       app_list_main_view_(app_list_main_view),
       page_before_search_(0) {
   pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
@@ -76,8 +43,6 @@
 
 ContentsView::~ContentsView() {
   pagination_model_.RemoveObserver(this);
-  if (model_)
-    model_->RemoveObserver(this);
 }
 
 void ContentsView::Init(AppListModel* model) {
@@ -104,14 +69,14 @@
   // Search results UI.
   search_results_page_view_ = new SearchResultPageView();
 
-  // Search answer container UI.
-  search_answer_container_view_ =
-      new SearchAnswerContainerView(search_results_page_view_);
-  search_answer_container_view_->SetVisible(false);
-  views::View* search_answer_view = view_delegate->GetSearchAnswerWebView();
-  if (search_answer_view)
-    search_answer_container_view_->AddChildView(search_answer_view);
-  search_results_page_view_->AddChildView(search_answer_container_view_);
+  // Search result containers.
+  views::View* const search_answer_view =
+      view_delegate->GetSearchAnswerWebView();
+  if (search_answer_view) {
+    search_results_page_view_->AddSearchResultContainerView(
+        nullptr, new SearchResultAnswerCardView(
+                     model_, search_results_page_view_, search_answer_view));
+  }
 
   AppListModel::SearchResults* results = view_delegate->GetModel()->results();
   search_results_page_view_->AddSearchResultContainerView(
@@ -142,8 +107,6 @@
   pagination_model_.SelectPage(initial_page_index, false);
 
   ActivePageChanged();
-
-  model_->AddObserver(this);
 }
 
 void ContentsView::CancelDrag() {
@@ -524,12 +487,4 @@
   UpdatePageBounds();
 }
 
-void ContentsView::OnSearchAnswerAvailableChanged(bool has_answer) {
-  if (has_answer == search_answer_container_view_->visible())
-    return;
-
-  search_answer_container_view_->SetVisible(has_answer);
-  search_results_page_view_->Layout();
-}
-
 }  // namespace app_list
diff --git a/ui/app_list/views/contents_view.h b/ui/app_list/views/contents_view.h
index 435ff00..62a52cd2 100644
--- a/ui/app_list/views/contents_view.h
+++ b/ui/app_list/views/contents_view.h
@@ -13,7 +13,6 @@
 #include "base/macros.h"
 #include "ui/app_list/app_list_export.h"
 #include "ui/app_list/app_list_model.h"
-#include "ui/app_list/app_list_model_observer.h"
 #include "ui/app_list/pagination_model.h"
 #include "ui/app_list/pagination_model_observer.h"
 #include "ui/views/view.h"
@@ -43,8 +42,7 @@
 // interface for switching between launcher pages, and animates the transition
 // between them.
 class APP_LIST_EXPORT ContentsView : public views::View,
-                                     public PaginationModelObserver,
-                                     public AppListModelObserver {
+                                     public PaginationModelObserver {
  public:
   explicit ContentsView(AppListMainView* app_list_main_view);
   ~ContentsView() override;
@@ -136,9 +134,6 @@
   void TransitionStarted() override;
   void TransitionChanged() override;
 
-  // Overridden from AppListModelObserver:
-  void OnSearchAnswerAvailableChanged(bool has_answer) override;
-
  private:
   // Sets the active launcher page, accounting for whether the change is for
   // search results.
@@ -190,10 +185,6 @@
   StartPageView* start_page_view_;
   CustomLauncherPageView* custom_page_view_;
 
-  // Unowned pointer to the container of the search answer web view. This
-  // container view is a sub-view of search_results_page_view_.
-  View* search_answer_container_view_;
-
   // The child page views. Owned by the views hierarchy.
   std::vector<AppListPage*> app_list_pages_;
 
diff --git a/ui/app_list/views/page_switcher.cc b/ui/app_list/views/page_switcher.cc
index eb08075..4f9a649a 100644
--- a/ui/app_list/views/page_switcher.cc
+++ b/ui/app_list/views/page_switcher.cc
@@ -54,14 +54,13 @@
     return gfx::Size(button_width_, kButtonHeight);
   }
 
-  void OnPaint(gfx::Canvas* canvas) override {
+  void PaintButtonContents(gfx::Canvas* canvas) override {
     if (state() == STATE_HOVERED)
       PaintButton(canvas, kPagerHoverColor);
     else
       PaintButton(canvas, kPagerNormalColor);
   }
 
- private:
   void OnGestureEvent(ui::GestureEvent* event) override {
     CustomButton::OnGestureEvent(event);
 
@@ -73,6 +72,7 @@
     SchedulePaint();
   }
 
+ private:
   // Paints a button that has two rounded corner at bottom.
   void PaintButton(gfx::Canvas* canvas, SkColor base_color) {
     gfx::Rect rect(GetContentsBounds());
diff --git a/ui/app_list/views/search_result_answer_card_view.cc b/ui/app_list/views/search_result_answer_card_view.cc
new file mode 100644
index 0000000..89078c0
--- /dev/null
+++ b/ui/app_list/views/search_result_answer_card_view.cc
@@ -0,0 +1,144 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/app_list/views/search_result_answer_card_view.h"
+
+#include "ui/app_list/app_list_constants.h"
+#include "ui/app_list/app_list_features.h"
+#include "ui/app_list/views/search_result_page_view.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace app_list {
+
+namespace {
+
+// Answer card relevance is high to always have it first.
+constexpr double kSearchAnswerCardRelevance = 100;
+
+// Container of the search answer view.
+class SearchAnswerContainerView : public views::CustomButton {
+ public:
+  explicit SearchAnswerContainerView(views::View* search_results_page_view)
+      : CustomButton(nullptr),
+        search_results_page_view_(search_results_page_view) {
+    // Center the card horizontally in the container.
+    views::BoxLayout* answer_container_layout =
+        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
+    answer_container_layout->set_main_axis_alignment(
+        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+    SetLayoutManager(answer_container_layout);
+  }
+
+  void SetSelected(bool selected) {
+    if (selected == selected_)
+      return;
+    selected_ = selected;
+    UpdateBackgroundColor();
+  }
+
+  // views::CustomButton overrides:
+  void ChildPreferredSizeChanged(View* child) override {
+    // Card size changed.
+    if (visible())
+      search_results_page_view_->Layout();
+  }
+
+  int GetHeightForWidth(int w) const override {
+    return visible() ? CustomButton::GetHeightForWidth(w) : 0;
+  }
+
+  const char* GetClassName() const override {
+    return "SearchAnswerContainerView";
+  }
+
+  void StateChanged(ButtonState old_state) override { UpdateBackgroundColor(); }
+
+ private:
+  void UpdateBackgroundColor() {
+    views::Background* background = nullptr;
+
+    if (selected_) {
+      background = views::Background::CreateSolidBackground(kSelectedColor);
+    } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
+      background = views::Background::CreateSolidBackground(kHighlightedColor);
+    }
+
+    set_background(background);
+    SchedulePaint();
+  }
+
+  views::View* const search_results_page_view_;
+  bool selected_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(SearchAnswerContainerView);
+};
+
+}  // namespace
+
+SearchResultAnswerCardView::SearchResultAnswerCardView(
+    AppListModel* model,
+    SearchResultPageView* search_results_page_view,
+    views::View* search_answer_view)
+    : model_(model),
+      search_answer_container_view_(
+          new SearchAnswerContainerView(search_results_page_view)) {
+  search_answer_container_view_->SetVisible(false);
+  search_answer_container_view_->AddChildView(search_answer_view);
+  AddChildView(search_answer_container_view_);
+  model->AddObserver(this);
+  SetLayoutManager(new views::FillLayout);
+}
+
+SearchResultAnswerCardView::~SearchResultAnswerCardView() {
+  model_->RemoveObserver(this);
+}
+
+const char* SearchResultAnswerCardView::GetClassName() const {
+  return "SearchResultAnswerCardView";
+}
+
+void SearchResultAnswerCardView::OnContainerSelected(
+    bool from_bottom,
+    bool directional_movement) {
+  if (num_results() == 0)
+    return;
+
+  SetSelectedIndex(0);
+}
+
+int SearchResultAnswerCardView::GetYSize() {
+  return num_results();
+}
+
+int SearchResultAnswerCardView::DoUpdate() {
+  const bool have_result = search_answer_container_view_->visible();
+  set_container_score(have_result ? kSearchAnswerCardRelevance : 0);
+  return have_result ? 1 : 0;
+}
+
+void SearchResultAnswerCardView::UpdateSelectedIndex(int old_selected,
+                                                     int new_selected) {
+  if (new_selected == old_selected)
+    return;
+
+  const bool is_selected = new_selected == 0;
+  search_answer_container_view_->SetSelected(is_selected);
+  if (is_selected)
+    NotifyAccessibilityEvent(ui::AX_EVENT_SELECTION, true);
+}
+
+void SearchResultAnswerCardView::OnSearchAnswerAvailableChanged(
+    bool has_answer) {
+  const bool visible = has_answer && !features::IsAnswerCardDarkRunEnabled();
+  if (visible == search_answer_container_view_->visible())
+    return;
+
+  search_answer_container_view_->SetVisible(visible);
+  ScheduleUpdate();
+}
+
+}  // namespace app_list
diff --git a/ui/app_list/views/search_result_answer_card_view.h b/ui/app_list/views/search_result_answer_card_view.h
new file mode 100644
index 0000000..8c985f0
--- /dev/null
+++ b/ui/app_list/views/search_result_answer_card_view.h
@@ -0,0 +1,57 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_APP_LIST_VIEWS_SEARCH_RESULT_ANSWER_CARD_VIEW_H_
+#define UI_APP_LIST_VIEWS_SEARCH_RESULT_ANSWER_CARD_VIEW_H_
+
+#include "ui/app_list/app_list_model_observer.h"
+#include "ui/app_list/views/search_result_container_view.h"
+
+namespace app_list {
+
+class AppListModel;
+class SearchResultPageView;
+
+namespace {
+class SearchAnswerContainerView;
+}
+
+// Result container for the search answer card.
+class APP_LIST_EXPORT SearchResultAnswerCardView
+    : public SearchResultContainerView,
+      public AppListModelObserver {
+ public:
+  SearchResultAnswerCardView(AppListModel* model,
+                             SearchResultPageView* search_results_page_view,
+                             views::View* search_answer_view);
+  ~SearchResultAnswerCardView() override;
+
+ private:
+  // Overridden from views::View:
+  const char* GetClassName() const override;
+
+  // Overridden from SearchResultContainerView:
+  void OnContainerSelected(bool from_bottom,
+                           bool directional_movement) override;
+  void NotifyFirstResultYIndex(int y_index) override {}
+  int GetYSize() override;
+  int DoUpdate() override;
+  void UpdateSelectedIndex(int old_selected, int new_selected) override;
+
+  // Overridden from AppListModelObserver
+  void OnSearchAnswerAvailableChanged(bool has_answer) override;
+
+  // Unowned pointer to application list model.
+  AppListModel* const model_;
+
+  // Pointer to the container of the search answer; owned by the view hierarchy.
+  // It's visible iff we have a search answer result.
+  SearchAnswerContainerView* const search_answer_container_view_;
+
+  DISALLOW_COPY_AND_ASSIGN(SearchResultAnswerCardView);
+};
+
+}  // namespace app_list
+
+#endif  // UI_APP_LIST_VIEWS_SEARCH_RESULT_ANSWER_CARD_VIEW_H_
diff --git a/ui/app_list/views/search_result_container_view.h b/ui/app_list/views/search_result_container_view.h
index b5f8496..19c02c13 100644
--- a/ui/app_list/views/search_result_container_view.h
+++ b/ui/app_list/views/search_result_container_view.h
@@ -84,11 +84,12 @@
   virtual void OnContainerSelected(bool from_bottom,
                                    bool directional_movement) = 0;
 
- private:
+ protected:
   // Schedules an Update call using |update_factory_|. Do nothing if there is a
   // pending call.
   void ScheduleUpdate();
 
+ private:
   // Updates UI with model. Returns the number of visible results.
   virtual int DoUpdate() = 0;
 
diff --git a/ui/app_list/views/search_result_view.cc b/ui/app_list/views/search_result_view.cc
index 678579d..320585d 100644
--- a/ui/app_list/views/search_result_view.cc
+++ b/ui/app_list/views/search_result_view.cc
@@ -240,7 +240,7 @@
   Layout();
 }
 
-void SearchResultView::OnPaint(gfx::Canvas* canvas) {
+void SearchResultView::PaintButtonContents(gfx::Canvas* canvas) {
   gfx::Rect rect(GetContentsBounds());
   if (rect.IsEmpty())
     return;
diff --git a/ui/app_list/views/search_result_view.h b/ui/app_list/views/search_result_view.h
index c9f697c9..dee91d4 100644
--- a/ui/app_list/views/search_result_view.h
+++ b/ui/app_list/views/search_result_view.h
@@ -80,7 +80,7 @@
   void Layout() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void ChildPreferredSizeChanged(views::View* child) override;
-  void OnPaint(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // views::ButtonListener overrides:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
diff --git a/ui/aura/mus/client_surface_embedder.cc b/ui/aura/mus/client_surface_embedder.cc
index 94ad333..4458206 100644
--- a/ui/aura/mus/client_surface_embedder.cc
+++ b/ui/aura/mus/client_surface_embedder.cc
@@ -6,6 +6,7 @@
 
 #include "cc/surfaces/surface_reference_factory.h"
 #include "ui/aura/window.h"
+#include "ui/gfx/geometry/dip_util.h"
 
 namespace aura {
 namespace {
@@ -65,22 +66,26 @@
 
 void ClientSurfaceEmbedder::UpdateSizeAndGutters() {
   surface_layer_->SetBounds(gfx::Rect(window_->bounds().size()));
-  // TODO(fsamuel): Fix this for high DPI.
-  gfx::Size fallback_surface_size(
-      surface_layer_->GetFallbackSurfaceInfo()
-          ? surface_layer_->GetFallbackSurfaceInfo()->size_in_pixels()
-          : gfx::Size());
+  gfx::Size fallback_surface_size_in_dip;
+  const cc::SurfaceInfo* fallback_surface_info =
+      surface_layer_->GetFallbackSurfaceInfo();
+  if (fallback_surface_info) {
+    float fallback_device_scale_factor =
+        fallback_surface_info->device_scale_factor();
+    fallback_surface_size_in_dip = gfx::ConvertSizeToDIP(
+        fallback_device_scale_factor, fallback_surface_info->size_in_pixels());
+  }
   gfx::Rect window_bounds(window_->bounds());
-  if (fallback_surface_size.width() < window_bounds.width()) {
+  if (fallback_surface_size_in_dip.width() < window_bounds.width()) {
     right_gutter_ = base::MakeUnique<ui::Layer>(ui::LAYER_SOLID_COLOR);
     // TODO(fsamuel): Use the embedded client's background color.
     right_gutter_->SetColor(SK_ColorWHITE);
-    int width = window_bounds.width() - fallback_surface_size.width();
+    int width = window_bounds.width() - fallback_surface_size_in_dip.width();
     // The right gutter also includes the bottom-right corner, if necessary.
     int height = window_bounds.height() - client_area_insets_.height();
-    right_gutter_->SetBounds(
-        gfx::Rect(client_area_insets_.left() + fallback_surface_size.width(),
-                  client_area_insets_.top(), width, height));
+    right_gutter_->SetBounds(gfx::Rect(
+        client_area_insets_.left() + fallback_surface_size_in_dip.width(),
+        client_area_insets_.top(), width, height));
     window_->layer()->Add(right_gutter_.get());
   } else {
     right_gutter_.reset();
@@ -88,15 +93,15 @@
 
   // Only create a bottom gutter if a fallback surface is available. Otherwise,
   // the right gutter will fill the whole window until a fallback is available.
-  if (!fallback_surface_size.IsEmpty() &&
-      fallback_surface_size.height() < window_bounds.height()) {
+  if (!fallback_surface_size_in_dip.IsEmpty() &&
+      fallback_surface_size_in_dip.height() < window_bounds.height()) {
     bottom_gutter_ = base::MakeUnique<ui::Layer>(ui::LAYER_SOLID_COLOR);
     // TODO(fsamuel): Use the embedded client's background color.
     bottom_gutter_->SetColor(SK_ColorWHITE);
-    int width = fallback_surface_size.width();
-    int height = window_bounds.height() - fallback_surface_size.height();
+    int width = fallback_surface_size_in_dip.width();
+    int height = window_bounds.height() - fallback_surface_size_in_dip.height();
     bottom_gutter_->SetBounds(
-        gfx::Rect(0, fallback_surface_size.height(), width, height));
+        gfx::Rect(0, fallback_surface_size_in_dip.height(), width, height));
     window_->layer()->Add(bottom_gutter_.get());
   } else {
     bottom_gutter_.reset();
diff --git a/ui/aura/mus/window_port_mus.cc b/ui/aura/mus/window_port_mus.cc
index 7f2a083..e550ddb 100644
--- a/ui/aura/mus/window_port_mus.cc
+++ b/ui/aura/mus/window_port_mus.cc
@@ -17,6 +17,7 @@
 #include "ui/base/class_property.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/geometry/dip_util.h"
 
 namespace aura {
 
@@ -230,7 +231,9 @@
   ServerChangeData data;
   data.bounds_in_dip = bounds;
   ScopedServerChange change(this, ServerChangeType::BOUNDS, data);
-  last_surface_size_ = bounds.size();
+  float device_scale_factor = ScaleFactorForDisplay(window_);
+  last_surface_size_in_pixels_ =
+      gfx::ConvertSizeToPixel(device_scale_factor, bounds.size());
   if (local_surface_id)
     local_surface_id_ = *local_surface_id;
   else
@@ -284,12 +287,14 @@
 }
 
 const cc::LocalSurfaceId& WindowPortMus::GetOrAllocateLocalSurfaceId(
-    const gfx::Size& surface_size) {
-  if (last_surface_size_ == surface_size && local_surface_id_.is_valid())
+    const gfx::Size& surface_size_in_pixels) {
+  if (last_surface_size_in_pixels_ == surface_size_in_pixels &&
+      local_surface_id_.is_valid()) {
     return local_surface_id_;
+  }
 
   local_surface_id_ = local_surface_id_allocator_.GenerateId();
-  last_surface_size_ = surface_size;
+  last_surface_size_in_pixels_ = surface_size_in_pixels;
 
   // If the FrameSinkId is available, then immediately embed the SurfaceId.
   // The newly generated frame by the embedder will block in the display
@@ -533,9 +538,9 @@
   if (!frame_sink_id_.is_valid() || !local_surface_id_.is_valid())
     return;
 
-  SetPrimarySurfaceInfo(
-      cc::SurfaceInfo(cc::SurfaceId(frame_sink_id_, local_surface_id_),
-                      ScaleFactorForDisplay(window_), last_surface_size_));
+  SetPrimarySurfaceInfo(cc::SurfaceInfo(
+      cc::SurfaceId(frame_sink_id_, local_surface_id_),
+      ScaleFactorForDisplay(window_), last_surface_size_in_pixels_));
 }
 
 void WindowPortMus::UpdateClientSurfaceEmbedder() {
diff --git a/ui/aura/mus/window_port_mus.h b/ui/aura/mus/window_port_mus.h
index 70f8dcd..c72fb58 100644
--- a/ui/aura/mus/window_port_mus.h
+++ b/ui/aura/mus/window_port_mus.h
@@ -222,7 +222,7 @@
       const std::vector<uint8_t>* property_data) override;
   void SetFrameSinkIdFromServer(const cc::FrameSinkId& frame_sink_id) override;
   const cc::LocalSurfaceId& GetOrAllocateLocalSurfaceId(
-      const gfx::Size& surface_size) override;
+      const gfx::Size& surface_size_in_pixels) override;
   void SetPrimarySurfaceInfo(const cc::SurfaceInfo& surface_info) override;
   void SetFallbackSurfaceInfo(const cc::SurfaceInfo& surface_info) override;
   void DestroyFromServer() override;
@@ -281,7 +281,7 @@
 
   cc::LocalSurfaceId local_surface_id_;
   cc::LocalSurfaceIdAllocator local_surface_id_allocator_;
-  gfx::Size last_surface_size_;
+  gfx::Size last_surface_size_in_pixels_;
 
   ui::CursorData cursor_;
 
diff --git a/ui/aura/mus/window_tree_client_unittest.cc b/ui/aura/mus/window_tree_client_unittest.cc
index a2b76d0..3e8b5d87 100644
--- a/ui/aura/mus/window_tree_client_unittest.cc
+++ b/ui/aura/mus/window_tree_client_unittest.cc
@@ -104,16 +104,19 @@
 using WindowTreeClientWmTest = test::AuraMusWmTestBase;
 using WindowTreeClientClientTest = test::AuraMusClientTestBase;
 
-// WindowTreeClientWmTest with --enable-surface-synchronization.
-class WindowTreeClientWmTestSurfaceSync : public WindowTreeClientWmTest {
+class WindowTreeClientWmTestSurfaceSync
+    : public WindowTreeClientWmTest,
+      public ::testing::WithParamInterface<bool> {
  public:
   WindowTreeClientWmTestSurfaceSync() {}
   ~WindowTreeClientWmTestSurfaceSync() override {}
 
   // WindowTreeClientWmTest:
   void SetUp() override {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        cc::switches::kEnableSurfaceSynchronization);
+    if (GetParam()) {
+      base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+          switches::kForceDeviceScaleFactor, "2");
+    }
     WindowTreeClientWmTest::SetUp();
   }
 
@@ -204,9 +207,13 @@
   EXPECT_FALSE(window_mus->GetLocalSurfaceId().is_valid());
 }
 
+INSTANTIATE_TEST_CASE_P(/* no prefix */,
+                        WindowTreeClientWmTestSurfaceSync,
+                        ::testing::Bool());
+
 // Verifies that a ClientSurfaceEmbedder is created for a window once it has
 // a bounds, and a valid FrameSinkId.
-TEST_F(WindowTreeClientWmTestSurfaceSync,
+TEST_P(WindowTreeClientWmTestSurfaceSync,
        ClientSurfaceEmbedderOnValidEmbedding) {
   Window window(nullptr);
   // TOP_LEVEL_IN_WM and EMBED_IN_OWNER windows allocate cc::LocalSurfaceIds
@@ -219,7 +226,7 @@
   WindowMus* window_mus = WindowMus::Get(&window);
   ASSERT_NE(nullptr, window_mus);
   EXPECT_FALSE(window_mus->GetLocalSurfaceId().is_valid());
-  const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
+  gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
   ASSERT_NE(new_bounds, window.bounds());
   window.SetBounds(new_bounds);
   EXPECT_EQ(new_bounds, window.bounds());
@@ -240,12 +247,14 @@
   ASSERT_NE(nullptr, client_surface_embedder);
 
   // Until the fallback surface fills the window, we will have gutter.
-  ui::Layer* right_gutter = client_surface_embedder->RightGutterForTesting();
-  ASSERT_NE(nullptr, right_gutter);
-  EXPECT_EQ(gfx::Rect(100, 100), right_gutter->bounds());
-  // We don't have a bottom gutter if the fallback surface size is (0, 0) as the
-  // right gutter will fill the whole area.
-  ASSERT_EQ(nullptr, client_surface_embedder->BottomGutterForTesting());
+  {
+    ui::Layer* right_gutter = client_surface_embedder->RightGutterForTesting();
+    ASSERT_NE(nullptr, right_gutter);
+    EXPECT_EQ(gfx::Rect(100, 100), right_gutter->bounds());
+    // We don't have a bottom gutter if the fallback surface size is (0, 0) as
+    // the right gutter will fill the whole area.
+    ASSERT_EQ(nullptr, client_surface_embedder->BottomGutterForTesting());
+  }
 
   // When a SurfaceInfo arrives from the window server, we use it as the
   // fallback SurfaceInfo. Here we issue the PrimarySurfaceInfo back to the
@@ -256,11 +265,30 @@
   // The gutter is gone.
   ASSERT_EQ(nullptr, client_surface_embedder->BottomGutterForTesting());
   ASSERT_EQ(nullptr, client_surface_embedder->RightGutterForTesting());
+
+  // Resize again: we should have gutter.
+  new_bounds.SetRect(0, 0, 150, 150);
+  ASSERT_NE(new_bounds, window.bounds());
+  window.SetBounds(new_bounds);
+  ASSERT_NE(nullptr, client_surface_embedder->BottomGutterForTesting());
+  ASSERT_NE(nullptr, client_surface_embedder->RightGutterForTesting());
+
+  // Until the fallback surface fills the window, we will have gutter.
+  {
+    ui::Layer* right_gutter = client_surface_embedder->RightGutterForTesting();
+    ASSERT_NE(nullptr, right_gutter);
+    EXPECT_EQ(gfx::Rect(100, 0, 50, 150), right_gutter->bounds());
+
+    ui::Layer* bottom_gutter =
+        client_surface_embedder->BottomGutterForTesting();
+    ASSERT_NE(nullptr, bottom_gutter);
+    EXPECT_EQ(gfx::Rect(0, 100, 100, 50), bottom_gutter->bounds());
+  }
 }
 
 // Verifies that the cc::LocalSurfaceId generated by an embedder changes when
 // the size changes, but not when the position changes.
-TEST_F(WindowTreeClientWmTest, SetBoundsLocalSurfaceIdChanges) {
+TEST_P(WindowTreeClientWmTestSurfaceSync, SetBoundsLocalSurfaceIdChanges) {
   ASSERT_EQ(base::nullopt, window_tree()->last_local_surface_id());
   Window window(nullptr);
   // TOP_LEVEL_IN_WM and EMBED_IN_OWNER windows allocate cc::LocalSurfaceIds
diff --git a/ui/gl/gl_image_dxgi.cc b/ui/gl/gl_image_dxgi.cc
index a470f33..a7ab19c 100644
--- a/ui/gl/gl_image_dxgi.cc
+++ b/ui/gl/gl_image_dxgi.cc
@@ -118,6 +118,10 @@
   base::win::ScopedComPtr<ID3D11DeviceContext> context;
   d3d11_device_->GetImmediateContext(context.GetAddressOf());
   context.CopyTo(video_context_.GetAddressOf());
+
+  base::win::ScopedComPtr<ID3D10Multithread> multithread;
+  d3d11_device_.CopyTo(multithread.GetAddressOf());
+  CHECK(multithread->GetMultithreadProtected());
   return true;
 }
 
@@ -126,6 +130,10 @@
     const base::win::ScopedComPtr<ID3D11VideoProcessorEnumerator>& enumerator) {
   output_view_.Reset();
 
+  base::win::ScopedComPtr<ID3D11Device> processor_device;
+  video_processor->GetDevice(processor_device.GetAddressOf());
+  CHECK_EQ(d3d11_device_.Get(), processor_device.Get());
+
   d3d11_processor_ = video_processor;
   enumerator_ = enumerator;
   D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_view_desc = {
@@ -150,6 +158,11 @@
   if (copied_)
     return true;
 
+  CHECK(video_device_);
+  base::win::ScopedComPtr<ID3D11Device> texture_device;
+  texture_->GetDevice(texture_device.GetAddressOf());
+  CHECK_EQ(d3d11_device_.Get(), texture_device.Get());
+
   D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_view_desc = {0};
   input_view_desc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
   input_view_desc.Texture2D.ArraySlice = (UINT)level_;
diff --git a/ui/message_center/views/notification_button.cc b/ui/message_center/views/notification_button.cc
index 72b2be7..4fd8ac6a 100644
--- a/ui/message_center/views/notification_button.cc
+++ b/ui/message_center/views/notification_button.cc
@@ -17,12 +17,7 @@
 namespace message_center {
 
 NotificationButton::NotificationButton(views::ButtonListener* listener)
-    : views::CustomButton(listener),
-      icon_(NULL),
-      title_(NULL),
-      focus_painter_(views::Painter::CreateSolidFocusPainter(
-          message_center::kFocusBorderColor,
-          gfx::Insets(1, 2, 2, 2))) {
+    : views::CustomButton(listener), icon_(NULL), title_(NULL) {
   SetFocusForPlatform();
   // Create a background so that it does not change when the MessageView
   // background changes to show touch feedback
@@ -34,6 +29,8 @@
                            message_center::kButtonHorizontalPadding,
                            kButtonVecticalPadding,
                            message_center::kButtonIconToTitlePadding));
+  SetFocusPainter(views::Painter::CreateSolidFocusPainter(
+      message_center::kFocusBorderColor, gfx::Insets(1, 2, 2, 2)));
 }
 
 NotificationButton::~NotificationButton() {
@@ -83,22 +80,9 @@
   return message_center::kButtonHeight;
 }
 
-void NotificationButton::OnPaint(gfx::Canvas* canvas) {
-  CustomButton::OnPaint(canvas);
-  views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
-}
-
 void NotificationButton::OnFocus() {
   views::CustomButton::OnFocus();
   ScrollRectToVisible(GetLocalBounds());
-  // We render differently when focused.
-  SchedulePaint();
- }
-
-void NotificationButton::OnBlur() {
-  views::CustomButton::OnBlur();
-  // We render differently when focused.
-  SchedulePaint();
 }
 
 void NotificationButton::ViewHierarchyChanged(
diff --git a/ui/message_center/views/notification_button.h b/ui/message_center/views/notification_button.h
index 69f80bae..3d3d218 100644
--- a/ui/message_center/views/notification_button.h
+++ b/ui/message_center/views/notification_button.h
@@ -30,9 +30,7 @@
   // Overridden from views::View:
   gfx::Size CalculatePreferredSize() const override;
   int GetHeightForWidth(int width) const override;
-  void OnPaint(gfx::Canvas* canvas) override;
   void OnFocus() override;
-  void OnBlur() override;
   void ViewHierarchyChanged(
       const ViewHierarchyChangedDetails& details) override;
 
@@ -42,7 +40,6 @@
  private:
   views::ImageView* icon_;
   views::Label* title_;
-  std::unique_ptr<views::Painter> focus_painter_;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationButton);
 };
diff --git a/ui/views/controls/button/blue_button_unittest.cc b/ui/views/controls/button/blue_button_unittest.cc
index a4292ca..657f5bfa 100644
--- a/ui/views/controls/button/blue_button_unittest.cc
+++ b/ui/views/controls/button/blue_button_unittest.cc
@@ -27,12 +27,12 @@
   LabelButton* button = new LabelButton(nullptr, base::ASCIIToUTF16("foo"));
   EXPECT_EQ(Button::STYLE_TEXTBUTTON, button->style());
   // Focus painter by default.
-  EXPECT_TRUE(button->focus_painter());
+  EXPECT_TRUE(button->focus_painter_.get());
 
   // Switch to the same style as BlueButton for a more compelling comparison.
   button->SetStyleDeprecated(Button::STYLE_BUTTON);
   EXPECT_EQ(Button::STYLE_BUTTON, button->style());
-  EXPECT_FALSE(button->focus_painter());
+  EXPECT_FALSE(button->focus_painter_.get());
 
   widget->GetContentsView()->AddChildView(button);
   button->SizeToPreferredSize();
@@ -47,7 +47,7 @@
   // ... a special blue border should be used.
   BlueButton* blue_button = new BlueButton(nullptr, base::ASCIIToUTF16("foo"));
   EXPECT_EQ(Button::STYLE_BUTTON, blue_button->style());
-  EXPECT_FALSE(blue_button->focus_painter());
+  EXPECT_FALSE(blue_button->focus_painter_.get());
 
   widget->GetContentsView()->AddChildView(blue_button);
   blue_button->SizeToPreferredSize();
diff --git a/ui/views/controls/button/checkbox.cc b/ui/views/controls/button/checkbox.cc
index 52a5d69..6e2dccd 100644
--- a/ui/views/controls/button/checkbox.cc
+++ b/ui/views/controls/button/checkbox.cc
@@ -125,23 +125,6 @@
   }
 }
 
-void Checkbox::OnPaint(gfx::Canvas* canvas) {
-  LabelButton::OnPaint(canvas);
-
-  if (!UseMd() || !HasFocus())
-    return;
-
-  cc::PaintFlags focus_flags;
-  focus_flags.setAntiAlias(true);
-  focus_flags.setColor(
-      SkColorSetA(GetNativeTheme()->GetSystemColor(
-                      ui::NativeTheme::kColorId_FocusedBorderColor),
-                  0x66));
-  focus_flags.setStyle(cc::PaintFlags::kStroke_Style);
-  focus_flags.setStrokeWidth(2);
-  PaintFocusRing(canvas, focus_flags);
-}
-
 void Checkbox::OnFocus() {
   LabelButton::OnFocus();
   if (!UseMd())
@@ -181,6 +164,21 @@
       ui::NativeTheme::kColorId_ButtonEnabledColor);
 }
 
+void Checkbox::PaintButtonContents(gfx::Canvas* canvas) {
+  if (!UseMd() || !HasFocus())
+    return;
+
+  cc::PaintFlags focus_flags;
+  focus_flags.setAntiAlias(true);
+  focus_flags.setColor(
+      SkColorSetA(GetNativeTheme()->GetSystemColor(
+                      ui::NativeTheme::kColorId_FocusedBorderColor),
+                  0x66));
+  focus_flags.setStyle(cc::PaintFlags::kStroke_Style);
+  focus_flags.setStrokeWidth(2);
+  PaintFocusRing(canvas, focus_flags);
+}
+
 gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
   if (UseMd()) {
     return gfx::CreateVectorIcon(
diff --git a/ui/views/controls/button/checkbox.h b/ui/views/controls/button/checkbox.h
index e45ea53a..54ea6169 100644
--- a/ui/views/controls/button/checkbox.h
+++ b/ui/views/controls/button/checkbox.h
@@ -43,13 +43,13 @@
   // LabelButton:
   const char* GetClassName() const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  void OnPaint(gfx::Canvas* canvas) override;
   void OnFocus() override;
   void OnBlur() override;
   void OnNativeThemeChanged(const ui::NativeTheme* theme) override;
   std::unique_ptr<InkDrop> CreateInkDrop() override;
   std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
   SkColor GetInkDropBaseColor() const override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
   gfx::ImageSkia GetImage(ButtonState for_state) const override;
 
   // Set the image shown for each button state depending on whether it is
diff --git a/ui/views/controls/button/custom_button.cc b/ui/views/controls/button/custom_button.cc
index 53809ab..2c69362 100644
--- a/ui/views/controls/button/custom_button.cc
+++ b/ui/views/controls/button/custom_button.cc
@@ -20,6 +20,7 @@
 #include "ui/views/controls/button/menu_button.h"
 #include "ui/views/controls/button/radio_button.h"
 #include "ui/views/controls/button/toggle_button.h"
+#include "ui/views/painter.h"
 #include "ui/views/style/platform_style.h"
 #include "ui/views/widget/widget.h"
 
@@ -136,6 +137,10 @@
   return state_ == STATE_HOVERED;
 }
 
+void CustomButton::SetFocusPainter(std::unique_ptr<Painter> focus_painter) {
+  focus_painter_ = std::move(focus_painter);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // CustomButton, View overrides:
 
@@ -352,6 +357,12 @@
   AnimateInkDrop(InkDropState::HIDDEN, nullptr /* event */);
 }
 
+void CustomButton::OnPaint(gfx::Canvas* canvas) {
+  Button::OnPaint(canvas);
+  PaintButtonContents(canvas);
+  Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
+}
+
 void CustomButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   Button::GetAccessibleNodeData(node_data);
   switch (state_) {
@@ -408,6 +419,12 @@
     SetState(STATE_NORMAL);
 }
 
+void CustomButton::OnFocus() {
+  Button::OnFocus();
+  if (focus_painter_)
+    SchedulePaint();
+}
+
 void CustomButton::OnBlur() {
   Button::OnBlur();
   if (IsHotTracked() || state_ == STATE_PRESSED) {
@@ -419,6 +436,8 @@
     // it is possible for a Mouse Release to trigger an action however there
     // would be no visual cue to the user that this will occur.
   }
+  if (focus_painter_)
+    SchedulePaint();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -455,6 +474,8 @@
   return IsTriggerableEvent(event);
 }
 
+void CustomButton::PaintButtonContents(gfx::Canvas* canvas) {}
+
 bool CustomButton::ShouldEnterHoveredState() {
   if (!visible())
     return false;
diff --git a/ui/views/controls/button/custom_button.h b/ui/views/controls/button/custom_button.h
index b6f96710..b76b2f8 100644
--- a/ui/views/controls/button/custom_button.h
+++ b/ui/views/controls/button/custom_button.h
@@ -17,6 +17,8 @@
 
 namespace views {
 
+class Painter;
+
 // A button with custom rendering. The base of ImageButton and LabelButton.
 // Note that this type of button is not focusable by default and will not be
 // part of the focus chain, unless in accessibility mode. Call
@@ -104,6 +106,8 @@
   void SetHotTracked(bool is_hot_tracked);
   bool IsHotTracked() const;
 
+  void SetFocusPainter(std::unique_ptr<Painter> focus_painter);
+
   // Overridden from View:
   void OnEnabledChanged() override;
   const char* GetClassName() const override;
@@ -122,6 +126,9 @@
   void ShowContextMenu(const gfx::Point& p,
                        ui::MenuSourceType source_type) override;
   void OnDragDone() override;
+  // Instead of overriding this, subclasses that want custom painting should use
+  // PaintButtonContents.
+  void OnPaint(gfx::Canvas* canvas) final;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void VisibilityChanged(View* starting_from, bool is_visible) override;
 
@@ -135,6 +142,7 @@
   // Overridden from View:
   void ViewHierarchyChanged(
       const ViewHierarchyChangedDetails& details) override;
+  void OnFocus() override;
   void OnBlur() override;
 
  protected:
@@ -162,6 +170,11 @@
   // we simply return IsTriggerableEvent(event).
   virtual bool ShouldEnterPushedState(const ui::Event& event);
 
+  // Override to paint custom button contents. Any background or border set on
+  // the view will be painted before this is called and |focus_painter_| will be
+  // painted afterwards.
+  virtual void PaintButtonContents(gfx::Canvas* canvas);
+
   // Returns true if the button should enter hovered state; that is, if the
   // mouse is over the button, and no other window has capture (which would
   // prevent the button from receiving MouseExited events and updating its
@@ -177,6 +190,8 @@
   }
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(BlueButtonTest, Border);
+
   ButtonState state_;
 
   gfx::ThrobAnimation hover_animation_;
@@ -207,6 +222,8 @@
   // The color of the ripple and hover.
   SkColor ink_drop_base_color_;
 
+  std::unique_ptr<Painter> focus_painter_;
+
   DISALLOW_COPY_AND_ASSIGN(CustomButton);
 };
 
diff --git a/ui/views/controls/button/image_button.cc b/ui/views/controls/button/image_button.cc
index abd2fab..19577bb 100644
--- a/ui/views/controls/button/image_button.cc
+++ b/ui/views/controls/button/image_button.cc
@@ -32,8 +32,8 @@
     : CustomButton(listener),
       h_alignment_(ALIGN_LEFT),
       v_alignment_(ALIGN_TOP),
-      draw_image_mirrored_(false),
-      focus_painter_(Painter::CreateDashedFocusPainter()) {
+      draw_image_mirrored_(false) {
+  SetFocusPainter(Painter::CreateDashedFocusPainter());
   // By default, we request that the gfx::Canvas passed to our View::OnPaint()
   // implementation is flipped horizontally so that the button's images are
   // mirrored when the UI directionality is right-to-left.
@@ -83,10 +83,6 @@
   SchedulePaint();
 }
 
-void ImageButton::SetFocusPainter(std::unique_ptr<Painter> focus_painter) {
-  focus_painter_ = std::move(focus_painter);
-}
-
 void ImageButton::SetMinimumImageSize(const gfx::Size& size) {
   if (minimum_image_size_ == size)
     return;
@@ -102,10 +98,21 @@
   return kViewClassName;
 }
 
-void ImageButton::OnPaint(gfx::Canvas* canvas) {
-  // Call the base class first to paint any background/borders.
-  View::OnPaint(canvas);
+gfx::Size ImageButton::CalculatePreferredSize() const {
+  gfx::Size size(kDefaultWidth, kDefaultHeight);
+  if (!images_[STATE_NORMAL].isNull()) {
+    size = gfx::Size(images_[STATE_NORMAL].width(),
+                     images_[STATE_NORMAL].height());
+  }
 
+  size.SetToMax(minimum_image_size_);
+
+  gfx::Insets insets = GetInsets();
+  size.Enlarge(insets.width(), insets.height());
+  return size;
+}
+
+void ImageButton::PaintButtonContents(gfx::Canvas* canvas) {
   // TODO(estade|tdanderson|bruthig): The ink drop layer should be positioned
   // behind the button's image which means the image needs to be painted to its
   // own layer instead of to the Canvas.
@@ -124,39 +131,11 @@
 
     canvas->DrawImageInt(img, position.x(), position.y());
   }
-
-  Painter::PaintFocusPainter(this, canvas, focus_painter());
-}
-
-gfx::Size ImageButton::CalculatePreferredSize() const {
-  gfx::Size size(kDefaultWidth, kDefaultHeight);
-  if (!images_[STATE_NORMAL].isNull()) {
-    size = gfx::Size(images_[STATE_NORMAL].width(),
-                     images_[STATE_NORMAL].height());
-  }
-
-  size.SetToMax(minimum_image_size_);
-
-  gfx::Insets insets = GetInsets();
-  size.Enlarge(insets.width(), insets.height());
-  return size;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // ImageButton, protected:
 
-void ImageButton::OnFocus() {
-  CustomButton::OnFocus();
-  if (focus_painter_.get())
-    SchedulePaint();
-}
-
-void ImageButton::OnBlur() {
-  CustomButton::OnBlur();
-  if (focus_painter_.get())
-    SchedulePaint();
-}
-
 gfx::ImageSkia ImageButton::GetImageToPaint() {
   gfx::ImageSkia img;
 
diff --git a/ui/views/controls/button/image_button.h b/ui/views/controls/button/image_button.h
index 5ee902c..e17a5ce 100644
--- a/ui/views/controls/button/image_button.h
+++ b/ui/views/controls/button/image_button.h
@@ -15,8 +15,6 @@
 
 namespace views {
 
-class Painter;
-
 // An image button.
 // Note that this type of button is not focusable by default and will not be
 // part of the focus chain, unless in accessibility mode. Call
@@ -60,8 +58,6 @@
   void SetImageAlignment(HorizontalAlignment h_align,
                          VerticalAlignment v_align);
 
-  void SetFocusPainter(std::unique_ptr<Painter> focus_painter);
-
   // The minimum size of the contents (not including the border). The contents
   // will be at least this size, but may be larger if the image itself is
   // larger.
@@ -75,13 +71,11 @@
 
   // Overridden from View:
   const char* GetClassName() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
   gfx::Size CalculatePreferredSize() const override;
 
  protected:
-  // Overridden from View:
-  void OnFocus() override;
-  void OnBlur() override;
+  // Overridden from CustomButton:
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // Returns the image to paint. This is invoked from paint and returns a value
   // from images.
@@ -90,8 +84,6 @@
   // Updates button background for |scale_factor|.
   void UpdateButtonBackground(ui::ScaleFactor scale_factor);
 
-  Painter* focus_painter() { return focus_painter_.get(); }
-
   // The images used to render the different states of this button.
   gfx::ImageSkia images_[STATE_COUNT];
 
@@ -119,8 +111,6 @@
   // resources.
   bool draw_image_mirrored_;
 
-  std::unique_ptr<Painter> focus_painter_;
-
   DISALLOW_COPY_AND_ASSIGN(ImageButton);
 };
 
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index ab726dc7..6fb6e39e 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -179,10 +179,6 @@
   InvalidateLayout();
 }
 
-void LabelButton::SetFocusPainter(std::unique_ptr<Painter> focus_painter) {
-  focus_painter_ = std::move(focus_painter);
-}
-
 gfx::Size LabelButton::CalculatePreferredSize() const {
   if (cached_preferred_size_valid_)
     return cached_preferred_size_;
@@ -337,11 +333,6 @@
   return !GetText().empty();
 }
 
-void LabelButton::OnPaint(gfx::Canvas* canvas) {
-  View::OnPaint(canvas);
-  Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
-}
-
 void LabelButton::OnFocus() {
   CustomButton::OnFocus();
   // Typically the border renders differently when focused.
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index a608594c..5d61e32c 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -23,7 +23,6 @@
 
 class InkDropContainerView;
 class LabelButtonBorder;
-class Painter;
 
 // LabelButton is a button with text and an icon, it's not focusable by default.
 class VIEWS_EXPORT LabelButton : public CustomButton,
@@ -89,9 +88,6 @@
   // Call SetMinSize(gfx::Size()) to clear the size if needed.
   void SetImageLabelSpacing(int spacing);
 
-  void SetFocusPainter(std::unique_ptr<Painter> focus_painter);
-  Painter* focus_painter() { return focus_painter_.get(); }
-
   // Creates the default border for this button. This can be overridden by
   // subclasses.
   virtual std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const;
@@ -129,7 +125,6 @@
   virtual bool ShouldUseFloodFillInkDrop() const;
 
   // View:
-  void OnPaint(gfx::Canvas* canvas) override;
   void OnFocus() override;
   void OnBlur() override;
   void OnNativeThemeChanged(const ui::NativeTheme* theme) override;
@@ -239,8 +234,6 @@
   // UI direction).
   gfx::HorizontalAlignment horizontal_alignment_;
 
-  std::unique_ptr<Painter> focus_painter_;
-
   DISALLOW_COPY_AND_ASSIGN(LabelButton);
 };
 
diff --git a/ui/views/controls/button/menu_button.cc b/ui/views/controls/button/menu_button.cc
index 74188e4..83ad60f3 100644
--- a/ui/views/controls/button/menu_button.cc
+++ b/ui/views/controls/button/menu_button.cc
@@ -178,13 +178,6 @@
   return event.type() == ui::ET_GESTURE_TAP;
 }
 
-void MenuButton::OnPaint(gfx::Canvas* canvas) {
-  LabelButton::OnPaint(canvas);
-
-  if (show_menu_marker_)
-    PaintMenuMarker(canvas);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 //
 // MenuButton - Events
@@ -367,6 +360,11 @@
   Activate(&event);
 }
 
+void MenuButton::PaintButtonContents(gfx::Canvas* canvas) {
+  if (show_menu_marker_)
+    PaintMenuMarker(canvas);
+}
+
 void MenuButton::IncrementPressedLocked(bool snap_ink_drop_to_activated,
                                         const ui::LocatedEvent* event) {
   ++pressed_lock_count_;
diff --git a/ui/views/controls/button/menu_button.h b/ui/views/controls/button/menu_button.h
index 0d4af71..9adbee93 100644
--- a/ui/views/controls/button/menu_button.h
+++ b/ui/views/controls/button/menu_button.h
@@ -78,7 +78,6 @@
   // Overridden from View:
   gfx::Size CalculatePreferredSize() const override;
   const char* GetClassName() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
@@ -101,6 +100,7 @@
   bool ShouldEnterPushedState(const ui::Event& event) override;
   void StateChanged(ButtonState old_state) override;
   void NotifyClick(const ui::Event& event) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // Offset of the associated menu position.
   gfx::Point menu_offset_;
diff --git a/ui/views/controls/button/toggle_button.cc b/ui/views/controls/button/toggle_button.cc
index f5885b1..5585524 100644
--- a/ui/views/controls/button/toggle_button.cc
+++ b/ui/views/controls/button/toggle_button.cc
@@ -143,10 +143,6 @@
   }
 }
 
-void ToggleButton::SetFocusPainter(std::unique_ptr<Painter> focus_painter) {
-  focus_painter_ = std::move(focus_painter);
-}
-
 gfx::Size ToggleButton::CalculatePreferredSize() const {
   gfx::Rect rect(kTrackWidth, kTrackHeight);
   rect.Inset(gfx::Insets(-kTrackVerticalMargin, -kTrackHorizontalMargin));
@@ -189,38 +185,6 @@
   return kViewClassName;
 }
 
-void ToggleButton::OnPaint(gfx::Canvas* canvas) {
-  // Paint the toggle track. To look sharp even at fractional scale factors,
-  // round up to pixel boundaries.
-  canvas->Save();
-  float dsf = canvas->UndoDeviceScaleFactor();
-  gfx::RectF track_rect(GetTrackBounds());
-  track_rect.Scale(dsf);
-  track_rect = gfx::RectF(gfx::ToEnclosingRect(track_rect));
-  cc::PaintFlags track_flags;
-  track_flags.setAntiAlias(true);
-  const double color_ratio = slide_animation_.GetCurrentValue();
-  track_flags.setColor(color_utils::AlphaBlend(
-      GetTrackColor(true), GetTrackColor(false),
-      static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio)));
-  canvas->DrawRoundRect(track_rect, track_rect.height() / 2, track_flags);
-  canvas->Restore();
-
-  Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
-}
-
-void ToggleButton::OnFocus() {
-  CustomButton::OnFocus();
-  if (focus_painter_)
-    SchedulePaint();
-}
-
-void ToggleButton::OnBlur() {
-  CustomButton::OnBlur();
-  if (focus_painter_)
-    SchedulePaint();
-}
-
 void ToggleButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
   UpdateThumb();
 }
@@ -243,6 +207,24 @@
   CustomButton::NotifyClick(event);
 }
 
+void ToggleButton::PaintButtonContents(gfx::Canvas* canvas) {
+  // Paint the toggle track. To look sharp even at fractional scale factors,
+  // round up to pixel boundaries.
+  canvas->Save();
+  float dsf = canvas->UndoDeviceScaleFactor();
+  gfx::RectF track_rect(GetTrackBounds());
+  track_rect.Scale(dsf);
+  track_rect = gfx::RectF(gfx::ToEnclosingRect(track_rect));
+  cc::PaintFlags track_flags;
+  track_flags.setAntiAlias(true);
+  const double color_ratio = slide_animation_.GetCurrentValue();
+  track_flags.setColor(color_utils::AlphaBlend(
+      GetTrackColor(true), GetTrackColor(false),
+      static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio)));
+  canvas->DrawRoundRect(track_rect, track_rect.height() / 2, track_flags);
+  canvas->Restore();
+}
+
 void ToggleButton::AddInkDropLayer(ui::Layer* ink_drop_layer) {
   thumb_view_->AddInkDropLayer(ink_drop_layer);
 }
diff --git a/ui/views/controls/button/toggle_button.h b/ui/views/controls/button/toggle_button.h
index aef030a..7b35688 100644
--- a/ui/views/controls/button/toggle_button.h
+++ b/ui/views/controls/button/toggle_button.h
@@ -10,8 +10,6 @@
 
 namespace views {
 
-class Painter;
-
 // This view presents a button that has two states: on and off. This is similar
 // to a checkbox but has no text and looks more like a two-state horizontal
 // slider.
@@ -25,8 +23,6 @@
   void SetIsOn(bool is_on, bool animate);
   bool is_on() const { return is_on_; }
 
-  void SetFocusPainter(std::unique_ptr<Painter> focus_painter);
-
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
 
@@ -47,15 +43,13 @@
 
   // views::View:
   const char* GetClassName() const override;
-  void OnPaint(gfx::Canvas* canvas) override;
-  void OnFocus() override;
-  void OnBlur() override;
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
   void OnNativeThemeChanged(const ui::NativeTheme* theme) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   // CustomButton:
   void NotifyClick(const ui::Event& event) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
   void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
   void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
   std::unique_ptr<InkDrop> CreateInkDrop() override;
@@ -68,7 +62,6 @@
   bool is_on_;
   gfx::SlideAnimation slide_animation_;
   ThumbView* thumb_view_;
-  std::unique_ptr<Painter> focus_painter_;
 
   DISALLOW_COPY_AND_ASSIGN(ToggleButton);
 };
diff --git a/ui/views/controls/scrollbar/scroll_bar_views.cc b/ui/views/controls/scrollbar/scroll_bar_views.cc
index aec7b254..3dc0527 100644
--- a/ui/views/controls/scrollbar/scroll_bar_views.cc
+++ b/ui/views/controls/scrollbar/scroll_bar_views.cc
@@ -35,7 +35,7 @@
   const char* GetClassName() const override { return "ScrollBarButton"; }
 
  protected:
-  void OnPaint(gfx::Canvas* canvas) override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
  private:
   ui::NativeTheme::ExtraParams GetNativeThemeParams() const;
@@ -80,7 +80,7 @@
       GetNativeThemePart(), GetNativeThemeState(), GetNativeThemeParams());
 }
 
-void ScrollBarButton::OnPaint(gfx::Canvas* canvas) {
+void ScrollBarButton::PaintButtonContents(gfx::Canvas* canvas) {
   gfx::Rect bounds(GetPreferredSize());
   GetNativeTheme()->Paint(canvas->sk_canvas(), GetNativeThemePart(),
                           GetNativeThemeState(), bounds,
diff --git a/ui/views/controls/webview/BUILD.gn b/ui/views/controls/webview/BUILD.gn
index b8daeebab..2836c31 100644
--- a/ui/views/controls/webview/BUILD.gn
+++ b/ui/views/controls/webview/BUILD.gn
@@ -8,6 +8,8 @@
     "unhandled_keyboard_event_handler.h",
     "unhandled_keyboard_event_handler_mac.mm",
     "unhandled_keyboard_event_handler_win.cc",
+    "web_contents_set_background_color.cc",
+    "web_contents_set_background_color.h",
     "web_dialog_view.cc",
     "web_dialog_view.h",
     "webview.cc",
diff --git a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc b/ui/views/controls/webview/web_contents_set_background_color.cc
similarity index 85%
rename from chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc
rename to ui/views/controls/webview/web_contents_set_background_color.cc
index 6b57011..e5de0e42 100644
--- a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc
+++ b/ui/views/controls/webview/web_contents_set_background_color.cc
@@ -1,17 +1,17 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/login/ui/web_contents_set_background_color.h"
+#include "ui/views/controls/webview/web_contents_set_background_color.h"
 
 #include "base/memory/ptr_util.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 
-DEFINE_WEB_CONTENTS_USER_DATA_KEY(chromeos::WebContentsSetBackgroundColor);
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(views::WebContentsSetBackgroundColor);
 
-namespace chromeos {
+namespace views {
 
 // static
 void WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
@@ -54,4 +54,4 @@
   new_host->GetWidget()->GetView()->SetBackgroundColor(color_);
 }
 
-}  // namespace chromeos
+}  // namespace views
diff --git a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.h b/ui/views/controls/webview/web_contents_set_background_color.h
similarity index 72%
rename from chrome/browser/chromeos/login/ui/web_contents_set_background_color.h
rename to ui/views/controls/webview/web_contents_set_background_color.h
index 024d310..e10a3a3 100644
--- a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.h
+++ b/ui/views/controls/webview/web_contents_set_background_color.h
@@ -2,16 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
+#ifndef UI_VIEWS_CONTROLS_WEBVIEW_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
+#define UI_VIEWS_CONTROLS_WEBVIEW_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
 
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
+#include "ui/views/controls/webview/webview_export.h"
 
 // Defined in SkColor.h (32-bit ARGB color).
 using SkColor = unsigned int;
 
-namespace chromeos {
+namespace views {
 
 // Ensures that the background color of a given WebContents instance is always
 // set to a given color value.
@@ -19,8 +20,9 @@
     : public content::WebContentsObserver,
       public content::WebContentsUserData<WebContentsSetBackgroundColor> {
  public:
-  static void CreateForWebContentsWithColor(content::WebContents* web_contents,
-                                            SkColor color);
+  WEBVIEW_EXPORT static void CreateForWebContentsWithColor(
+      content::WebContents* web_contents,
+      SkColor color);
 
   ~WebContentsSetBackgroundColor() override;
 
@@ -39,6 +41,6 @@
   DISALLOW_COPY_AND_ASSIGN(WebContentsSetBackgroundColor);
 };
 
-}  // namespace chromeos
+}  // namespace views
 
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
+#endif  // UI_VIEWS_CONTROLS_WEBVIEW_WEB_CONTENTS_SET_BACKGROUND_COLOR_H_
diff --git a/ui/webui/resources/js/cr/ui/array_data_model.js b/ui/webui/resources/js/cr/ui/array_data_model.js
index 8efdb78..d5a596e 100644
--- a/ui/webui/resources/js/cr/ui/array_data_model.js
+++ b/ui/webui/resources/js/cr/ui/array_data_model.js
@@ -126,7 +126,7 @@
     slice: function(opt_from, opt_to) {
       var arr = this.array_;
       return this.indexes_.slice(opt_from, opt_to).map(function(index) {
-        return arr[index]
+        return arr[index];
       });
     },
 
@@ -399,7 +399,7 @@
       } else {
         return function(a, b) {
           return defaultValuesCompareFunction.call(null, a[field], b[field]);
-        }
+        };
       }
     },