diff --git a/DEPS b/DEPS
index d9100da..24171ad6 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'c6912f712f3423b2a09a6e96c999b65a1fd86062',
+  'skia_revision': '1bfece8556cc03b6aa904b2445d2332136bce037',
   # 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': 'dccdc02878d7136d2d4b6ca80bd025294cf1c7f8',
+  'v8_revision': '8aacdbbccf43caf1ed51109609797545d6fad4d3',
   # 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.
@@ -52,7 +52,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'cae72d6ab32cca4c9ce4af6a10bc6f4b89138ed4',
+  'angle_revision': 'b7d5e303339bb90447e12f05ee50e6269f97493e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -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': '5f34d479d06ebab9079c2d0704dee872cc45dd86',
+  'pdfium_revision': '5171a27eaa7489939310bd2864864867cc78ce21',
   # 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': 'c294424b587eb0a331556f215cacb5b22e066438',
+  'catapult_revision': '817b8684be607f1ac5aad1e6ef5713188a8cfb2c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/ash/devtools/ash_devtools_dom_agent.cc b/ash/devtools/ash_devtools_dom_agent.cc
index ce290b5..72fc8e3 100644
--- a/ash/devtools/ash_devtools_dom_agent.cc
+++ b/ash/devtools/ash_devtools_dom_agent.cc
@@ -359,8 +359,7 @@
   constexpr int kBorderThickness = 1;
   views::View* root_view = widget_for_highlighting_->GetRootView();
   root_view->SetBorder(views::CreateSolidBorder(kBorderThickness, border));
-  root_view->set_background(
-      views::Background::CreateSolidBackground(background));
+  root_view->SetBackground(views::CreateSolidBackground(background));
   display::Display display =
       display::Screen::GetScreen()->GetDisplayNearestWindow(
           window_and_bounds.first);
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 7107e989..59b5d15 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -21,7 +21,7 @@
 
   views::Label* label = new views::Label();
   label->SetBorder(views::CreateEmptyBorder(2, 3, 4, 5));
-  label->set_background(views::Background::CreateThemedSolidBackground(
+  label->SetBackground(views::CreateThemedSolidBackground(
       label, ui::NativeTheme::kColorId_BubbleBackground));
   label->SetText(base::ASCIIToUTF16("User"));
   AddChildView(label);
diff --git a/ash/shelf/overflow_bubble_view.cc b/ash/shelf/overflow_bubble_view.cc
index de12a49..dcada6d 100644
--- a/ash/shelf/overflow_bubble_view.cc
+++ b/ash/shelf/overflow_bubble_view.cc
@@ -54,7 +54,7 @@
 
   SetAnchorView(anchor);
   set_arrow(views::BubbleBorder::NONE);
-  set_background(nullptr);
+  SetBackground(nullptr);
   if (shelf_->IsHorizontalAlignment())
     set_margins(gfx::Insets(0, kEndPadding));
   else
diff --git a/ash/system/audio/volume_view.cc b/ash/system/audio/volume_view.cc
index ea4a5d4..9d42635 100644
--- a/ash/system/audio/volume_view.cc
+++ b/ash/system/audio/volume_view.cc
@@ -133,7 +133,7 @@
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_VOLUME));
   tri_view_->AddView(TriView::Container::CENTER, slider_);
 
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_BubbleBackground));
 
   Update();
diff --git a/ash/system/display_scale/scale_view.cc b/ash/system/display_scale/scale_view.cc
index 8a80128..0543bb7 100644
--- a/ash/system/display_scale/scale_view.cc
+++ b/ash/system/display_scale/scale_view.cc
@@ -50,7 +50,7 @@
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCALE_SLIDER));
   tri_view_->AddView(TriView::Container::CENTER, slider_);
 
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_BubbleBackground));
 
   if (!is_default_view_) {
diff --git a/ash/system/network/network_list.cc b/ash/system/network/network_list.cc
index 21ee550..3f4cff25 100644
--- a/ash/system/network/network_list.cc
+++ b/ash/system/network/network_list.cc
@@ -739,23 +739,21 @@
   // Set up layout and apply sticky row property.
   TriView* connection_warning = TrayPopupUtils::CreateDefaultRowView();
   TrayPopupUtils::ConfigureAsStickyHeader(connection_warning);
-  connection_warning->set_background(
-      views::Background::CreateSolidBackground(kHeaderBackgroundColor));
+  connection_warning->SetBackground(
+      views::CreateSolidBackground(kHeaderBackgroundColor));
 
   // Set 'info' icon on left side.
   views::ImageView* image_view = TrayPopupUtils::CreateMainImageView();
   image_view->SetImage(
       gfx::CreateVectorIcon(kSystemMenuInfoIcon, kMenuIconColor));
-  image_view->set_background(
-      views::Background::CreateSolidBackground(SK_ColorTRANSPARENT));
+  image_view->SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
   connection_warning->AddView(TriView::Container::START, image_view);
 
   // Set message label in middle of row.
   views::Label* label = TrayPopupUtils::CreateDefaultLabel();
   label->SetText(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MONITORED_WARNING));
-  label->set_background(
-      views::Background::CreateSolidBackground(SK_ColorTRANSPARENT));
+  label->SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
   TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::DETAILED_VIEW_LABEL);
   style.SetupLabel(label);
   connection_warning->AddView(TriView::Container::CENTER, label);
diff --git a/ash/system/tray/tray_background_view.cc b/ash/system/tray/tray_background_view.cc
index d360779..ec269d2 100644
--- a/ash/system/tray/tray_background_view.cc
+++ b/ash/system/tray/tray_background_view.cc
@@ -149,7 +149,8 @@
 
   SetLayoutManager(new views::FillLayout);
 
-  tray_container_->set_background(background_);
+  tray_container_->SetBackground(
+      std::unique_ptr<views::Background>(background_));
   AddChildView(tray_container_);
 
   tray_event_filter_.reset(new TrayEventFilter);
diff --git a/ash/system/tray/tray_details_view.cc b/ash/system/tray/tray_details_view.cc
index 4aaf772..ea8d244f 100644
--- a/ash/system/tray/tray_details_view.cc
+++ b/ash/system/tray/tray_details_view.cc
@@ -279,7 +279,7 @@
       tri_view_(nullptr),
       back_button_(nullptr) {
   SetLayoutManager(box_layout_);
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_BubbleBackground));
 }
 
@@ -337,7 +337,7 @@
   // Make the |scroller_| have a layer to clip |scroll_content_|'s children.
   // TODO(varkha): Make the sticky rows work with EnableViewPortLayer().
   scroller_->SetPaintToLayer();
-  scroller_->set_background(views::Background::CreateThemedSolidBackground(
+  scroller_->SetBackground(views::CreateThemedSolidBackground(
       scroller_, ui::NativeTheme::kColorId_BubbleBackground));
   scroller_->layer()->SetMasksToBounds(true);
 
diff --git a/ash/system/tray/tray_popup_header_button.cc b/ash/system/tray/tray_popup_header_button.cc
index b6e37e5..0a69db8b 100644
--- a/ash/system/tray/tray_popup_header_button.cc
+++ b/ash/system/tray/tray_popup_header_button.cc
@@ -62,10 +62,9 @@
 
 void TrayPopupHeaderButton::StateChanged(ButtonState old_state) {
   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
-    set_background(views::Background::CreateSolidBackground(
-        kTrayPopupHoverBackgroundColor));
+    SetBackground(views::CreateSolidBackground(kTrayPopupHoverBackgroundColor));
   } else {
-    set_background(nullptr);
+    SetBackground(nullptr);
   }
   SchedulePaint();
 }
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index 820647d..0fdbd9c 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -210,7 +210,7 @@
   // Frequently the label will paint to a layer that's non-opaque, so subpixel
   // rendering won't work unless we explicitly set a background. See
   // crbug.com/686363
-  label->set_background(views::Background::CreateThemedSolidBackground(
+  label->SetBackground(views::CreateThemedSolidBackground(
       label, ui::NativeTheme::kColorId_BubbleBackground));
   return label;
 }
@@ -269,7 +269,7 @@
 
 void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) {
   view->set_id(VIEW_ID_STICKY_HEADER);
-  view->set_background(views::Background::CreateThemedSolidBackground(
+  view->SetBackground(views::CreateThemedSolidBackground(
       view, ui::NativeTheme::kColorId_BubbleBackground));
   view->SetBorder(
       views::CreateEmptyBorder(gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
diff --git a/ash/system/user/user_card_view.cc b/ash/system/user/user_card_view.cc
index 0cd6512..aa69592 100644
--- a/ash/system/user/user_card_view.cc
+++ b/ash/system/user/user_card_view.cc
@@ -290,7 +290,7 @@
   layout->set_cross_axis_alignment(
       views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
 
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_BubbleBackground));
 
   Shell::Get()->media_controller()->AddObserver(this);
diff --git a/ash/system/user/user_view.cc b/ash/system/user/user_view.cc
index c7294b8..9b79b8ca 100644
--- a/ash/system/user/user_view.cc
+++ b/ash/system/user/user_view.cc
@@ -80,7 +80,7 @@
                            kTrayPopupLabelHorizontalPadding + icon_padding);
   layout->set_minimum_cross_axis_size(kTrayPopupItemMinHeight);
   view->SetLayoutManager(layout);
-  view->set_background(views::Background::CreateThemedSolidBackground(
+  view->SetBackground(views::CreateThemedSolidBackground(
       view, ui::NativeTheme::kColorId_BubbleBackground));
 
   int message_id = 0;
diff --git a/ash/test/child_modal_window.cc b/ash/test/child_modal_window.cc
index 297ad9ab..426d8710 100644
--- a/ash/test/child_modal_window.cc
+++ b/ash/test/child_modal_window.cc
@@ -114,8 +114,8 @@
   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
   params.context = context;
   widget_->Init(params);
-  widget_->GetRootView()->set_background(
-      views::Background::CreateSolidBackground(kModalParentColor));
+  widget_->GetRootView()->SetBackground(
+      views::CreateSolidBackground(kModalParentColor));
   widget_->GetNativeView()->SetName("ModalParent");
   AddChildView(button_);
   AddChildView(textfield_);
diff --git a/ash/wm/overview/window_grid.cc b/ash/wm/overview/window_grid.cc
index e1448593..3b27667 100644
--- a/ash/wm/overview/window_grid.cc
+++ b/ash/wm/overview/window_grid.cc
@@ -242,7 +242,7 @@
   } else {
     views::View* content_view =
         new RoundedRectView(border_radius, SK_ColorTRANSPARENT);
-    content_view->set_background(new BackgroundWith1PxBorder(
+    content_view->SetBackground(base::MakeUnique<BackgroundWith1PxBorder>(
         background_color, border_color, border_thickness, border_radius));
     widget->SetContentsView(content_view);
   }
diff --git a/ash/wm/panels/panel_layout_manager.cc b/ash/wm/panels/panel_layout_manager.cc
index b57ab56..d19b82b8 100644
--- a/ash/wm/panels/panel_layout_manager.cc
+++ b/ash/wm/panels/panel_layout_manager.cc
@@ -229,7 +229,8 @@
     DCHECK_EQ(widget_window->GetRootWindow(), parent->GetRootWindow());
     views::View* content_view = new views::View;
     background_ = new CalloutWidgetBackground;
-    content_view->set_background(background_);
+    content_view->SetBackground(
+        std::unique_ptr<views::Background>(background_));
     SetContentsView(content_view);
     widget_window->layer()->SetOpacity(0);
   }
diff --git a/ash/wm/window_cycle_list.cc b/ash/wm/window_cycle_list.cc
index a2c7c38..5047f2c 100644
--- a/ash/wm/window_cycle_list.cc
+++ b/ash/wm/window_cycle_list.cc
@@ -89,9 +89,8 @@
     AddChildView(window_title_);
 
     // Preview padding is black at 50% opacity.
-    preview_background_->set_background(
-        views::Background::CreateSolidBackground(
-            SkColorSetA(SK_ColorBLACK, 0xFF / 2)));
+    preview_background_->SetBackground(
+        views::CreateSolidBackground(SkColorSetA(SK_ColorBLACK, 0xFF / 2)));
     AddChildView(preview_background_);
 
     AddChildView(mirror_view_);
@@ -247,7 +246,7 @@
     // The background needs to be painted to fill the layer, not the View,
     // because the layer animates bounds changes but the View's bounds change
     // immediately.
-    highlight_view_->set_background(new LayerFillBackgroundPainter(
+    highlight_view_->SetBackground(base::MakeUnique<LayerFillBackgroundPainter>(
         views::Painter::CreateRoundRectWith1PxBorderPainter(
             SkColorSetA(SK_ColorWHITE, 0x4D), SkColorSetA(SK_ColorWHITE, 0x33),
             kBackgroundCornerRadius)));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
index 2e371ea7..39f330c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.contextualsearch;
 
+import android.annotation.SuppressLint;
 import android.text.TextUtils;
 
 import org.chromium.base.annotations.CalledByNative;
@@ -17,7 +18,10 @@
  * or changed.
  */
 public abstract class ContextualSearchContext {
-    static final int INVALID_SELECTION_OFFSET = -1;
+    static final int INVALID_OFFSET = -1;
+
+    // Non-visible word-break marker.
+    private static final int SOFT_HYPHEN_CHAR = '\u00AD';
 
     // Pointer to the native instance of this class.
     private long mNativePointer;
@@ -30,8 +34,11 @@
     private String mSurroundingText;
 
     // The start and end offsets of the selection within the text content.
-    private int mSelectionStartOffset = INVALID_SELECTION_OFFSET;
-    private int mSelectionEndOffset = INVALID_SELECTION_OFFSET;
+    private int mSelectionStartOffset = INVALID_OFFSET;
+    private int mSelectionEndOffset = INVALID_OFFSET;
+
+    // The offset of an initial Tap gesture within the text content.
+    private int mTapOffset = INVALID_OFFSET;
 
     // The initial word selected by a Tap, or null.
     private String mInitialSelectedWord;
@@ -39,6 +46,13 @@
     // The original encoding of the base page.
     private String mEncoding;
 
+    // The tapped word, as analyzed internally before selection takes place, or {@code null} if no
+    // analysis has been done yet.
+    private String mTappedWord;
+
+    // The offset of the tap within the tapped word or {@code INVALID_OFFSET} if not yet analyzed.
+    private int mTappedWordOffset = INVALID_OFFSET;
+
     /**
      * Constructs a context that tracks the selection and some amount of page content.
      */
@@ -85,6 +99,9 @@
         mSurroundingText = surroundingText;
         mSelectionStartOffset = startOffset;
         mSelectionEndOffset = endOffset;
+        if (startOffset == endOffset && !hasAnalyzedTap()) {
+            analyzeTap(startOffset);
+        }
         // Notify of an initial selection if it's not empty.
         if (endOffset > startOffset) onSelectionChanged();
     }
@@ -99,7 +116,7 @@
 
     /**
      * @return The offset into the surrounding text of the start of the selection, or
-     *         {@link #INVALID_SELECTION_OFFSET} if not yet established.
+     *         {@link #INVALID_OFFSET} if not yet established.
      */
     int getSelectionStartOffset() {
         return mSelectionStartOffset;
@@ -107,7 +124,7 @@
 
     /**
      * @return The offset into the surrounding text of the end of the selection, or
-     *         {@link #INVALID_SELECTION_OFFSET} if not yet established.
+     *         {@link #INVALID_OFFSET} if not yet established.
      */
     int getSelectionEndOffset() {
         return mSelectionEndOffset;
@@ -143,9 +160,7 @@
      * @return Whether this context can Resolve the Search Term.
      */
     boolean canResolve() {
-        return mHasSetResolveProperties && mSelectionStartOffset != INVALID_SELECTION_OFFSET
-                && mSelectionEndOffset != INVALID_SELECTION_OFFSET
-                && mSelectionEndOffset > mSelectionStartOffset;
+        return mHasSetResolveProperties && hasValidSelection();
     }
 
     /**
@@ -180,7 +195,138 @@
      */
     abstract void onSelectionChanged();
 
-    // TODO(donnd): Add a test for this class!
+    // ============================================================================================
+    // Content Analysis.
+    // ============================================================================================
+
+    /**
+     * @return Whether this context has valid Surrounding text and initial Tap offset.
+     */
+    private boolean hasValidTappedText() {
+        return !TextUtils.isEmpty(mSurroundingText) && mTapOffset >= 0
+                && mTapOffset <= mSurroundingText.length();
+    }
+
+    /**
+     * @return Whether this context has a valid selection.
+     */
+    private boolean hasValidSelection() {
+        if (!hasValidTappedText()) return false;
+
+        return mSelectionStartOffset != INVALID_OFFSET && mSelectionEndOffset != INVALID_OFFSET
+                && mSelectionStartOffset < mSelectionEndOffset
+                && mSelectionEndOffset < mSurroundingText.length();
+    }
+
+    /**
+     * @return Whether a Tap gesture has occurred and been analyzed.
+     */
+    private boolean hasAnalyzedTap() {
+        return mTapOffset >= 0;
+    }
+
+    /**
+     * @return The tapped word, or {@code null} if the tapped word cannot be identified by the
+     *         current limited parsing capability.
+     * @see #analyzeTap
+     */
+    String getTappedWord() {
+        return mTappedWord;
+    }
+
+    /**
+     * @return The offset of the tap within the tapped word, or {@code -1} if the tapped word cannot
+     *         be identified by the current parsing capability.
+     * @see #analyzeTap
+     */
+    int getTappedWordOffset() {
+        return mTappedWordOffset;
+    }
+
+    /**
+     * Finds the tapped word by expanding from the initial Tap offset looking for word-breaks.
+     * This mimics the Blink word-segmentation invoked by SelectWordAroundCaret and similar
+     * selection logic, but is only appropriate for limited use.  Does not work on ideographic
+     * languages and possibly many other cases.  Should only be used only for ML signal evaluation.
+     * @param tapOffset The offset of the Tap within the surrounding text.
+     */
+    private void analyzeTap(int tapOffset) {
+        mTapOffset = tapOffset;
+        mTappedWord = null;
+        mTappedWordOffset = INVALID_OFFSET;
+
+        assert hasValidTappedText();
+
+        int wordStartOffset = findWordStartOffset(mSurroundingText, mTapOffset);
+        int wordEndOffset = findWordEndOffset(mSurroundingText, mTapOffset);
+        if (wordStartOffset == INVALID_OFFSET || wordEndOffset == INVALID_OFFSET) return;
+
+        mTappedWord = mSurroundingText.substring(wordStartOffset, wordEndOffset);
+        mTappedWordOffset = mTapOffset - wordStartOffset;
+    }
+
+    /**
+     * Finds the offset of the start of the word that includes the given initial offset.
+     * The character at the initial offset is not examined, but the one before it is, and scanning
+     * continues on to earlier characters until a non-word character is found.  The offset just
+     * before the non-word character is returned.  If the initial offset is a space immediately
+     * following a word then the start offset of that word is returned.
+     * @param text The text to scan.
+     * @param initial The initial offset to scan before.
+     * @return The start of the word that contains the given initial offset, within {@code text}.
+     */
+    private int findWordStartOffset(String text, int initial) {
+        // Scan before, aborting if we hit any ideographic letter.
+        for (int offset = initial - 1; offset >= 0; offset--) {
+            if (isIdeographicAtIndex(text, offset)) return INVALID_OFFSET;
+
+            if (isWordBreakAtIndex(text, offset)) {
+                // The start of the word is after this word break.
+                return offset + 1;
+            }
+        }
+        return INVALID_OFFSET;
+    }
+
+    /**
+     * Finds the offset of the end of the word that includes the given initial offset.
+     * NOTE: this is the index of the character just past the last character of the word,
+     * so a 3 character word "who" has start index 0 and end index 3.
+     * The character at the initial offset is examined and each one after that too until a non-word
+     * character is encountered, and that offset will be returned.
+     * @param text The text to scan.
+     * @param initial The initial offset to scan from.
+     * @return The end of the word that contains the given initial offset, within {@code text}.
+     */
+    private int findWordEndOffset(String text, int initial) {
+        // Scan after, aborting if we hit any CJKN letter.
+        for (int offset = initial; offset < text.length(); offset++) {
+            if (isIdeographicAtIndex(text, offset)) return INVALID_OFFSET;
+
+            if (isWordBreakAtIndex(text, offset)) {
+                // The end of the word is the offset of this word break.
+                return offset;
+            }
+        }
+        return INVALID_OFFSET;
+    }
+
+    /**
+     * @return Whether the character at the given index in the text is "Ideographic" (as in CJKV
+     *         languages), which means there may not be reliable word breaks.
+     */
+    @SuppressLint("NewApi")
+    private boolean isIdeographicAtIndex(String text, int index) {
+        return Character.isIdeographic(text.charAt(index));
+    }
+
+    /**
+     * @return Whether the character at the given index is a word-break.
+     */
+    private boolean isWordBreakAtIndex(String text, int index) {
+        return !Character.isLetterOrDigit(text.charAt(index))
+                && text.codePointAt(index) != SOFT_HYPHEN_CHAR;
+    }
 
     // ============================================================================================
     // Native callback support.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
index 8f2fd64..768d0c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
@@ -48,6 +48,10 @@
     private static final String SCREEN_TOP_SUPPRESSION_DPS = "screen_top_suppression_dps";
     private static final String ENABLE_BAR_OVERLAP_COLLECTION = "enable_bar_overlap_collection";
     private static final String BAR_OVERLAP_SUPPRESSION_ENABLED = "enable_bar_overlap_suppression";
+    private static final String WORD_EDGE_SUPPRESSION_ENABLED = "enable_word_edge_suppression";
+    private static final String SHORT_WORD_SUPPRESSION_ENABLED = "enable_short_word_suppression";
+    private static final String NOT_LONG_WORD_SUPPRESSION_ENABLED =
+            "enable_not_long_word_suppression";
 
     private static final String MINIMUM_SELECTION_LENGTH = "minimum_selection_length";
 
@@ -67,6 +71,7 @@
             "disable_page_content_notification";
 
     // Cached values to avoid repeated and redundant JNI operations.
+    // TODO(donnd): consider creating a single Map to cache these static values.
     private static Boolean sEnabled;
     private static Boolean sDisableSearchTermResolution;
     private static Boolean sIsMandatoryPromoEnabled;
@@ -78,6 +83,9 @@
     private static Integer sScreenTopSuppressionDps;
     private static Boolean sIsBarOverlapCollectionEnabled;
     private static Boolean sIsBarOverlapSuppressionEnabled;
+    private static Boolean sIsWordEdgeSuppressionEnabled;
+    private static Boolean sIsShortWordSuppressionEnabled;
+    private static Boolean sIsNotLongWordSuppressionEnabled;
     private static Integer sMinimumSelectionLength;
     private static Boolean sIsOnlineDetectionDisabled;
     private static Boolean sIsAmpAsSeparateTabDisabled;
@@ -248,6 +256,36 @@
     }
 
     /**
+     * @return Whether triggering is suppressed by a tap that's near the edge of a word.
+     */
+    static boolean isWordEdgeSuppressionEnabled() {
+        if (sIsWordEdgeSuppressionEnabled == null) {
+            sIsWordEdgeSuppressionEnabled = getBooleanParam(WORD_EDGE_SUPPRESSION_ENABLED);
+        }
+        return sIsWordEdgeSuppressionEnabled.booleanValue();
+    }
+
+    /**
+     * @return Whether triggering is suppressed by a tap that's in a short word.
+     */
+    static boolean isShortWordSuppressionEnabled() {
+        if (sIsShortWordSuppressionEnabled == null) {
+            sIsShortWordSuppressionEnabled = getBooleanParam(SHORT_WORD_SUPPRESSION_ENABLED);
+        }
+        return sIsShortWordSuppressionEnabled.booleanValue();
+    }
+
+    /**
+     * @return Whether triggering is suppressed by a tap that's not in a long word.
+     */
+    static boolean isNotLongWordSuppressionEnabled() {
+        if (sIsNotLongWordSuppressionEnabled == null) {
+            sIsNotLongWordSuppressionEnabled = getBooleanParam(NOT_LONG_WORD_SUPPRESSION_ENABLED);
+        }
+        return sIsNotLongWordSuppressionEnabled.booleanValue();
+    }
+
+    /**
      * @return The minimum valid selection length.
      */
     static int getMinimumSelectionLength() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 3f43032a..7075f12 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -1475,13 +1475,14 @@
             @Override
             public void decideSuppression() {
                 mInternalStateController.notifyStartingWorkOn(InternalState.DECIDING_SUPPRESSION);
+
                 // Ranker will handle the suppression, but our legacy implementation uses
                 // TapSuppressionHeuristics (run from the ContextualSearchSelectionConroller).
                 // Usage includes tap-far-from-previous suppression.
                 mTapSuppressionRankerLogger.setupLoggingForPage(getBasePageUrl());
 
                 // TODO(donnd): Move handleShouldSuppressTap out of the Selection Controller.
-                mSelectionController.handleShouldSuppressTap(mTapSuppressionRankerLogger);
+                mSelectionController.handleShouldSuppressTap(mContext, mTapSuppressionRankerLogger);
             }
 
             /** Starts showing the Tap UI by selecting a word around the current caret. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 9f296ce..edae544 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -205,7 +205,7 @@
         if (baseContentView != null) {
             baseContentView.clearSelection();
         }
-        resetAllStates();
+        resetSelectionStates();
     }
 
     /**
@@ -351,10 +351,12 @@
      * or #handleNonSuppressedTap() after a possible delay.
      * This should be called when the context is fully built (by gathering surrounding text
      * if needed, etc) but before showing any UX.
+     * @param contextualSearchContext The {@link ContextualSearchContext} for the Tap gesture.
      * @param rankerLogger The {@link ContextualSearchRankerLogger} currently being used to measure
      *        or suppress the UI by Ranker.
      */
-    void handleShouldSuppressTap(ContextualSearchRankerLogger rankerLogger) {
+    void handleShouldSuppressTap(ContextualSearchContext contextualSearchContext,
+            ContextualSearchRankerLogger rankerLogger) {
         int x = (int) mX;
         int y = (int) mY;
 
@@ -362,9 +364,8 @@
         ChromePreferenceManager prefs = ChromePreferenceManager.getInstance();
         int adjustedTapsSinceOpen = prefs.getContextualSearchTapCount()
                 - prefs.getContextualSearchTapQuickAnswerCount();
-        TapSuppressionHeuristics tapHeuristics =
-                new TapSuppressionHeuristics(this, mLastTapState, x, y, adjustedTapsSinceOpen);
-
+        TapSuppressionHeuristics tapHeuristics = new TapSuppressionHeuristics(
+                this, mLastTapState, x, y, adjustedTapsSinceOpen, contextualSearchContext);
         // TODO(donnd): Move to be called when the panel closes to work with states that change.
         tapHeuristics.logConditionState();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
index b1acdc6..589c9767 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
@@ -803,6 +803,47 @@
     }
 
     /**
+     * Log whether results were seen due to a Tap on a short word.
+     * @param wasSearchContentViewSeen If the panel was opened.
+     * @param isTapOnShortWord Whether this tap was on a "short" word.
+     */
+    public static void logTapShortWordSeen(
+            boolean wasSearchContentViewSeen, boolean isTapOnShortWord) {
+        if (!isTapOnShortWord) return;
+
+        // We just record CTR of short words.
+        RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapShortWordSeen",
+                wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
+    }
+
+    /**
+     * Log whether results were seen due to a Tap on a long word.
+     * @param wasSearchContentViewSeen If the panel was opened.
+     * @param isTapOnLongWord Whether this tap was on a long word.
+     */
+    public static void logTapLongWordSeen(
+            boolean wasSearchContentViewSeen, boolean isTapOnLongWord) {
+        if (!isTapOnLongWord) return;
+
+        RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapLongWordSeen",
+                wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
+    }
+
+    /**
+     * Log whether results were seen due to a Tap that was on the middle of a word.
+     * @param wasSearchContentViewSeen If the panel was opened.
+     * @param isTapOnWordMiddle Whether this tap was on the middle of a word.
+     */
+    public static void logTapOnWordMiddleSeen(
+            boolean wasSearchContentViewSeen, boolean isTapOnWordMiddle) {
+        if (!isTapOnWordMiddle) return;
+
+        // We just record CTR of words tapped in the "middle".
+        RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchTapOnWordMiddleSeen",
+                wasSearchContentViewSeen ? RESULTS_SEEN : RESULTS_NOT_SEEN, RESULTS_SEEN_BOUNDARY);
+    }
+
+    /**
      * Logs whether results were seen and whether any tap suppression heuristics were satisfied.
      * @param wasSearchContentViewSeen If the panel was opened.
      * @param wasAnySuppressionHeuristicSatisfied Whether any of the implemented suppression
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
index fb27502..c12da127 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
@@ -19,22 +19,19 @@
      * @param tapsSinceOpen the number of Tap gestures since the last open of the panel.
      */
     TapSuppressionHeuristics(ContextualSearchSelectionController selectionController,
-            ContextualSearchTapState previousTapState, int x, int y, int tapsSinceOpen) {
+            ContextualSearchTapState previousTapState, int x, int y, int tapsSinceOpen,
+            ContextualSearchContext contextualSearchContext) {
         super();
         mCtrSuppression = new CtrSuppression();
         mHeuristics.add(mCtrSuppression);
-        RecentScrollTapSuppression scrollTapExperiment =
-                new RecentScrollTapSuppression(selectionController);
-        mHeuristics.add(scrollTapExperiment);
+        mHeuristics.add(new RecentScrollTapSuppression(selectionController));
         TapFarFromPreviousSuppression farFromPreviousHeuristic =
                 new TapFarFromPreviousSuppression(selectionController, previousTapState, x, y);
         mHeuristics.add(farFromPreviousHeuristic);
-        NearTopTapSuppression tapNearTopSuppression =
-                new NearTopTapSuppression(selectionController, y);
-        mHeuristics.add(tapNearTopSuppression);
-        BarOverlapTapSuppression barOverlapTapSuppression =
-                new BarOverlapTapSuppression(selectionController, y);
-        mHeuristics.add(barOverlapTapSuppression);
+        mHeuristics.add(new TapWordLengthSuppression(contextualSearchContext));
+        mHeuristics.add(new TapWordEdgeSuppression(contextualSearchContext));
+        mHeuristics.add(new NearTopTapSuppression(selectionController, y));
+        mHeuristics.add(new BarOverlapTapSuppression(selectionController, y));
         // General Tap Suppression and Tap Twice.
         TapSuppression tapSuppression =
                 new TapSuppression(selectionController, previousTapState, x, y, tapsSinceOpen);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java
new file mode 100644
index 0000000..0e25b12e
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.contextualsearch;
+
+import android.text.TextUtils;
+
+/**
+ * Implements the policy that a Tap relatively far away from the middle of a word should be
+ * ignored.  When a Tap is close to the middle of the word tapped it's treated normally.
+ */
+class TapWordEdgeSuppression extends ContextualSearchHeuristic {
+    private static final int INVALID_OFFSET = ContextualSearchContext.INVALID_OFFSET;
+    private static final int MIN_WORD_LENGTH = 4;
+    private static final double MIN_WORD_START_RATIO = 0.25;
+    private static final double MIN_WORD_END_RATIO = 0.25;
+
+    private final boolean mIsSuppressionEnabled;
+    private final boolean mIsConditionSatisfied;
+
+    /**
+     * Constructs a heuristic to determine if the current Tap is close to the edge of the word.
+     * @param contextualSearchContext The current {@link ContextualSearchContext} so we can figure
+     *        out what part of the word has been tapped.
+     */
+    TapWordEdgeSuppression(ContextualSearchContext contextualSearchContext) {
+        mIsSuppressionEnabled = ContextualSearchFieldTrial.isWordEdgeSuppressionEnabled();
+        mIsConditionSatisfied = isTapNearWordEdge(contextualSearchContext);
+    }
+
+    @Override
+    protected boolean isConditionSatisfiedAndEnabled() {
+        return mIsSuppressionEnabled && mIsConditionSatisfied;
+    }
+
+    @Override
+    protected void logResultsSeen(boolean wasSearchContentViewSeen, boolean wasActivatedByTap) {
+        if (wasActivatedByTap) {
+            ContextualSearchUma.logTapOnWordMiddleSeen(
+                    wasSearchContentViewSeen, !mIsConditionSatisfied);
+        }
+    }
+
+    @Override
+    protected boolean shouldAggregateLogForTapSuppression() {
+        return true;
+    }
+
+    @Override
+    protected boolean isConditionSatisfiedForAggregateLogging() {
+        return mIsConditionSatisfied;
+    }
+
+    /**
+     * Whether the tap is near the word edge and not a second-tap (tap following a suppressed tap).
+     * @param contextualSearchContext The {@link ContextualSearchContext}, used to determine how
+     *        close the tap offset is to the word edges.
+     * @return Whether the tap is near an edge and not a second tap.
+     */
+    private boolean isTapNearWordEdge(ContextualSearchContext contextualSearchContext) {
+        // If setup failed, don't suppress.
+        String tappedWord = contextualSearchContext.getTappedWord();
+        int tapOffset = contextualSearchContext.getTappedWordOffset();
+        if (TextUtils.isEmpty(tappedWord) || tapOffset == INVALID_OFFSET) return false;
+
+        // If the word is long enough, suppress if the tap was near one end or the other.
+        boolean isInStartEdge = (double) tapOffset / tappedWord.length() < MIN_WORD_START_RATIO;
+        boolean isInEndEdge = (double) (tappedWord.length() - tapOffset) / tappedWord.length()
+                < MIN_WORD_END_RATIO;
+        if (tappedWord.length() >= MIN_WORD_LENGTH && (isInStartEdge || isInEndEdge)) return true;
+
+        return false;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java
new file mode 100644
index 0000000..11a039d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.contextualsearch;
+
+import android.text.TextUtils;
+
+/**
+ * Implements signals for Taps on short and long words.
+ * This signal could be used for suppression when the word is short, so we aggregate-log too.
+ * We log CTR to UMA for Taps on both short and long words.
+ */
+class TapWordLengthSuppression extends ContextualSearchHeuristic {
+    private static final int MAXIMUM_SHORT_WORD_LENGTH = 3;
+    private static final int MAXIMUM_NOT_LONG_WORD_LENGTH = 9;
+
+    private final boolean mIsShortWordSuppressionEnabled;
+    private final boolean mIsNotLongWordSuppressionEnabled;
+    private final boolean mIsShortWordConditionSatisfied;
+    private final boolean mIsNotLongWordConditionSatisfied;
+
+    /**
+     * Constructs a heuristic to categorize the Tap based on word length of the tapped word.
+     * @param contextualSearchContext The current {@link ContextualSearchContext} so we can inspect
+     *        the word tapped.
+     */
+    TapWordLengthSuppression(ContextualSearchContext contextualSearchContext) {
+        mIsShortWordSuppressionEnabled = ContextualSearchFieldTrial.isShortWordSuppressionEnabled();
+        mIsNotLongWordSuppressionEnabled =
+                ContextualSearchFieldTrial.isNotLongWordSuppressionEnabled();
+        mIsShortWordConditionSatisfied =
+                !isTapOnWordLongerThan(MAXIMUM_SHORT_WORD_LENGTH, contextualSearchContext);
+        mIsNotLongWordConditionSatisfied =
+                !isTapOnWordLongerThan(MAXIMUM_NOT_LONG_WORD_LENGTH, contextualSearchContext);
+    }
+
+    @Override
+    protected boolean isConditionSatisfiedAndEnabled() {
+        return mIsShortWordSuppressionEnabled && mIsShortWordConditionSatisfied
+                || mIsNotLongWordSuppressionEnabled && mIsNotLongWordConditionSatisfied;
+    }
+
+    @Override
+    protected void logResultsSeen(boolean wasSearchContentViewSeen, boolean wasActivatedByTap) {
+        if (wasActivatedByTap) {
+            ContextualSearchUma.logTapShortWordSeen(
+                    wasSearchContentViewSeen, mIsShortWordConditionSatisfied);
+            // Log CTR of long words, since not-long word CTR is probably not useful.
+            ContextualSearchUma.logTapLongWordSeen(
+                    wasSearchContentViewSeen, !mIsNotLongWordConditionSatisfied);
+        }
+    }
+
+    @Override
+    protected boolean shouldAggregateLogForTapSuppression() {
+        return true;
+    }
+
+    @Override
+    protected boolean isConditionSatisfiedForAggregateLogging() {
+        // Short-word suppression is a good candidate for aggregate logging of overall suppression.
+        return mIsShortWordConditionSatisfied;
+    }
+
+    /**
+     * @return Whether the tap is on a word longer than the given word length maximum.
+     */
+    private boolean isTapOnWordLongerThan(
+            int maxWordLength, ContextualSearchContext contextualSearchContext) {
+        // If setup failed, don't suppress.
+        String tappedWord = contextualSearchContext.getTappedWord();
+        return !TextUtils.isEmpty(tappedWord) && tappedWord.length() > maxWordLength;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index 40e1851..ce22e1e9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -73,6 +73,7 @@
 import org.chromium.chrome.browser.webapps.ChromeWebApkHost;
 import org.chromium.chrome.browser.webapps.WebApkVersionManager;
 import org.chromium.chrome.browser.webapps.WebappRegistry;
+import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.components.signin.AccountManagerHelper;
 import org.chromium.content.browser.ChildProcessLauncher;
@@ -400,6 +401,13 @@
                 }
             }
         });
+
+        deferredStartupHandler.addDeferredTask(new Runnable() {
+            @Override
+            public void run() {
+                BackgroundTaskSchedulerFactory.getScheduler().checkForOSUpgrade(application);
+            }
+        });
     }
 
     private void initChannelsAsync() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
index 34120dc..a03d62d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.locale;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
@@ -22,7 +21,6 @@
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.widget.PromoDialog;
 import org.chromium.chrome.browser.widget.RadioButtonLayout;
-import org.chromium.ui.base.WindowAndroid;
 
 /** A dialog that forces the user to choose a default search engine. */
 public class DefaultSearchEnginePromoDialog extends PromoDialog {
@@ -46,11 +44,11 @@
      * Construct and show the dialog.  Will be asynchronous if the TemplateUrlService has not yet
      * been loaded.
      *
-     * @param context     Context to build the dialog with.
+     * @param activity    Activity to build the dialog with.
      * @param dialogType  Type of dialog to show.
      * @param onDismissed Notified about whether the user chose an engine when it got dismissed.
      */
-    public static void show(final Context context, @SearchEnginePromoType final int dialogType,
+    public static void show(final Activity activity, @SearchEnginePromoType final int dialogType,
             @Nullable final Callback<Boolean> onDismissed) {
         assert LibraryLoader.isInitialized();
 
@@ -61,21 +59,20 @@
             public void onTemplateUrlServiceLoaded() {
                 instance.unregisterLoadListener(this);
 
-                Activity activity = WindowAndroid.activityFromContext(context);
                 if (ApplicationStatus.getStateForActivity(activity) == ActivityState.DESTROYED) {
                     if (onDismissed != null) onDismissed.onResult(false);
                     return;
                 }
 
-                new DefaultSearchEnginePromoDialog(context, dialogType, onDismissed).show();
+                new DefaultSearchEnginePromoDialog(activity, dialogType, onDismissed).show();
             }
         });
         if (!instance.isLoaded()) instance.load();
     }
 
     private DefaultSearchEnginePromoDialog(
-            Context context, int dialogType, @Nullable Callback<Boolean> onDismissed) {
-        super(context);
+            Activity activity, int dialogType, @Nullable Callback<Boolean> onDismissed) {
+        super(activity);
         mDialogType = dialogType;
         mOnDismissed = onDismissed;
         setOnDismissListener(this);
@@ -129,8 +126,8 @@
     @Override
     public void onDismiss(DialogInterface dialog) {
         if (mHelper.getCurrentlySelectedKeyword() == null) {
-            // This shouldn't happen, but in case it does, finish the Activity so that the user has
-            // to respond to the dialog next time.
+            // If no selection, finish the Activity so that the user has to respond to the dialog
+            // next time.
             if (getOwnerActivity() != null) getOwnerActivity().finish();
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
index ef9bf57..19491e4f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.locale;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -208,22 +209,22 @@
      * Shows a promotion dialog about search engines depending on Locale and other conditions.
      * See {@link LocaleManager#getSearchEnginePromoShowType()} for possible types and logic.
      *
-     * @param context     Context showing the dialog.
+     * @param activity    Activity showing the dialog.
      * @param onDismissed Notified when the dialog is dismissed and whether the user acted on it.
      * @return Whether such dialog is needed.
      */
     public boolean showSearchEnginePromoIfNeeded(
-            Context context, @Nullable Callback<Boolean> onDismissed) {
+            Activity activity, @Nullable Callback<Boolean> onDismissed) {
         int shouldShow = getSearchEnginePromoShowType();
         switch (shouldShow) {
             case SEARCH_ENGINE_PROMO_DONT_SHOW:
                 return false;
             case SEARCH_ENGINE_PROMO_SHOW_SOGOU:
-                new SogouPromoDialog(context, this, onDismissed).show();
+                new SogouPromoDialog(activity, this, onDismissed).show();
                 return true;
             case SEARCH_ENGINE_PROMO_SHOW_EXISTING:
             case SEARCH_ENGINE_PROMO_SHOW_NEW:
-                DefaultSearchEnginePromoDialog.show(context, shouldShow, onDismissed);
+                DefaultSearchEnginePromoDialog.show(activity, shouldShow, onDismissed);
                 return true;
             default:
                 assert false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
index 3b9244e..144a7ba0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.locale;
 
-import android.content.Context;
+import android.app.Activity;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
@@ -68,9 +68,9 @@
     /**
      * Creates an instance of the dialog.
      */
-    public SogouPromoDialog(
-            Context context, LocaleManager localeManager, @Nullable Callback<Boolean> onDismissed) {
-        super(context);
+    public SogouPromoDialog(Activity activity, LocaleManager localeManager,
+            @Nullable Callback<Boolean> onDismissed) {
+        super(activity);
         mLocaleManager = localeManager;
         setOnDismissListener(this);
         setCanceledOnTouchOutside(false);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionPromoScreen.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionPromoScreen.java
index 818e58e2..34ed542 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionPromoScreen.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionPromoScreen.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.preferences.datareduction;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.view.View;
 
@@ -42,10 +41,10 @@
     /**
      * DataReductionPromoScreen constructor.
      *
-     * @param context An Android context.
+     * @param activity An Android activity to display the dialog.
      */
-    public DataReductionPromoScreen(Context context) {
-        super(context);
+    public DataReductionPromoScreen(Activity activity) {
+        super(activity);
         mState = DataReductionProxyUma.ACTION_DISMISSED;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index 3d3f272..52d0523 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -936,7 +936,8 @@
         // mListeningForWebVrActivate for them.
         if (mVrSupportLevel != VR_DAYDREAM) return;
         mListeningForWebVrActivate = listening;
-        if (listening && !mPaused) {
+        if (mPaused) return;
+        if (listening) {
             registerDaydreamIntent(mVrDaydreamApi, mActivity);
             if (mAutopresentWebVr) {
                 // Dispatch vrdisplayactivate so that the WebVr page can call requestPresent
@@ -946,7 +947,7 @@
                 // UI which is suboptimal.
                 nativeDisplayActivate(mNativeVrShellDelegate);
             }
-        } else {
+        } else if (!canEnterVr(mActivity.getActivityTab())) {
             unregisterDaydreamIntent(mVrDaydreamApi);
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
index cd3b51b..30626367 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
@@ -4,8 +4,7 @@
 
 package org.chromium.chrome.browser.widget;
 
-import android.app.Dialog;
-import android.content.Context;
+import android.app.Activity;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.view.LayoutInflater;
@@ -19,8 +18,8 @@
 /**
  * Generic builder for promo dialogs.
  */
-public abstract class PromoDialog
-        extends Dialog implements View.OnClickListener, DialogInterface.OnDismissListener {
+public abstract class PromoDialog extends AlwaysDismissedDialog
+        implements View.OnClickListener, DialogInterface.OnDismissListener {
     /** Parameters that can be used to create a new PromoDialog. */
     public static class DialogParams {
         /**
@@ -56,13 +55,13 @@
     private final FrameLayout mScrimView;
     private final PromoDialogLayout mDialogLayout;
 
-    protected PromoDialog(Context context) {
-        super(context, R.style.PromoDialog);
+    protected PromoDialog(Activity activity) {
+        super(activity, R.style.PromoDialog);
 
-        mScrimView = new FrameLayout(context);
+        mScrimView = new FrameLayout(activity);
         mScrimView.setBackgroundColor(ApiCompatibilityUtils.getColor(
-                context.getResources(), R.color.modal_dialog_scrim_color));
-        LayoutInflater.from(context).inflate(R.layout.promo_dialog_layout, mScrimView, true);
+                activity.getResources(), R.color.modal_dialog_scrim_color));
+        LayoutInflater.from(activity).inflate(R.layout.promo_dialog_layout, mScrimView, true);
 
         mDialogLayout = (PromoDialogLayout) mScrimView.findViewById(R.id.promo_dialog_layout);
         mDialogLayout.initialize(getDialogParams());
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index b64a883c..4d9e84e99 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -262,6 +262,8 @@
   "java/src/org/chromium/chrome/browser/contextualsearch/TapFarFromPreviousSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
+  "java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java",
+  "java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java",
   "java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
   "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
   "java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobService.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/WebVrTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/WebVrTest.java
index 1fde63c6..d436ba18 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/WebVrTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/WebVrTest.java
@@ -23,6 +23,7 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
@@ -161,7 +162,8 @@
      * Tests that non-focused tabs cannot get pose information.
      */
     @Test
-    @SmallTest
+    // @SmallTest
+    @DisabledTest(message = "Flaky. http://crbug.com/726986")
     public void testPoseDataUnfocusedTab() throws InterruptedException {
         mVrTestRule.loadUrlAndAwaitInitialization(
                 VrTestRule.getHtmlTestFile("test_pose_data_unfocused_tab"), PAGE_LOAD_TIMEOUT_S);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java
index c9287f7..4871303 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.widget;
 
-import android.content.Context;
+import android.app.Activity;
 import android.content.DialogInterface;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -13,27 +13,39 @@
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.widget.PromoDialog.DialogParams;
-import org.chromium.chrome.test.util.ApplicationTestUtils;
+import org.chromium.chrome.test.ChromeActivityTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 
 import java.util.concurrent.Callable;
 
 /**
  * Tests for the PromoDialog and PromoDialogLayout.
  */
-@RunWith(BaseJUnit4ClassRunner.class)
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+        ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG,
+})
 public class PromoDialogTest {
+    // TODO(tedchoc): Find a way to introduce a lightweight activity that doesn't spin up the world.
+    //                crbug.com/728297
+    @Rule
+    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+
     /**
      * Creates a PromoDialog.  Doesn't call {@link PromoDialog#show} because there is no Window to
      * attach them to, but it does create them and inflate the layouts.
@@ -44,16 +56,15 @@
         public final PromoDialog dialog;
         public final PromoDialogLayout dialogLayout;
 
-        private final Context mContext;
         private final DialogParams mDialogParams;
 
-        PromoDialogWrapper(final DialogParams dialogParams) throws Exception {
-            mContext = InstrumentationRegistry.getTargetContext();
+        PromoDialogWrapper(final Activity activity, final DialogParams dialogParams)
+                throws Exception {
             mDialogParams = dialogParams;
             dialog = ThreadUtils.runOnUiThreadBlocking(new Callable<PromoDialog>() {
                 @Override
                 public PromoDialog call() throws Exception {
-                    PromoDialog dialog = new PromoDialog(mContext) {
+                    PromoDialog dialog = new PromoDialog(activity) {
                         @Override
                         public DialogParams getDialogParams() {
                             return mDialogParams;
@@ -105,13 +116,8 @@
     }
 
     @Before
-    public void setUp() {
-        ApplicationTestUtils.setUp(InstrumentationRegistry.getTargetContext(), true);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        ApplicationTestUtils.tearDown(InstrumentationRegistry.getTargetContext());
+    public void setUp() throws Exception {
+        mActivityTestRule.startMainActivityOnBlankPage();
     }
 
     @Test
@@ -136,7 +142,8 @@
 
     /** Confirm that PromoDialogs are constructed with all the elements expected. */
     private void checkDialogControlVisibility(final DialogParams dialogParams) throws Exception {
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
 
         View illustration = promoDialogLayout.findViewById(R.id.illustration);
@@ -175,7 +182,8 @@
         dialogParams.secondaryButtonStringResource = R.string.cancel;
         dialogParams.footerStringResource = R.string.learn_more;
 
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         final PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
         LinearLayout flippableLayout =
                 (LinearLayout) promoDialogLayout.findViewById(R.id.full_promo_content);
@@ -211,7 +219,8 @@
         dialogParams.primaryButtonStringResource = R.string.ok;
         dialogParams.secondaryButtonStringResource = R.string.cancel;
 
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         final PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
 
         // Nothing should have been clicked yet.
@@ -248,7 +257,8 @@
         dialogParams.headerStringResource = R.string.data_reduction_promo_title;
         dialogParams.primaryButtonStringResource = R.string.data_reduction_enable_button;
 
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
         ViewGroup scrollableLayout =
                 (ViewGroup) promoDialogLayout.findViewById(R.id.scrollable_promo_content);
@@ -267,7 +277,8 @@
         dialogParams.headerStringResource = R.string.search_with_sogou;
         dialogParams.primaryButtonStringResource = R.string.ok;
 
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
         ViewGroup scrollableLayout =
                 (ViewGroup) promoDialogLayout.findViewById(R.id.scrollable_promo_content);
@@ -286,7 +297,8 @@
         dialogParams.headerStringResource = R.string.search_with_sogou;
         dialogParams.primaryButtonStringResource = R.string.ok;
 
-        PromoDialogWrapper wrapper = new PromoDialogWrapper(dialogParams);
+        PromoDialogWrapper wrapper =
+                new PromoDialogWrapper(mActivityTestRule.getActivity(), dialogParams);
         PromoDialogLayout promoDialogLayout = wrapper.dialogLayout;
 
         // Add a dummy control view to ensure the scrolling container has some content.
diff --git a/chrome/app/bookmarks_strings.grdp b/chrome/app/bookmarks_strings.grdp
index d6d070a..22d0be7 100644
--- a/chrome/app/bookmarks_strings.grdp
+++ b/chrome/app/bookmarks_strings.grdp
@@ -383,8 +383,8 @@
   <message name="IDS_MD_BOOKMARK_MANAGER_CLEAR_SEARCH" desc="Title of the button in the bookmark manager that stops a search.">
     Clear search
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_EMPTY_LIST" desc="Text on the bookmarks list that indicates that a folder has no bookmarks.">
-    This folder is empty
+  <message name="IDS_MD_BOOKMARK_MANAGER_EMPTY_LIST" desc="The message shown when the user has no bookmarks added. 'Star' refers to the icon in the omnibox for adding to bookmarks.">
+    To bookmark pages, click the star in the address bar
   </message>
   <message name="IDS_MD_BOOKMARK_MANAGER_FOLDER_RENAME_TITLE" desc="Title of the bookmark editor window when editing folders.">
     Rename Folder
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 1965435..b304bf8 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1183,9 +1183,11 @@
       </message>
 
       <!-- Plugin Placeholders -->
-      <message name="IDS_PLUGIN_RESTART_REQUIRED" desc="The placeholder text for a plugin that can't be loaded until the browser is restarted.">
-        Restart Chromium to enable <ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>
-      </message>
+      <if expr="is_linux">
+        <message name="IDS_PLUGIN_RESTART_REQUIRED" desc="The placeholder text for a plugin that can't be loaded until the browser is restarted.">
+          Restart Chromium to enable <ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>
+        </message>
+      </if>
 
       <!-- Desktop to iOS promotion -->
       <message name="IDS_PASSWORD_MANAGER_DESKTOP_TO_IOS_PROMO_TEXT" desc="Text for Chrome iOS Promotion appearing in the password bubble after a password is saved.">
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e13f52b..32ceee2a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1545,7 +1545,7 @@
         No search results found
       </message>
       <message name="IDS_MD_DOWNLOAD_NO_DOWNLOADS" desc="A message shown when the user has no downloads to show on chrome://downloads.">
-        Nothing to see here...
+        Files you download appear here
       </message>
       <message name="IDS_DOWNLOAD_LINK_RESUME"
                desc="In the download view, 'Resume' link text">
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index abb9a307..ed18a2a 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1193,9 +1193,11 @@
       </message>
 
       <!-- Plugin Placeholders -->
-      <message name="IDS_PLUGIN_RESTART_REQUIRED" desc="The placeholder text for a plugin that can't be loaded until the browser is restarted.">
-        Restart Chrome to enable <ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>
-      </message>
+      <if expr="is_linux">
+        <message name="IDS_PLUGIN_RESTART_REQUIRED" desc="The placeholder text for a plugin that can't be loaded until the browser is restarted.">
+          Restart Chrome to enable <ph name="PLUGIN_NAME">$1<ex>Flash</ex></ph>
+        </message>
+      </if>
 
       <!-- Desktop to iOS promotion -->
       <message name="IDS_PASSWORD_MANAGER_DESKTOP_TO_IOS_PROMO_TEXT" desc="Text for Chrome iOS Promotion appearing in the password bubble after a password is saved.">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 26f3eb9..e185ebc1 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -108,6 +108,7 @@
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "components/feature_engagement_tracker/public/feature_constants.h"
+#include "components/feature_engagement_tracker/public/feature_list.h"
 #else  // OS_ANDROID
 #include "ui/message_center/message_center_switches.h"
 #endif  // OS_ANDROID
@@ -1781,9 +1782,12 @@
      MULTI_VALUE_TYPE(kChromeHomeSwipeLogicChoices)},
 #endif  // OS_ANDROID
 #if defined(OS_ANDROID)
-    {"enable-iph-demo-mode", flag_descriptions::kEnableIphDemoModeName,
-     flag_descriptions::kEnableIphDemoModeDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(feature_engagement_tracker::kIPHDemoMode)},
+    {"iph-demo-mode-choice", flag_descriptions::kIphDemoModeChoiceName,
+     flag_descriptions::kIphDemoModeChoiceDescription, kOsAndroid,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(
+         feature_engagement_tracker::kIPHDemoMode,
+         feature_engagement_tracker::kIPHDemoModeChoiceVariations,
+         feature_engagement_tracker::kIPHDemoMode.name)},
 #endif  // OS_ANDROID
     {"num-raster-threads", flag_descriptions::kNumRasterThreadsName,
      flag_descriptions::kNumRasterThreadsDescription, kOsAll,
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 12d496f..7e31d29 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -93,8 +93,10 @@
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/blocked_content/blocked_window_params.h"
 #include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_select_file_policy.h"
 #include "chrome/browser/ui/sync/sync_promo_ui.h"
 #include "chrome/browser/ui/tab_contents/chrome_web_contents_view_delegate.h"
@@ -542,20 +544,30 @@
 
   // mash::mojom::Launchable:
   void Launch(uint32_t what, mash::mojom::LaunchMode how) override {
-    if (how != mash::mojom::LaunchMode::MAKE_NEW) {
-      LOG(ERROR) << "Unable to handle Launch request with how = " << how;
-      return;
-    }
+    bool is_incognito;
     switch (what) {
       case mash::mojom::kWindow:
-        CreateNewWindowImpl(false /* is_incognito */);
+        is_incognito = false;
         break;
       case mash::mojom::kIncognitoWindow:
-        CreateNewWindowImpl(true /* is_incognito */);
+        is_incognito = true;
         break;
       default:
         NOTREACHED();
     }
+
+    bool reuse = how != mash::mojom::LaunchMode::MAKE_NEW;
+    if (reuse) {
+      Profile* profile = ProfileManager::GetActiveUserProfile();
+      Browser* browser = chrome::FindTabbedBrowser(
+          is_incognito ? profile->GetOffTheRecordProfile() : profile, false);
+      if (browser) {
+        browser->window()->Show();
+        return;
+      }
+    }
+
+    CreateNewWindowImpl(is_incognito);
   }
 
   void Create(const service_manager::BindSourceInfo& source_info,
diff --git a/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc b/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
index 32ff477..b653898 100644
--- a/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
+++ b/chrome/browser/chromeos/display/touch_calibrator/touch_calibrator_view.cc
@@ -554,8 +554,8 @@
   touch_point_view_->SetPaintToLayer();
   touch_point_view_->layer()->SetFillsBoundsOpaquely(false);
   touch_point_view_->layer()->GetAnimator()->AddObserver(this);
-  touch_point_view_->set_background(
-      views::Background::CreateSolidBackground(SK_ColorTRANSPARENT));
+  touch_point_view_->SetBackground(
+      views::CreateSolidBackground(SK_ColorTRANSPARENT));
 
   touch_point_view_->AddChildView(throbber_circle_);
   touch_point_view_->AddChildView(tap_label_);
@@ -612,8 +612,8 @@
   completion_message_view_->SetPaintToLayer();
   completion_message_view_->layer()->SetFillsBoundsOpaquely(false);
   completion_message_view_->layer()->GetAnimator()->AddObserver(this);
-  completion_message_view_->set_background(
-      views::Background::CreateSolidBackground(SK_ColorTRANSPARENT));
+  completion_message_view_->SetBackground(
+      views::CreateSolidBackground(SK_ColorTRANSPARENT));
 
   AddChildView(completion_message_view_);
 }
diff --git a/chrome/browser/chromeos/logging.cc b/chrome/browser/chromeos/logging.cc
index 41a1302..5cf1d0de 100644
--- a/chrome/browser/chromeos/logging.cc
+++ b/chrome/browser/chromeos/logging.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/task_scheduler/post_task.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/logging_chrome.h"
 #include "content/public/browser/browser_thread.h"
 
@@ -51,8 +52,8 @@
     return;
   }
 
-  DCHECK(!chrome_logging_redirected_)
-      << "Attempted to redirect logging when it was already initialized.";
+  if (command_line.HasSwitch(switches::kDisableLoggingRedirect))
+    return;
 
   // Redirect logs to the session log directory, if set.  Otherwise
   // defaults to the profile dir.
diff --git a/chrome/browser/chromeos/login/ui/simple_web_view_dialog.cc b/chrome/browser/chromeos/login/ui/simple_web_view_dialog.cc
index f395a02e3..850938e 100644
--- a/chrome/browser/chromeos/login/ui/simple_web_view_dialog.cc
+++ b/chrome/browser/chromeos/login/ui/simple_web_view_dialog.cc
@@ -58,7 +58,7 @@
 class ToolbarRowView : public views::View {
  public:
   ToolbarRowView() {
-    set_background(views::Background::CreateSolidBackground(kDialogColor));
+    SetBackground(views::CreateSolidBackground(kDialogColor));
   }
 
   ~ToolbarRowView() override {}
@@ -168,7 +168,7 @@
   toolbar_model_.reset(
       new ToolbarModelImpl(this, content::kMaxURLDisplayChars));
 
-  set_background(views::Background::CreateSolidBackground(kDialogColor));
+  SetBackground(views::CreateSolidBackground(kDialogColor));
 
   // Back/Forward buttons.
   back_ = new views::ImageButton(this);
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ee3cb30..81d4b8e3 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1991,10 +1991,10 @@
 
 #if defined(OS_ANDROID)
 
-const char kEnableIphDemoModeName[] = "In-Product Help Demo Mode";
+const char kIphDemoModeChoiceName[] = "In-Product Help Demo Mode";
 
-const char kEnableIphDemoModeDescription[] =
-    "Enables In-Product Help demo mode on Android.";
+const char kIphDemoModeChoiceDescription[] =
+    "Selects the In-Product Help demo mode.";
 
 #endif  // defined(OS_ANDROID)
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1c2adae..a6f9845c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -908,9 +908,6 @@
 extern const char kEnableDataReductionProxySiteBreakdownName[];
 extern const char kEnableDataReductionProxySiteBreakdownDescription[];
 
-extern const char kEnableIphDemoModeName[];
-extern const char kEnableIphDemoModeDescription[];
-
 extern const char kEnableOmniboxClipboardProviderName[];
 extern const char kEnableOmniboxClipboardProviderDescription[];
 
@@ -988,6 +985,9 @@
 extern const char kHerbPrototypeChoicesDescription[];
 extern const char kHerbPrototypeFlavorElderberry[];
 
+extern const char kIphDemoModeChoiceName[];
+extern const char kIphDemoModeChoiceDescription[];
+
 extern const char kLsdPermissionPromptName[];
 extern const char kLsdPermissionPromptDescription[];
 
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
index b7a7ddf9..99e8752 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -283,7 +283,7 @@
 #endif
 }
 
-#if defined(USE_X11) && !defined(OS_CHROMEOS)
+#if defined(OS_LINUX) && defined(USE_X11) && !defined(OS_CHROMEOS)
 UMALinuxWindowManager GetLinuxWindowManager() {
   switch (ui::GuessWindowManager()) {
     case ui::WM_OTHER:
diff --git a/chrome/browser/plugins/plugin_info_message_filter.cc b/chrome/browser/plugins/plugin_info_message_filter.cc
index 8c31c98..917e3db 100644
--- a/chrome/browser/plugins/plugin_info_message_filter.cc
+++ b/chrome/browser/plugins/plugin_info_message_filter.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/memory/ptr_util.h"
 #include "base/memory/singleton.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
@@ -497,10 +498,10 @@
       output->status =
           ChromeViewHostMsg_GetPluginInfo_Status::kRestartRequired;
     }
-#endif  // defined(OS_LINUX)
-    plugin_metadata.reset(new PluginMetadata(
+#endif
+    plugin_metadata = base::MakeUnique<PluginMetadata>(
         cus_plugin_info->id, cus_plugin_info->name, false, GURL(), GURL(),
-        base::ASCIIToUTF16(cus_plugin_info->id), std::string()));
+        base::ASCIIToUTF16(cus_plugin_info->id), std::string());
   }
   GetPluginInfoReply(params, std::move(output), std::move(plugin_metadata),
                      reply_msg);
diff --git a/chrome/browser/prefs/active_profile_pref_service.cc b/chrome/browser/prefs/active_profile_pref_service.cc
index 35293d8..5bbb03a 100644
--- a/chrome/browser/prefs/active_profile_pref_service.cc
+++ b/chrome/browser/prefs/active_profile_pref_service.cc
@@ -29,6 +29,17 @@
                           std::move(callback));
 }
 
+void ActiveProfilePrefService::ConnectToUserPrefStore(
+    const std::vector<std::string>& prefs_to_observe,
+    ConnectToUserPrefStoreCallback callback) {
+  auto* connector = content::BrowserContext::GetConnectorFor(
+      ProfileManager::GetActiveUserProfile()->GetOriginalProfile());
+  connector->BindInterface(prefs::mojom::kServiceName, &connector_ptr_);
+  connector_ptr_.set_connection_error_handler(base::Bind(
+      &ActiveProfilePrefService::OnConnectError, base::Unretained(this)));
+  connector_ptr_->ConnectToUserPrefStore(prefs_to_observe, std::move(callback));
+}
+
 void ActiveProfilePrefService::Create(
     const service_manager::BindSourceInfo& source_info,
     prefs::mojom::PrefStoreConnectorRequest request) {
diff --git a/chrome/browser/prefs/active_profile_pref_service.h b/chrome/browser/prefs/active_profile_pref_service.h
index c2e2c29..ca0674d 100644
--- a/chrome/browser/prefs/active_profile_pref_service.h
+++ b/chrome/browser/prefs/active_profile_pref_service.h
@@ -32,6 +32,8 @@
       prefs::mojom::PrefRegistryPtr pref_registry,
       const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
       ConnectCallback callback) override;
+  void ConnectToUserPrefStore(const std::vector<std::string>& prefs_to_observe,
+                              ConnectToUserPrefStoreCallback callback) override;
 
   // service_manager::Service:
   void OnStart() override;
diff --git a/chrome/browser/prefs/profile_pref_store_manager.h b/chrome/browser/prefs/profile_pref_store_manager.h
index 95757a67..ee38a40 100644
--- a/chrome/browser/prefs/profile_pref_store_manager.h
+++ b/chrome/browser/prefs/profile_pref_store_manager.h
@@ -14,7 +14,7 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 #include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
 
 class PersistentPrefStore;
diff --git a/chrome/browser/resources/chromeos/login/md_login_shared.js b/chrome/browser/resources/chromeos/login/md_login_shared.js
index 41afbaa..195102d 100644
--- a/chrome/browser/resources/chromeos/login/md_login_shared.js
+++ b/chrome/browser/resources/chromeos/login/md_login_shared.js
@@ -425,6 +425,12 @@
   Oobe.setVirtualKeyboardShown = function(shown) {
     Oobe.getInstance().virtualKeyboardShown = shown;
     $('pod-row').setFocusedPodPinVisibility(!shown);
+    // The dark overlay should always have the same size with the larger one
+    // of the outer-container and the scroll-container.
+    if (shown)
+      $('login-shield').style.minHeight = $('outer-container').style.minHeight;
+    else
+      $('login-shield').style.minHeight = 'unset';
   };
 
   /**
diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js
index 116b1c64..19499638 100644
--- a/chrome/browser/resources/print_preview/data/destination_store.js
+++ b/chrome/browser/resources/print_preview/data/destination_store.js
@@ -1078,7 +1078,8 @@
     startLoadLocalDestinations: function() {
       if (!this.hasLoadedAllLocalDestinations_) {
         this.hasLoadedAllLocalDestinations_ = true;
-        this.nativeLayer_.startGetLocalDestinations();
+        this.nativeLayer_.getPrinters().then(
+            this.onLocalDestinationsSet_.bind(this));
         this.isLocalDestinationSearchInProgress_ = true;
         cr.dispatchSimpleEvent(
             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
@@ -1362,10 +1363,6 @@
       var nativeLayerEventTarget = this.nativeLayer_.getEventTarget();
       this.tracker_.add(
           nativeLayerEventTarget,
-          print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET,
-          this.onLocalDestinationsSet_.bind(this));
-      this.tracker_.add(
-          nativeLayerEventTarget,
           print_preview.NativeLayer.EventType.CAPABILITIES_SET,
           this.onLocalDestinationCapabilitiesSet_.bind(this));
       this.tracker_.add(
@@ -1437,11 +1434,12 @@
 
     /**
      * Called when the local destinations have been got from the native layer.
-     * @param {Event} event Contains the local destinations.
+     * @param {!Array<!print_preview.LocalDestinationInfo>} destinationInfos A
+     *     list of the local destinations retrieved.
      * @private
      */
-    onLocalDestinationsSet_: function(event) {
-      var localDestinations = event.destinationInfos.map(function(destInfo) {
+    onLocalDestinationsSet_: function(destinationInfos) {
+      var localDestinations = destinationInfos.map(function(destInfo) {
         return print_preview.LocalDestinationParser.parse(destInfo);
       });
       this.insertDestinations_(localDestinations);
diff --git a/chrome/browser/resources/print_preview/data/local_parsers.js b/chrome/browser/resources/print_preview/data/local_parsers.js
index 4155f5a..97cceab 100644
--- a/chrome/browser/resources/print_preview/data/local_parsers.js
+++ b/chrome/browser/resources/print_preview/data/local_parsers.js
@@ -10,8 +10,8 @@
 
   /**
    * Parses a local print destination.
-   * @param {!Object} destinationInfo Information describing a local print
-   *     destination.
+   * @param {!print_preview.LocalDestinationInfo} destinationInfo Information
+   *     describing a local print destination.
    * @return {!print_preview.Destination} Parsed local print destination.
    */
   LocalDestinationParser.parse = function(destinationInfo) {
diff --git a/chrome/browser/resources/print_preview/native_layer.js b/chrome/browser/resources/print_preview/native_layer.js
index d3a12f02..4ad76dea 100644
--- a/chrome/browser/resources/print_preview/native_layer.js
+++ b/chrome/browser/resources/print_preview/native_layer.js
@@ -17,6 +17,17 @@
 
 /**
  * @typedef {{
+ *   deviceName: string,
+ *   printerName: string,
+ *   printerDescription: (string | undefined),
+ *   cupsEnterprisePrinter: (boolean | undefined),
+ *   printerOptions: (Object | undefined),
+ * }}
+ */
+print_preview.LocalDestinationInfo;
+
+/**
+ * @typedef {{
  *   printerId: string,
  *   success: boolean,
  *   capabilities: Object,
@@ -34,7 +45,6 @@
   function NativeLayer() {
     // Bind global handlers
     global.setUseCloudPrint = this.onSetUseCloudPrint_.bind(this);
-    global.setPrinters = this.onSetPrinters_.bind(this);
     global.updateWithPrinterCapabilities =
         this.onUpdateWithPrinterCapabilities_.bind(this);
     global.failedToGetPrinterCapabilities =
@@ -220,9 +230,10 @@
     /**
      * Requests the system's local print destinations. A LOCAL_DESTINATIONS_SET
      * event will be dispatched in response.
+     * @return {!Promise<!Array<print_preview.LocalDestinationInfo>>}
      */
-    startGetLocalDestinations: function() {
-      chrome.send('getPrinters');
+    getPrinters: function() {
+      return cr.sendWithPromise('getPrinters');
     },
 
     /**
@@ -550,19 +561,6 @@
     },
 
     /**
-     * Updates the print preview with local printers.
-     * Called from PrintPreviewHandler::SetupPrinterList().
-     * @param {Array} printers Array of printer info objects.
-     * @private
-     */
-    onSetPrinters_: function(printers) {
-      var localDestsSetEvent = new Event(
-          NativeLayer.EventType.LOCAL_DESTINATIONS_SET);
-      localDestsSetEvent.destinationInfos = printers;
-      this.eventTarget_.dispatchEvent(localDestsSetEvent);
-    },
-
-    /**
      * Called when native layer gets settings information for a requested local
      * destination.
      * @param {Object} settingsInfo printer setting information.
diff --git a/chrome/browser/resources/settings/about_page/about_page.html b/chrome/browser/resources/settings/about_page/about_page.html
index f4da9748..05b00cf 100644
--- a/chrome/browser/resources/settings/about_page/about_page.html
+++ b/chrome/browser/resources/settings/about_page/about_page.html
@@ -36,7 +36,7 @@
       }
 
       .copyable {
-        -webkit-user-select: text;
+        user-select: text;
       }
 
       .info-section {
@@ -107,7 +107,8 @@
                   src="[[getIconSrc_(obsoleteSystemInfo_, currentUpdateStatusEvent_)]]">
               </iron-icon>
               <div class="start padded">
-                <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]"
+                <div id="updateStatusMessage" class="copyable"
+                    hidden="[[!showUpdateStatus_]]"
 <if expr="not chromeos">
                     inner-h-t-m-l="[[getUpdateStatusMessage_(
                         currentUpdateStatusEvent_)]]">
diff --git a/chrome/browser/resources/settings/site_settings/site_details.html b/chrome/browser/resources/settings/site_settings/site_details.html
index 3e902fe..7cc87ab5 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.html
+++ b/chrome/browser/resources/settings/site_settings/site_details.html
@@ -5,6 +5,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../icons.html">
 <link rel="import" href="../route.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="constants.html">
@@ -90,6 +91,10 @@
           icon="cr:extension" id="plugins" label="$i18n{siteSettingsFlash}"
           site="[[site]]">
       </site-details-permission>
+      <site-details-permission category="{{ContentSettingsTypes.IMAGES}}"
+          icon="settings:photo" id="images" label="$i18n{siteSettingsImages}"
+          site="[[site]]">
+      </site-details-permission>
       <site-details-permission category="{{ContentSettingsTypes.POPUPS}}"
           icon="cr:open-in-new" id="popups" label="$i18n{siteSettingsPopups}"
           site="[[site]]">
diff --git a/chrome/browser/ui/cocoa/hover_close_button.mm b/chrome/browser/ui/cocoa/hover_close_button.mm
index f025a58..973ef4f 100644
--- a/chrome/browser/ui/cocoa/hover_close_button.mm
+++ b/chrome/browser/ui/cocoa/hover_close_button.mm
@@ -59,16 +59,18 @@
     gTooltip = [l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_CLOSE_TAB) copy];
 }
 
-- (id)initWithFrame:(NSRect)frameRect {
-  if ((self = [super initWithFrame:frameRect])) {
-    [self commonInit];
-  }
-  return self;
-}
+- (void)commonInit {
+  [super commonInit];
 
-- (void)awakeFromNib {
-  [super awakeFromNib];
-  [self commonInit];
+  [self setAccessibilityTitle:nil];
+
+  // Add a tooltip. Using 'owner:self' means that
+  // -view:stringForToolTip:point:userData: will be called to provide the
+  // tooltip contents immediately before showing it.
+  [self addToolTipRect:[self bounds] owner:self userData:NULL];
+
+  previousState_ = kHoverStateNone;
+  iconColor_ = kDefaultIconColor;
 }
 
 - (void)removeFromSuperview {
@@ -198,18 +200,6 @@
   }
 }
 
-- (void)commonInit {
-  [self setAccessibilityTitle:nil];
-
-  // Add a tooltip. Using 'owner:self' means that
-  // -view:stringForToolTip:point:userData: will be called to provide the
-  // tooltip contents immediately before showing it.
-  [self addToolTipRect:[self bounds] owner:self userData:NULL];
-
-  previousState_ = kHoverStateNone;
-  iconColor_ = kDefaultIconColor;
-}
-
 // Called each time a tooltip is about to be shown.
 - (NSString*)view:(NSView*)view
  stringForToolTip:(NSToolTipTag)tag
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
index aa6938e..52216a8 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
@@ -134,7 +134,7 @@
   AppListDialogContainer(views::View* dialog_body,
                          const base::Closure& close_callback)
       : BaseDialogContainer(dialog_body, close_callback) {
-    set_background(new AppListOverlayBackground());
+    SetBackground(base::MakeUnique<AppListOverlayBackground>());
     close_button_ = views::BubbleFrameView::CreateCloseButton(this);
     AddChildView(close_button_);
   }
diff --git a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc
index 1758bc0..ede410f 100644
--- a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc
+++ b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc
@@ -228,8 +228,7 @@
   storage_row_->SetLayoutManager(storage_row_layout);
   storage_row_->SetBorder(
       views::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
-  storage_row_->set_background(
-      views::Background::CreateSolidBackground(kLightShadingColor));
+  storage_row_->SetBackground(views::CreateSolidBackground(kLightShadingColor));
 
   storage_checkbox_ = new views::Checkbox(l10n_util::GetStringUTF16(
       IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX));
@@ -276,8 +275,7 @@
 void CardUnmaskPromptViews::OnNativeThemeChanged(const ui::NativeTheme* theme) {
   SkColor bg_color =
       theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground);
-  progress_overlay_->set_background(
-      views::Background::CreateSolidBackground(bg_color));
+  progress_overlay_->SetBackground(views::CreateSolidBackground(bg_color));
   progress_label_->SetBackgroundColor(bg_color);
   progress_label_->SetEnabledColor(theme->GetSystemColor(
       ui::NativeTheme::kColorId_ThrobberSpinningColor));
@@ -393,8 +391,8 @@
   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
   permanent_error_label_->SetFontList(
       rb.GetFontList(ui::ResourceBundle::BoldFont));
-  permanent_error_label_->set_background(
-      views::Background::CreateSolidBackground(kWarningColor));
+  permanent_error_label_->SetBackground(
+      views::CreateSolidBackground(kWarningColor));
   permanent_error_label_->SetBorder(
       views::CreateEmptyBorder(12, kEdgePadding, 12, kEdgePadding));
   permanent_error_label_->SetEnabledColor(SK_ColorWHITE);
diff --git a/chrome/browser/ui/views/autofill/password_generation_popup_view_views.cc b/chrome/browser/ui/views/autofill/password_generation_popup_view_views.cc
index 6970f7d..c226346 100644
--- a/chrome/browser/ui/views/autofill/password_generation_popup_view_views.cc
+++ b/chrome/browser/ui/views/autofill/password_generation_popup_view_views.cc
@@ -143,9 +143,8 @@
   link_style.disable_line_wrapping = false;
   help_label_->AddStyleRange(controller_->HelpTextLinkRange(), link_style);
 
-  help_label_->set_background(
-      views::Background::CreateSolidBackground(
-          kExplanatoryTextBackgroundColor));
+  help_label_->SetBackground(
+      views::CreateSolidBackground(kExplanatoryTextBackgroundColor));
   help_label_->SetBorder(views::CreateEmptyBorder(
       PasswordGenerationPopupController::kHelpVerticalPadding -
           kHelpVerticalOffset,
@@ -155,7 +154,7 @@
       PasswordGenerationPopupController::kHorizontalPadding));
   AddChildView(help_label_);
 
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_ResultsTableNormalBackground));
 }
 
@@ -211,7 +210,7 @@
   if (controller_->password_selected())
     NotifyAccessibilityEvent(ui::AX_EVENT_SELECTION, true);
 
-  password_view_->set_background(views::Background::CreateThemedSolidBackground(
+  password_view_->SetBackground(views::CreateThemedSolidBackground(
       password_view_,
       controller_->password_selected()
           ? ui::NativeTheme::kColorId_ResultsTableHoveredBackground
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views.cc b/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
index 68bd769..56566fe2 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
@@ -234,9 +234,8 @@
 
 std::unique_ptr<views::View> SaveCardBubbleViews::CreateRequestCvcView() {
   auto request_cvc_view = base::MakeUnique<views::View>();
-  request_cvc_view->set_background(
-      views::Background::CreateThemedSolidBackground(
-          request_cvc_view.get(), ui::NativeTheme::kColorId_BubbleBackground));
+  request_cvc_view->SetBackground(views::CreateThemedSolidBackground(
+      request_cvc_view.get(), ui::NativeTheme::kColorId_BubbleBackground));
   views::BoxLayout* layout =
       new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
                            ChromeLayoutProvider::Get()->GetDistanceMetric(
@@ -280,7 +279,7 @@
 void SaveCardBubbleViews::Init() {
   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
   view_stack_ = new ViewStack();
-  view_stack_->set_background(views::Background::CreateThemedSolidBackground(
+  view_stack_->SetBackground(views::CreateThemedSolidBackground(
       view_stack_, ui::NativeTheme::kColorId_BubbleBackground));
   view_stack_->Push(CreateMainContentView(), /*animate=*/false);
   AddChildView(view_stack_);
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
index b2cd4c1..7ab33a64 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
@@ -303,8 +303,7 @@
     bb_view_->set_owned_by_client();
     // Real bookmark bars get a BookmarkBarViewBackground. Set an opaque
     // background here just to avoid triggering subpixel rendering issues.
-    bb_view_->set_background(
-        views::Background::CreateSolidBackground(SK_ColorWHITE));
+    bb_view_->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
     bb_view_->SetPageNavigator(&navigator_);
 
     AddTestData(CreateBigMenu());
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
index d38df91..875173c 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
@@ -117,8 +117,7 @@
         kGenericScreenStyle.item_size.height(),
         kGenericScreenStyle.item_size.height() * 2);
     screen_scroll_view->set_hide_horizontal_scrollbar(true);
-    screen_scroll_view->set_background(
-        views::Background::CreateSolidBackground(bg_color));
+    screen_scroll_view->SetBackground(views::CreateSolidBackground(bg_color));
 
     pane_->AddTab(screen_title_text, screen_scroll_view);
     pane_->set_listener(this);
@@ -148,8 +147,7 @@
     window_scroll_view->ClipHeightTo(kWindowStyle.item_size.height(),
                                      kWindowStyle.item_size.height() * 2);
     window_scroll_view->set_hide_horizontal_scrollbar(true);
-    window_scroll_view->set_background(
-        views::Background::CreateSolidBackground(bg_color));
+    window_scroll_view->SetBackground(views::CreateSolidBackground(bg_color));
 
     pane_->AddTab(window_title_text, window_scroll_view);
     pane_->set_listener(this);
@@ -179,8 +177,7 @@
     tab_scroll_view->ClipHeightTo(kTabStyle.item_size.height(),
                                   kTabStyle.item_size.height() * 10);
     tab_scroll_view->set_hide_horizontal_scrollbar(true);
-    tab_scroll_view->set_background(
-        views::Background::CreateSolidBackground(bg_color));
+    tab_scroll_view->SetBackground(views::CreateSolidBackground(bg_color));
 
     pane_->AddTab(tab_title_text, tab_scroll_view);
     pane_->set_listener(this);
diff --git a/chrome/browser/ui/views/download/download_shelf_view.cc b/chrome/browser/ui/views/download/download_shelf_view.cc
index 541bbdb..12eff65 100644
--- a/chrome/browser/ui/views/download/download_shelf_view.cc
+++ b/chrome/browser/ui/views/download/download_shelf_view.cc
@@ -329,7 +329,7 @@
   if (show_all_view_)
     ConfigureButtonForTheme(show_all_view_);
 
-  set_background(views::Background::CreateSolidBackground(
+  SetBackground(views::CreateSolidBackground(
       GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR)));
 
   views::SetImageFromVectorIcon(
diff --git a/chrome/browser/ui/views/extensions/extension_dialog.cc b/chrome/browser/ui/views/extensions/extension_dialog.cc
index 6620241..67287457 100644
--- a/chrome/browser/ui/views/extensions/extension_dialog.cc
+++ b/chrome/browser/ui/views/extensions/extension_dialog.cc
@@ -89,8 +89,7 @@
 
   // Show a white background while the extension loads.  This is prettier than
   // flashing a black unfilled window frame.
-  view->set_background(
-      views::Background::CreateSolidBackground(0xFF, 0xFF, 0xFF));
+  view->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
   view->SetVisible(true);
 
   // Ensure the DOM JavaScript can respond immediately to keyboard shortcuts.
@@ -209,7 +208,7 @@
     case extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD:
       // Avoid potential overdraw by removing the temporary background after
       // the extension finishes loading.
-      GetExtensionView(host_.get())->set_background(NULL);
+      GetExtensionView(host_.get())->SetBackground(nullptr);
       // The render view is created during the LoadURL(), so we should
       // set the focus to the view if nobody else takes the focus.
       if (content::Details<extensions::ExtensionHost>(host()) == details)
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index 57550b7b..c0a768c 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -405,7 +405,7 @@
   auto border = base::MakeUnique<views::BubbleBorder>(
       views::BubbleBorder::NONE, views::BubbleBorder::SMALL_SHADOW,
       bg_color);
-  set_background(new views::BubbleBackground(border.get()));
+  SetBackground(base::MakeUnique<views::BubbleBackground>(border.get()));
   SetBorder(std::move(border));
 
   match_count_text_->SetBackgroundColor(bg_color);
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index de4fdc63..e530c8d 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2100,7 +2100,7 @@
   devtools_web_view_->SetVisible(false);
 
   contents_container_ = new views::View();
-  contents_container_->set_background(views::Background::CreateSolidBackground(
+  contents_container_->SetBackground(views::CreateSolidBackground(
       GetThemeProvider()->GetColor(ThemeProperties::COLOR_CONTROL_BACKGROUND)));
   contents_container_->AddChildView(devtools_web_view_);
   contents_container_->AddChildView(contents_web_view_);
@@ -2208,8 +2208,9 @@
   if (!bookmark_bar_view_.get()) {
     bookmark_bar_view_.reset(new BookmarkBarView(browser_.get(), this));
     bookmark_bar_view_->set_owned_by_client();
-    bookmark_bar_view_->set_background(
-        new BookmarkBarViewBackground(this, bookmark_bar_view_.get()));
+    bookmark_bar_view_->SetBackground(
+        base::MakeUnique<BookmarkBarViewBackground>(this,
+                                                    bookmark_bar_view_.get()));
     bookmark_bar_view_->SetBookmarkBarState(
         browser_->bookmark_bar_state(),
         BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
diff --git a/chrome/browser/ui/views/frame/contents_web_view.cc b/chrome/browser/ui/views/frame/contents_web_view.cc
index 57f2004..994241a 100644
--- a/chrome/browser/ui/views/frame/contents_web_view.cc
+++ b/chrome/browser/ui/views/frame/contents_web_view.cc
@@ -61,12 +61,11 @@
   // Make sure the background is opaque.
   const SkColor ntp_background = color_utils::GetResultingPaintColor(
       theme->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND), SK_ColorWHITE);
-  set_background(views::Background::CreateSolidBackground(
+  SetBackground(views::CreateSolidBackground(SkColorSetARGB(
+      SkColorGetA(ntp_background),
       SkColorGetR(ntp_background) * kBackgroundBrightness / 0xFF,
       SkColorGetG(ntp_background) * kBackgroundBrightness / 0xFF,
-      SkColorGetB(ntp_background) * kBackgroundBrightness / 0xFF,
-      SkColorGetA(ntp_background)));
-
+      SkColorGetB(ntp_background) * kBackgroundBrightness / 0xFF)));
 
   if (web_contents()) {
     content::RenderWidgetHostView* rwhv =
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 09b9db8..66a01df7 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -469,7 +469,7 @@
   button->SetImage(views::CustomButton::STATE_PRESSED,
                    tp->GetImageSkiaNamed(pushed_image_id));
   if (browser_view()->IsBrowserTypeNormal()) {
-    button->SetBackground(
+    button->SetBackgroundImage(
         tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND),
         tp->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND),
         tp->GetImageSkiaNamed(mask_image_id));
diff --git a/chrome/browser/ui/views/infobars/infobar_view.cc b/chrome/browser/ui/views/infobars/infobar_view.cc
index 2327752..ad0e3e6 100644
--- a/chrome/browser/ui/views/infobars/infobar_view.cc
+++ b/chrome/browser/ui/views/infobars/infobar_view.cc
@@ -64,8 +64,8 @@
       icon_(nullptr),
       close_button_(nullptr) {
   set_owned_by_client();  // InfoBar deletes itself at the appropriate time.
-  set_background(
-      new InfoBarBackground(infobars::InfoBar::delegate()->GetInfoBarType()));
+  SetBackground(base::MakeUnique<InfoBarBackground>(
+      infobars::InfoBar::delegate()->GetInfoBarType()));
   SetEventTargeter(base::MakeUnique<views::ViewTargeter>(this));
 
   AddChildView(child_container_);
@@ -75,8 +75,8 @@
 
   child_container_->SetPaintToLayer();
   child_container_->layer()->SetMasksToBounds(true);
-  child_container_->set_background(views::Background::CreateSolidBackground(
-      infobars::InfoBar::GetBackgroundColor(
+  child_container_->SetBackground(
+      views::CreateSolidBackground(infobars::InfoBar::GetBackgroundColor(
           infobars::InfoBar::delegate()->GetInfoBarType())));
 }
 
diff --git a/chrome/browser/ui/views/location_bar/keyword_hint_view.cc b/chrome/browser/ui/views/location_bar/keyword_hint_view.cc
index 8d41a703..4e4dcc61 100644
--- a/chrome/browser/ui/views/location_bar/keyword_hint_view.cc
+++ b/chrome/browser/ui/views/location_bar/keyword_hint_view.cc
@@ -66,8 +66,8 @@
 
   chip_container_->SetBorder(views::CreateEmptyBorder(
       gfx::Insets(LocationBarView::kBubbleVerticalPadding, 0)));
-  chip_container_->set_background(
-      new BackgroundWith1PxBorder(tab_bg_color, tab_border_color));
+  chip_container_->SetBackground(base::MakeUnique<BackgroundWith1PxBorder>(
+      tab_bg_color, tab_border_color));
   chip_container_->AddChildView(chip_label_);
   chip_container_->SetLayoutManager(new views::FillLayout());
   AddChildView(chip_container_);
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 066eb09c..3603e71 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -195,8 +195,8 @@
       new views::Label(base::string16(), {font_list});
   ime_inline_autocomplete_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   ime_inline_autocomplete_view_->SetAutoColorReadabilityEnabled(false);
-  ime_inline_autocomplete_view_->set_background(
-      views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+  ime_inline_autocomplete_view_->SetBackground(
+      views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
           ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
   ime_inline_autocomplete_view_->SetEnabledColor(
       GetNativeTheme()->GetSystemColor(
@@ -587,13 +587,12 @@
 void LocationBarView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
   RefreshLocationIcon();
   if (is_popup_mode_) {
-    set_background(
-        views::Background::CreateSolidBackground(GetColor(BACKGROUND)));
+    SetBackground(views::CreateSolidBackground(GetColor(BACKGROUND)));
   } else {
     // This border color will be blended on top of the toolbar (which may use an
     // image in the case of themes).
-    set_background(
-        new BackgroundWith1PxBorder(GetColor(BACKGROUND), GetBorderColor()));
+    SetBackground(base::MakeUnique<BackgroundWith1PxBorder>(
+        GetColor(BACKGROUND), GetBorderColor()));
   }
   SchedulePaint();
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index ce33862..5c4cdc2 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -272,10 +272,11 @@
 void OmniboxResultView::Invalidate() {
   const ResultViewState state = GetState();
   if (state == NORMAL) {
-    set_background(nullptr);
+    SetBackground(nullptr);
   } else {
     const SkColor bg_color = GetColor(state, BACKGROUND);
-    set_background(new BackgroundWith1PxBorder(bg_color, bg_color));
+    SetBackground(
+        base::MakeUnique<BackgroundWith1PxBorder>(bg_color, bg_color));
   }
 
   // While the text in the RenderTexts may not have changed, the styling
diff --git a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
index 14ff3d01..ca2e17c 100644
--- a/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_dialog_view.cc
@@ -342,8 +342,7 @@
   throbber_overlay_.SetVisible(false);
   // The throbber overlay has to have a solid white background to hide whatever
   // would be under it.
-  throbber_overlay_.set_background(
-      views::Background::CreateSolidBackground(SK_ColorWHITE));
+  throbber_overlay_.SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
 
   std::unique_ptr<views::GridLayout> layout =
       base::MakeUnique<views::GridLayout>(&throbber_overlay_);
diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.cc b/chrome/browser/ui/views/payments/payment_request_row_view.cc
index bc138a1..fbe5a09 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.cc
@@ -26,7 +26,7 @@
 
 void PaymentRequestRowView::SetActiveBackground() {
   ui::NativeTheme* theme = GetWidget()->GetNativeTheme();
-  set_background(views::Background::CreateSolidBackground(theme->GetSystemColor(
+  SetBackground(views::CreateSolidBackground(theme->GetSystemColor(
       ui::NativeTheme::kColorId_ResultsTableHoveredBackground)));
 }
 
@@ -46,7 +46,7 @@
     SetActiveBackground();
     HideBottomSeparator();
   } else {
-    set_background(nullptr);
+    SetBackground(nullptr);
     ShowBottomSeparator();
   }
 }
diff --git a/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc b/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
index d8d31ba2..452bd47 100644
--- a/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_request_sheet_controller.cc
@@ -196,7 +196,7 @@
   if (GetSheetId(&sheet_id))
     view->set_id(static_cast<int>(sheet_id));
 
-  view->set_background(views::Background::CreateSolidBackground(SK_ColorWHITE));
+  view->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
 
   // Paint the sheets to layers, otherwise the MD buttons (which do paint to a
   // layer) won't do proper clipping.
@@ -233,8 +233,7 @@
   content_view_ = new views::View;
   content_view_->SetPaintToLayer();
   content_view_->layer()->SetFillsBoundsOpaquely(true);
-  content_view_->set_background(
-      views::Background::CreateSolidBackground(SK_ColorWHITE));
+  content_view_->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
   pane_layout->AddView(content_view_);
   pane_->SizeToPreferredSize();
 
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index e7535c42..ba4f2141 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -246,11 +246,10 @@
   void UpdateColors() {
     bool is_selected = HasFocus();
 
-    set_background(
+    SetBackground(
         is_selected
-            ? views::Background::CreateSolidBackground(
-                  GetNativeTheme()->GetSystemColor(
-                      ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor))
+            ? views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+                  ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor))
             : nullptr);
 
     SkColor text_color = GetNativeTheme()->GetSystemColor(
@@ -630,9 +629,8 @@
 void ProfileChooserView::OnNativeThemeChanged(
     const ui::NativeTheme* native_theme) {
   views::BubbleDialogDelegateView::OnNativeThemeChanged(native_theme);
-  set_background(views::Background::CreateSolidBackground(
-      GetNativeTheme()->GetSystemColor(
-          ui::NativeTheme::kColorId_DialogBackground)));
+  SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_DialogBackground)));
   if (auth_error_email_button_) {
     auth_error_email_button_->SetTextColor(
         views::LabelButton::STATE_NORMAL,
@@ -1381,7 +1379,7 @@
     const AvatarMenu::Item& avatar_item) {
   DCHECK(avatar_item.signed_in);
   views::View* view = new views::View();
-  view->set_background(views::Background::CreateSolidBackground(
+  view->SetBackground(views::CreateSolidBackground(
       profiles::kAvatarBubbleAccountsBackgroundColor));
   views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth);
 
diff --git a/chrome/browser/ui/views/sad_tab_view.cc b/chrome/browser/ui/views/sad_tab_view.cc
index 710467f..544917a4 100644
--- a/chrome/browser/ui/views/sad_tab_view.cc
+++ b/chrome/browser/ui/views/sad_tab_view.cc
@@ -51,7 +51,7 @@
 SadTabView::SadTabView(content::WebContents* web_contents,
                        chrome::SadTabKind kind)
     : SadTab(web_contents, kind) {
-  set_background(views::Background::CreateThemedSolidBackground(
+  SetBackground(views::CreateThemedSolidBackground(
       this, ui::NativeTheme::kColorId_DialogBackground));
 
   views::GridLayout* layout = new views::GridLayout(this);
diff --git a/chrome/browser/ui/views/screen_capture_notification_ui_views.cc b/chrome/browser/ui/views/screen_capture_notification_ui_views.cc
index 6bbc7bb..802385a4 100644
--- a/chrome/browser/ui/views/screen_capture_notification_ui_views.cc
+++ b/chrome/browser/ui/views/screen_capture_notification_ui_views.cc
@@ -192,8 +192,8 @@
   widget->Init(params);
   widget->SetAlwaysOnTop(true);
 
-  set_background(views::Background::CreateSolidBackground(GetNativeTheme()->
-      GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
+  SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_DialogBackground)));
 
   display::Screen* screen = display::Screen::GetScreen();
   // TODO(sergeyu): Move the notification to the display being captured when
diff --git a/chrome/browser/ui/views/subtle_notification_view.cc b/chrome/browser/ui/views/subtle_notification_view.cc
index c590ddf6..44bc472 100644
--- a/chrome/browser/ui/views/subtle_notification_view.cc
+++ b/chrome/browser/ui/views/subtle_notification_view.cc
@@ -144,7 +144,7 @@
   std::unique_ptr<views::BubbleBorder> bubble_border(new views::BubbleBorder(
       views::BubbleBorder::NONE, views::BubbleBorder::NO_ASSETS,
       kBackgroundColor));
-  set_background(new views::BubbleBackground(bubble_border.get()));
+  SetBackground(base::MakeUnique<views::BubbleBackground>(bubble_border.get()));
   SetBorder(std::move(bubble_border));
 
   instruction_view_ =
diff --git a/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc b/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
index a4d5ad2..cab51a4 100644
--- a/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
+++ b/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
@@ -178,8 +178,8 @@
       1, 0, 1, 0,
       ui::GetSigninConfirmationPromptBarColor(
           GetNativeTheme(), ui::kSigninConfirmationPromptBarBorderAlpha)));
-  prompt_bar->set_background(views::Background::CreateSolidBackground(
-      kPromptBarBackgroundColor));
+  prompt_bar->SetBackground(
+      views::CreateSolidBackground(kPromptBarBackgroundColor));
 
   // Create the explanation label.
   std::vector<size_t> offsets;
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 79da62d..386bf97 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -243,7 +243,7 @@
 class InMenuButton : public LabelButton {
  public:
   InMenuButton(views::ButtonListener* listener, const base::string16& text)
-      : LabelButton(listener, text), in_menu_background_(NULL) {}
+      : LabelButton(listener, text) {}
   ~InMenuButton() override {}
 
   void Init(InMenuButtonBackground::ButtonType type) {
@@ -252,8 +252,7 @@
     SetFocusBehavior(FocusBehavior::ALWAYS);
     SetHorizontalAlignment(gfx::ALIGN_CENTER);
 
-    in_menu_background_ = new InMenuButtonBackground(type);
-    set_background(in_menu_background_);
+    SetBackground(base::MakeUnique<InMenuButtonBackground>(type));
     SetBorder(
         views::CreateEmptyBorder(0, kHorizontalPadding, 0, kHorizontalPadding));
     label()->SetFontList(MenuConfig::instance().font_list);
@@ -287,8 +286,6 @@
   }
 
  private:
-  InMenuButtonBackground* in_menu_background_;
-
   DISALLOW_COPY_AND_ASSIGN(InMenuButton);
 };
 
@@ -491,9 +488,8 @@
     zoom_label_->SetAutoColorReadabilityEnabled(false);
     zoom_label_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
 
-    InMenuButtonBackground* center_bg =
-        new InMenuButtonBackground(InMenuButtonBackground::NO_BORDER);
-    zoom_label_->set_background(center_bg);
+    zoom_label_->SetBackground(base::MakeUnique<InMenuButtonBackground>(
+        InMenuButtonBackground::NO_BORDER));
 
     AddChildView(zoom_label_);
     zoom_label_max_width_valid_ = false;
@@ -517,8 +513,8 @@
     fullscreen_button_->set_tag(fullscreen_index);
     fullscreen_button_->SetImageAlignment(
         ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE);
-    fullscreen_button_->set_background(
-        new InMenuButtonBackground(InMenuButtonBackground::LEADING_BORDER));
+    fullscreen_button_->SetBackground(base::MakeUnique<InMenuButtonBackground>(
+        InMenuButtonBackground::LEADING_BORDER));
     fullscreen_button_->SetAccessibleName(GetAccessibleNameForAppMenuItem(
         menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN));
     AddChildView(fullscreen_button_);
@@ -1105,7 +1101,7 @@
             new ExtensionToolbarMenuView(browser_, this, item));
         for (int i = 0; i < extension_toolbar->contents()->child_count(); ++i) {
           View* action_view = extension_toolbar->contents()->child_at(i);
-          action_view->set_background(new InMenuButtonBackground(
+          action_view->SetBackground(base::MakeUnique<InMenuButtonBackground>(
               InMenuButtonBackground::ROUNDED_BUTTON));
         }
         extension_toolbar_ = extension_toolbar.get();
diff --git a/chrome/browser/ui/views/try_chrome_dialog_view.cc b/chrome/browser/ui/views/try_chrome_dialog_view.cc
index 7a361551..ae5022f9 100644
--- a/chrome/browser/ui/views/try_chrome_dialog_view.cc
+++ b/chrome/browser/ui/views/try_chrome_dialog_view.cc
@@ -99,8 +99,8 @@
 
   views::View* root_view = popup_->GetRootView();
   // The window color is a tiny bit off-white.
-  root_view->set_background(
-      views::Background::CreateSolidBackground(0xfc, 0xfc, 0xfc));
+  root_view->SetBackground(
+      views::CreateSolidBackground(SkColorSetRGB(0xfc, 0xfc, 0xfc)));
 
   views::GridLayout* layout = views::GridLayout::CreatePanel(root_view);
   views::ColumnSet* columns;
diff --git a/chrome/browser/ui/webui/help/help_utils_chromeos.cc b/chrome/browser/ui/webui/help/help_utils_chromeos.cc
index 1420ac5e..ea4884e 100644
--- a/chrome/browser/ui/webui/help/help_utils_chromeos.cc
+++ b/chrome/browser/ui/webui/help/help_utils_chromeos.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/chromeos_switches.h"
+#include "chromeos/network/network_state.h"
 #include "chromeos/network/network_type_pattern.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -51,7 +52,9 @@
   return false;
 }
 
-base::string16 GetConnectionTypeAsUTF16(const std::string& type) {
+base::string16 GetConnectionTypeAsUTF16(const chromeos::NetworkState* network) {
+  const std::string type =
+      network->IsUsingMobileData() ? shill::kTypeCellular : network->type();
   if (chromeos::NetworkTypePattern::Ethernet().MatchesType(type))
     return l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_ETHERNET);
   if (type == shill::kTypeWifi)
diff --git a/chrome/browser/ui/webui/help/help_utils_chromeos.h b/chrome/browser/ui/webui/help/help_utils_chromeos.h
index 1afb8c5..ec388ef 100644
--- a/chrome/browser/ui/webui/help/help_utils_chromeos.h
+++ b/chrome/browser/ui/webui/help/help_utils_chromeos.h
@@ -5,10 +5,12 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_HELP_HELP_UTILS_CHROMEOS_H_
 #define CHROME_BROWSER_UI_WEBUI_HELP_HELP_UTILS_CHROMEOS_H_
 
-#include <string>
-
 #include "base/strings/string16.h"
 
+namespace chromeos {
+class NetworkState;
+}
+
 namespace help_utils_chromeos {
 
 // Returns true if updates over cellular networks are allowed. If |interactive|
@@ -19,7 +21,7 @@
 bool IsUpdateOverCellularAllowed(bool interactive);
 
 // Returns localized name for the connection |type|.
-base::string16 GetConnectionTypeAsUTF16(const std::string& type);
+base::string16 GetConnectionTypeAsUTF16(const chromeos::NetworkState* network);
 
 }  // namespace help_utils_chromeos
 
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos.cc b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
index e5885ce..f5a75e9a 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos.cc
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
@@ -55,7 +55,8 @@
   if (network->type() == shill::kTypeBluetooth)
     return NETWORK_STATUS_DISALLOWED;
 
-  if (network->type() == shill::kTypeCellular &&
+  // Treats tethered networks as cellular networks.
+  if (network->IsUsingMobileData() &&
       !help_utils_chromeos::IsUpdateOverCellularAllowed(interactive)) {
     return NETWORK_STATUS_DISALLOWED;
   }
@@ -99,10 +100,9 @@
                  l10n_util::GetStringUTF16(IDS_UPGRADE_OFFLINE));
     return false;
   } else if (status == NETWORK_STATUS_DISALLOWED) {
-    base::string16 message =
-        l10n_util::GetStringFUTF16(
-            IDS_UPGRADE_DISALLOWED,
-            help_utils_chromeos::GetConnectionTypeAsUTF16(network->type()));
+    base::string16 message = l10n_util::GetStringFUTF16(
+        IDS_UPGRADE_DISALLOWED,
+        help_utils_chromeos::GetConnectionTypeAsUTF16(network));
     callback.Run(VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED, 0,
                  std::string(), 0, message);
     return false;
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index fba7d030c..710dcd91 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -661,10 +661,17 @@
   return printer_backend_proxy_.get();
 }
 
-void PrintPreviewHandler::HandleGetPrinters(const base::ListValue* /*args*/) {
+void PrintPreviewHandler::HandleGetPrinters(const base::ListValue* args) {
   VLOG(1) << "Enumerate printers start";
-  printer_backend_proxy()->EnumeratePrinters(base::Bind(
-      &PrintPreviewHandler::SetupPrinterList, weak_factory_.GetWeakPtr()));
+  std::string callback_id;
+  CHECK(args->GetString(0, &callback_id));
+  CHECK(!callback_id.empty());
+
+  AllowJavascript();
+
+  printer_backend_proxy()->EnumeratePrinters(
+      base::Bind(&PrintPreviewHandler::SetupPrinterList,
+                 weak_factory_.GetWeakPtr(), callback_id));
 }
 
 void PrintPreviewHandler::HandleGetPrivetPrinters(const base::ListValue* args) {
@@ -1325,6 +1332,7 @@
 }
 
 void PrintPreviewHandler::SetupPrinterList(
+    const std::string& callback_id,
     const printing::PrinterList& printer_list) {
   base::ListValue printers;
   PrintersToValues(printer_list, &printers);
@@ -1337,7 +1345,7 @@
     has_logged_printers_count_ = true;
   }
 
-  web_ui()->CallJavascriptFunctionUnsafe("setPrinters", printers);
+  ResolveJavascriptCallback(base::Value(callback_id), printers);
 }
 
 void PrintPreviewHandler::SendCloudPrintEnabled() {
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.h b/chrome/browser/ui/webui/print_preview/print_preview_handler.h
index 2166080..8cee113 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.h
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.h
@@ -242,7 +242,8 @@
                         std::unique_ptr<base::DictionaryValue> settings_info);
 
   // Send the list of printers to the Web UI.
-  void SetupPrinterList(const printing::PrinterList& printer_list);
+  void SetupPrinterList(const std::string& callback_id,
+                        const printing::PrinterList& printer_list);
 
   // Send whether cloud print integration should be enabled.
   void SendCloudPrintEnabled();
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index bad234e..05db7d11 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -103,12 +103,12 @@
   const chromeos::NetworkState* network = chromeos::NetworkHandler::Get()
                                               ->network_state_handler()
                                               ->DefaultNetwork();
-  const bool cellular = network && network->IsConnectedState() &&
-                        network->type() == shill::kTypeCellular;
+  const bool mobile_data =
+      network && network->IsConnectedState() && network->IsUsingMobileData();
 
   if (help_utils_chromeos::IsUpdateOverCellularAllowed(
           true /* interactive */)) {
-    return cellular
+    return mobile_data
                ? l10n_util::GetStringUTF16(
                      IDS_UPGRADE_NETWORK_LIST_CELLULAR_ALLOWED_NOT_AUTOMATIC)
                : l10n_util::GetStringUTF16(
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 5367d52e..45b08d8 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -940,6 +940,9 @@
 // Custom crosh command.
 const char kCroshCommand[] = "crosh-command";
 
+// Disables logging redirect for testing.
+const char kDisableLoggingRedirect[] = "disable-logging-redirect";
+
 // Disables apps on the login screen. By default, they are allowed and can be
 // installed through policy.
 const char kDisableLoginScreenApps[] = "disable-login-screen-apps";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index d983d8c8..03043e8 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -273,6 +273,7 @@
 
 #if defined(OS_CHROMEOS)
 extern const char kCroshCommand[];
+extern const char kDisableLoggingRedirect[];
 extern const char kDisableLoginScreenApps[];
 extern const char kDisableNativeCups[];
 #endif  // defined(OS_CHROMEOS)
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 8ce6a83..12d7c45 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -55,7 +55,9 @@
   kOutdatedBlocked,
   kOutdatedDisallowed,
   kPlayImportantContent,
+#if defined(OS_LINUX)
   kRestartRequired,
+#endif
   kUnauthorized,
 };
 
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 95f3aa6..55acb6a 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -956,6 +956,7 @@
             identifier));
         break;
       }
+#if defined(OS_LINUX)
       case ChromeViewHostMsg_GetPluginInfo_Status::kRestartRequired: {
         placeholder = create_blocked_plugin(
             IDR_BLOCKED_PLUGIN_HTML,
@@ -963,6 +964,7 @@
                                        group_name));
         break;
       }
+#endif
     }
   }
   placeholder->SetStatus(status);
diff --git a/chrome/test/data/webui/print_preview/native_layer_stub.js b/chrome/test/data/webui/print_preview/native_layer_stub.js
index d16b11b..92f9f29 100644
--- a/chrome/test/data/webui/print_preview/native_layer_stub.js
+++ b/chrome/test/data/webui/print_preview/native_layer_stub.js
@@ -10,7 +10,7 @@
   */
   function NativeLayerStub() {
     settings.TestBrowserProxy.call(this, [
-        'getInitialSettings', 'setupPrinter' ]);
+        'getInitialSettings', 'getPrinters', 'setupPrinter' ]);
 
     /**
      * @private {!cr.EventTarget} The event target used for dispatching and
@@ -34,6 +34,13 @@
     this.initialSettings_ = null;
 
     /**
+     *
+     * @private {!Array<!print_preview.LocalDestinationInfo>} Local destination
+     *     list to be used for the response to |getPrinters|.
+     */
+    this.localDestinationInfos_ = [];
+
+    /**
      * @private {!print_preview.PrinterSetupResponse} The response to be sent
      *     on a |setupPrinter| call.
      */
@@ -67,6 +74,12 @@
     },
 
     /** @override */
+    getPrinters: function() {
+      this.methodCalled('getPrinters');
+      return Promise.resolve(this.localDestinationInfos_);
+    },
+
+    /** @override */
     setupPrinter: function(printerId) {
       this.methodCalled('setupPrinter', printerId);
       return this.shouldRejectPrinterSetup_ ?
@@ -132,6 +145,14 @@
     },
 
     /**
+     * @param {!Array<!print_preview.LocalDestinationInfo>} localDestinations
+     *     The local destinations to return as a response to |getPrinters|.
+     */
+    setLocalDestinations: function(localDestinations) {
+      this.localDestinationInfos_ = localDestinations;
+    },
+
+    /**
      * @param {boolean} reject Whether printSetup requests should be rejected.
      * @param {!print_preview.PrinterSetupResponse} The response to send when
      *     |setupPrinter| is called.
diff --git a/chrome/test/data/webui/print_preview/print_preview_tests.js b/chrome/test/data/webui/print_preview/print_preview_tests.js
index c7277f5..e70b7c3 100644
--- a/chrome/test/data/webui/print_preview/print_preview_tests.js
+++ b/chrome/test/data/webui/print_preview/print_preview_tests.js
@@ -32,8 +32,7 @@
 
   /**
    * Initialize print preview with the initial settings currently stored in
-   * |initialSettings|. Creates |printPreview| if it does not
-   * already exist.
+   * |initialSettings|.
    */
   function setInitialSettings() {
     nativeLayer.setInitialSettings(initialSettings);
@@ -41,14 +40,29 @@
   }
 
   /**
-   * Dispatch the LOCAL_DESTINATIONS_SET event. This call is NOT async and will
-   * happen in the same thread.
+   * Start loading the local destinations using the destination infos currently
+   * stored in |localDestinationInfos|.
    */
   function setLocalDestinations() {
-    var localDestsSetEvent =
-        new Event(print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET);
-    localDestsSetEvent.destinationInfos = localDestinationInfos;
-    nativeLayer.getEventTarget().dispatchEvent(localDestsSetEvent);
+    nativeLayer.setLocalDestinations(localDestinationInfos);
+    printPreview.destinationStore_.startLoadLocalDestinations();
+  }
+
+  /**
+   * Initializes print preview with the initial settings currently stored in
+   * |initialSettings|, waits for the getInitialSettings promise to resolve,
+   * and loads local destinations using destination infos currently stored in
+   * |localDestinationInfos|.
+   * @return {!Promise<!Array<!print_preview.LocalDestinationInfo>>} a
+   *     promise that will resolve when getPrinters has been resolved by
+   *     the native layer stub.
+   */
+  function setupSettingsAndDestinations() {
+    setInitialSettings();
+    return nativeLayer.whenCalled('getInitialSettings').then(function() {
+      setLocalDestinations();
+      return nativeLayer.whenCalled('getPrinters');
+    });
   }
 
   /**
@@ -196,16 +210,25 @@
 
   /**
    * Repeated setup steps for the advanced settings tests.
-   * Sets initial settings, and verifies advanced options section is visible
-   * after expanding more settings.
+   * Sets capabilities, and verifies advanced options section is visible
+   * after expanding more settings. Then opens the advanced settings overlay
+   * and verifies it is displayed.
    */
-  function setupAdvancedSettingsTest(device) {
-    setLocalDestinations();
+  function startAdvancedSettingsTest(device) {
     setCapabilities(device);
     expandMoreSettings();
 
     // Check that the advanced options settings section is visible.
     checkSectionVisible($('advanced-options-settings'), true);
+
+    // Open the advanced settings overlay.
+    openAdvancedSettings();
+
+    // Check advanced settings overlay is visible by checking that the close
+    // button is displayed.
+    var advancedSettingsCloseButton = $('advanced-settings').
+        querySelector('.close-button');
+    checkElementDisplayed(advancedSettingsCloseButton, true);
   }
 
   /** @return {boolean} */
@@ -262,92 +285,84 @@
 
     // Test some basic assumptions about the print preview WebUI.
     test('PrinterList', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            var recentList =
-                $('destination-search').querySelector('.recent-list ul');
-            var localList =
-                $('destination-search').querySelector('.local-list ul');
-            assertNotEquals(null, recentList);
-            assertEquals(1, recentList.childNodes.length);
-            assertEquals('FooName',
-                         recentList.childNodes.item(0).querySelector(
-                             '.destination-list-item-name').textContent);
-            assertNotEquals(null, localList);
-            assertEquals(3, localList.childNodes.length);
-            assertEquals(
-                'Save as PDF',
-                localList.childNodes.item(PDF_INDEX).
-                querySelector('.destination-list-item-name').textContent);
-            assertEquals(
-                'FooName',
-                localList.childNodes.item(FOO_INDEX).
-                querySelector('.destination-list-item-name').textContent);
-            assertEquals(
-                'BarName',
-                localList.childNodes.item(BAR_INDEX).
-                querySelector('.destination-list-item-name').textContent);
-          });
+      return setupSettingsAndDestinations().then(function() {
+        var recentList =
+            $('destination-search').querySelector('.recent-list ul');
+        var localList =
+            $('destination-search').querySelector('.local-list ul');
+        assertNotEquals(null, recentList);
+        assertEquals(1, recentList.childNodes.length);
+        assertEquals('FooName',
+                     recentList.childNodes.item(0).querySelector(
+                         '.destination-list-item-name').textContent);
+        assertNotEquals(null, localList);
+        assertEquals(3, localList.childNodes.length);
+        assertEquals(
+            'Save as PDF',
+            localList.childNodes.item(PDF_INDEX).
+            querySelector('.destination-list-item-name').textContent);
+        assertEquals(
+            'FooName',
+            localList.childNodes.item(FOO_INDEX).
+            querySelector('.destination-list-item-name').textContent);
+        assertEquals(
+            'BarName',
+            localList.childNodes.item(BAR_INDEX).
+            querySelector('.destination-list-item-name').textContent);
+      });
     });
 
     // Test that the printer list is structured correctly after calling
     // addCloudPrinters with an empty list.
     test('PrinterListCloudEmpty', function() {
-      setInitialSettings();
+      return setupSettingsAndDestinations().then(function() {
+        var cloudPrintEnableEvent = new Event(
+            print_preview.NativeLayer.EventType.CLOUD_PRINT_ENABLE);
+        cloudPrintEnableEvent.baseCloudPrintUrl = 'cloudprint url';
+        nativeLayer.getEventTarget().dispatchEvent(
+            cloudPrintEnableEvent);
 
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
+        var searchDoneEvent =
+            new Event(cloudprint.CloudPrintInterfaceEventType.SEARCH_DONE);
+        searchDoneEvent.printers = [];
+        searchDoneEvent.isRecent = true;
+        searchDoneEvent.email = 'foo@chromium.org';
+        printPreview.cloudPrintInterface_.dispatchEvent(searchDoneEvent);
 
-            var cloudPrintEnableEvent = new Event(
-                print_preview.NativeLayer.EventType.CLOUD_PRINT_ENABLE);
-            cloudPrintEnableEvent.baseCloudPrintUrl = 'cloudprint url';
-            nativeLayer.getEventTarget().dispatchEvent(
-                cloudPrintEnableEvent);
+        var recentList =
+            $('destination-search').querySelector('.recent-list ul');
+        var localList =
+            $('destination-search').querySelector('.local-list ul');
+        var cloudList =
+            $('destination-search').querySelector('.cloud-list ul');
 
-            var searchDoneEvent =
-                new Event(cloudprint.CloudPrintInterfaceEventType.SEARCH_DONE);
-            searchDoneEvent.printers = [];
-            searchDoneEvent.isRecent = true;
-            searchDoneEvent.email = 'foo@chromium.org';
-            printPreview.cloudPrintInterface_.dispatchEvent(searchDoneEvent);
+        assertNotEquals(null, recentList);
+        assertEquals(1, recentList.childNodes.length);
+        assertEquals('FooName',
+                     recentList.childNodes.item(0).
+                         querySelector('.destination-list-item-name').
+                         textContent);
 
-            var recentList =
-                $('destination-search').querySelector('.recent-list ul');
-            var localList =
-                $('destination-search').querySelector('.local-list ul');
-            var cloudList =
-                $('destination-search').querySelector('.cloud-list ul');
+        assertNotEquals(null, localList);
+        assertEquals(3, localList.childNodes.length);
+        assertEquals('Save as PDF',
+                     localList.childNodes.item(PDF_INDEX).
+                         querySelector('.destination-list-item-name').
+                         textContent);
+        assertEquals('FooName',
+                     localList.childNodes.
+                         item(FOO_INDEX).
+                         querySelector('.destination-list-item-name').
+                         textContent);
+        assertEquals('BarName',
+                     localList.childNodes.
+                         item(BAR_INDEX).
+                         querySelector('.destination-list-item-name').
+                         textContent);
 
-            assertNotEquals(null, recentList);
-            assertEquals(1, recentList.childNodes.length);
-            assertEquals('FooName',
-                         recentList.childNodes.item(0).
-                             querySelector('.destination-list-item-name').
-                             textContent);
-
-            assertNotEquals(null, localList);
-            assertEquals(3, localList.childNodes.length);
-            assertEquals('Save as PDF',
-                         localList.childNodes.item(PDF_INDEX).
-                             querySelector('.destination-list-item-name').
-                             textContent);
-            assertEquals('FooName',
-                         localList.childNodes.
-                             item(FOO_INDEX).
-                             querySelector('.destination-list-item-name').
-                             textContent);
-            assertEquals('BarName',
-                         localList.childNodes.
-                             item(BAR_INDEX).
-                             querySelector('.destination-list-item-name').
-                             textContent);
-
-            assertNotEquals(null, cloudList);
-            assertEquals(0, cloudList.childNodes.length);
-          });
+        assertNotEquals(null, cloudList);
+        assertEquals(0, cloudList.childNodes.length);
+      });
     });
 
     // Test restore settings with one destination.
@@ -446,14 +461,16 @@
       // It also makes sure these rules do override system default destination.
       initialSettings.serializedDefaultDestinationSelectionRulesStr_ =
           JSON.stringify({namePattern: '.*Bar.*'});
+      // Set this early as the app state selection string will trigger a load
+      // of local destinations on initialization.
+      nativeLayer.setLocalDestinations(localDestinationInfos);
       setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            assertEquals(
-                'BarDevice',
-                printPreview.destinationStore_.selectedDestination.id);
-          });
+      return nativeLayer.whenCalled('getInitialSettings').then(function() {
+        return nativeLayer.whenCalled('getPrinters').then(function() {
+          assertEquals('BarDevice',
+                       printPreview.destinationStore_.selectedDestination.id);
+        });
+      });
     });
 
     test('SystemDialogLinkIsHiddenInAppKioskMode', function() {
@@ -475,23 +492,20 @@
       checkSectionVisible($('color-settings'), false);
       checkSectionVisible($('copies-settings'), false);
 
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            var device = getCddTemplate('FooDevice');
-            device.capabilities.printer.color = {
-              option: [{is_default: true, type: 'STANDARD_COLOR'}]
-            };
-            delete device.capabilities.printer.copies;
-            setCapabilities(device);
+      return setupSettingsAndDestinations().then(function() {
+        var device = getCddTemplate('FooDevice');
+        device.capabilities.printer.color = {
+          option: [{is_default: true, type: 'STANDARD_COLOR'}]
+        };
+        delete device.capabilities.printer.copies;
+        setCapabilities(device);
 
-            checkSectionVisible($('layout-settings'), true);
-            checkSectionVisible($('color-settings'), false);
-            checkSectionVisible($('copies-settings'), false);
+        checkSectionVisible($('layout-settings'), true);
+        checkSectionVisible($('color-settings'), false);
+        checkSectionVisible($('copies-settings'), false);
 
-            return whenAnimationDone('other-options-collapsible');
-          });
+        return whenAnimationDone('other-options-collapsible');
+      });
     });
 
     // When the source is 'PDF' and 'Save as PDF' option is selected, we hide
@@ -533,10 +547,9 @@
           }
         };
         setCapabilities(device);
-
         var otherOptions = $('other-options-settings');
-        // If rasterization is an option, other options should be visible. If
-        // not, there should be no available other options.
+        // If rasterization is an option, other options should be visible.
+        // If not, there should be no available other options.
         checkSectionVisible(otherOptions, isPrintAsImageEnabled());
         if (isPrintAsImageEnabled()) {
           checkElementDisplayed(
@@ -552,9 +565,7 @@
     // When the source is 'HTML', we always hide the fit to page option and show
     // media size option.
     test('SourceIsHTMLCapabilities', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var otherOptions = $('other-options-settings');
@@ -565,8 +576,8 @@
         var mediaSize = $('media-size-settings');
         var scalingSettings = $('scaling-settings');
 
-        // Check that options are collapsed (section is visible, because duplex
-        // is available).
+        // Check that options are collapsed (section is visible, because
+        // duplex is available).
         checkSectionVisible(otherOptions, true);
         checkElementDisplayed(fitToPage, false);
         if (isPrintAsImageEnabled())
@@ -590,89 +601,81 @@
     // we show/hide the fit to page option and hide media size selection.
     test('SourceIsPDFCapabilities', function() {
       initialSettings.isDocumentModifiable_ = false;
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            setCapabilities(getCddTemplate('FooDevice'));
+      return setupSettingsAndDestinations().then(function() {
+        setCapabilities(getCddTemplate('FooDevice'));
 
-            var otherOptions = $('other-options-settings');
-            var scalingSettings = $('scaling-settings');
-            var fitToPageContainer =
-                otherOptions.querySelector('#fit-to-page-container');
-            var rasterizeContainer;
-            if (isPrintAsImageEnabled()) {
-              rasterizeContainer =
-                otherOptions.querySelector('#rasterize-container');
-            }
+        var otherOptions = $('other-options-settings');
+        var scalingSettings = $('scaling-settings');
+        var fitToPageContainer =
+            otherOptions.querySelector('#fit-to-page-container');
+        var rasterizeContainer;
+        if (isPrintAsImageEnabled()) {
+          rasterizeContainer =
+            otherOptions.querySelector('#rasterize-container');
+        }
 
-            checkSectionVisible(otherOptions, true);
-            checkElementDisplayed(fitToPageContainer, true);
-            if (isPrintAsImageEnabled())
-              checkElementDisplayed(rasterizeContainer, false);
-            expectTrue(
-                fitToPageContainer.querySelector('.checkbox').checked);
-            expandMoreSettings();
-            if (isPrintAsImageEnabled()) {
-              checkElementDisplayed(rasterizeContainer, true);
-              expectFalse(
-                  rasterizeContainer.querySelector('.checkbox').checked);
-            }
-            checkSectionVisible($('media-size-settings'), true);
-            checkSectionVisible(scalingSettings, true);
+        checkSectionVisible(otherOptions, true);
+        checkElementDisplayed(fitToPageContainer, true);
+        if (isPrintAsImageEnabled())
+          checkElementDisplayed(rasterizeContainer, false);
+        expectTrue(
+            fitToPageContainer.querySelector('.checkbox').checked);
+        expandMoreSettings();
+        if (isPrintAsImageEnabled()) {
+          checkElementDisplayed(rasterizeContainer, true);
+          expectFalse(
+              rasterizeContainer.querySelector('.checkbox').checked);
+        }
+        checkSectionVisible($('media-size-settings'), true);
+        checkSectionVisible(scalingSettings, true);
 
-            return whenAnimationDone('other-options-collapsible');
-          });
+        return whenAnimationDone('other-options-collapsible');
+      });
     });
 
     // When the source is 'PDF', depending on the selected destination printer,
     // we show/hide the fit to page option and hide media size selection.
     test('ScalingUnchecksFitToPage', function() {
       initialSettings.isDocumentModifiable_ = false;
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            setCapabilities(getCddTemplate('FooDevice'));
+      return setupSettingsAndDestinations().then(function() {
+        setCapabilities(getCddTemplate('FooDevice'));
 
-            var otherOptions = $('other-options-settings');
-            var scalingSettings = $('scaling-settings');
+        var otherOptions = $('other-options-settings');
+        var scalingSettings = $('scaling-settings');
 
-            checkSectionVisible(otherOptions, true);
-            var fitToPageContainer =
-                otherOptions.querySelector('#fit-to-page-container');
-            checkElementDisplayed(fitToPageContainer, true);
-            expectTrue(
-                fitToPageContainer.querySelector('.checkbox').checked);
-            expandMoreSettings();
-            checkSectionVisible($('media-size-settings'), true);
-            checkSectionVisible(scalingSettings, true);
+        checkSectionVisible(otherOptions, true);
+        var fitToPageContainer =
+            otherOptions.querySelector('#fit-to-page-container');
+        checkElementDisplayed(fitToPageContainer, true);
+        expectTrue(
+            fitToPageContainer.querySelector('.checkbox').checked);
+        expandMoreSettings();
+        checkSectionVisible($('media-size-settings'), true);
+        checkSectionVisible(scalingSettings, true);
 
-            // Change scaling input
-            var scalingInput = scalingSettings.querySelector('.user-value');
-            expectEquals('100', scalingInput.value);
-            scalingInput.stepUp(5);
-            expectEquals('105', scalingInput.value);
+        // Change scaling input
+        var scalingInput = scalingSettings.querySelector('.user-value');
+        expectEquals('100', scalingInput.value);
+        scalingInput.stepUp(5);
+        expectEquals('105', scalingInput.value);
 
-            // Trigger the event
-            var enterEvent = document.createEvent('Event');
-            enterEvent.initEvent('keydown');
-            enterEvent.keyCode = 'Enter';
-            scalingInput.dispatchEvent(enterEvent);
-            expectFalse(
-                fitToPageContainer.querySelector('.checkbox').checked);
+        // Trigger the event
+        var enterEvent = document.createEvent('Event');
+        enterEvent.initEvent('keydown');
+        enterEvent.keyCode = 'Enter';
+        scalingInput.dispatchEvent(enterEvent);
+        expectFalse(
+            fitToPageContainer.querySelector('.checkbox').checked);
 
-            return whenAnimationDone('other-options-collapsible');
-          });
+        return whenAnimationDone('other-options-collapsible');
+      });
     });
 
     // When the number of copies print preset is set for source 'PDF', we update
     // the copies value if capability is supported by printer.
     test('CheckNumCopiesPrintPreset', function() {
       initialSettings.isDocumentModifiable_ = false;
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         // Indicate that the number of copies print preset is set for source
@@ -690,7 +693,8 @@
         checkSectionVisible($('copies-settings'), true);
         expectEquals(
             printPresetOptions.copies,
-            parseInt($('copies-settings').querySelector('.user-value').value));
+            parseInt($('copies-settings').
+                querySelector('.user-value').value));
 
         return whenAnimationDone('other-options-collapsible');
       });
@@ -700,95 +704,83 @@
     // duplex setting if capability is supported by printer.
     test('CheckDuplexPrintPreset', function() {
       initialSettings.isDocumentModifiable_ = false;
-      setInitialSettings();
+      return setupSettingsAndDestinations().then(function() {
+        setCapabilities(getCddTemplate('FooDevice'));
 
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            setCapabilities(getCddTemplate('FooDevice'));
+        // Indicate that the duplex print preset is set to 'long edge' for
+        // source PDF.
+        var printPresetOptions = {
+          duplex: 1
+        };
+        var printPresetOptionsEvent = new Event(
+            print_preview.NativeLayer.EventType.PRINT_PRESET_OPTIONS);
+        printPresetOptionsEvent.optionsFromDocument = printPresetOptions;
+        nativeLayer.getEventTarget().
+            dispatchEvent(printPresetOptionsEvent);
 
-            // Indicate that the duplex print preset is set to 'long edge' for
-            // source PDF.
-            var printPresetOptions = {
-              duplex: 1
-            };
-            var printPresetOptionsEvent = new Event(
-                print_preview.NativeLayer.EventType.PRINT_PRESET_OPTIONS);
-            printPresetOptionsEvent.optionsFromDocument = printPresetOptions;
-            nativeLayer.getEventTarget().
-                dispatchEvent(printPresetOptionsEvent);
+        var otherOptions = $('other-options-settings');
+        checkSectionVisible(otherOptions, true);
+        var duplexContainer =
+            otherOptions.querySelector('#duplex-container');
+        checkElementDisplayed(duplexContainer, true);
+        expectTrue(duplexContainer.querySelector('.checkbox').checked);
 
-            var otherOptions = $('other-options-settings');
-            checkSectionVisible(otherOptions, true);
-            var duplexContainer =
-                otherOptions.querySelector('#duplex-container');
-            checkElementDisplayed(duplexContainer, true);
-            expectTrue(duplexContainer.querySelector('.checkbox').checked);
-
-            return whenAnimationDone('other-options-collapsible');
-          });
+        return whenAnimationDone('other-options-collapsible');
+      });
     });
 
     // Make sure that custom margins controls are properly set up.
     test('CustomMarginsControlsCheck', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            setCapabilities(getCddTemplate('FooDevice'));
+      return setupSettingsAndDestinations().then(function() {
+        setCapabilities(getCddTemplate('FooDevice'));
 
-            printPreview.printTicketStore_.marginsType.updateValue(
-                print_preview.ticket_items.MarginsTypeValue.CUSTOM);
+        printPreview.printTicketStore_.marginsType.updateValue(
+            print_preview.ticket_items.MarginsTypeValue.CUSTOM);
 
-            ['left', 'top', 'right', 'bottom'].forEach(function(margin) {
-              var control =
-                  $('preview-area').querySelector('.margin-control-' + margin);
-              assertNotEquals(null, control);
-              var input = control.querySelector('.margin-control-textbox');
-              assertTrue(input.hasAttribute('aria-label'));
-              assertNotEquals('undefined', input.getAttribute('aria-label'));
-            });
-            return whenAnimationDone('more-settings');
-          });
+        ['left', 'top', 'right', 'bottom'].forEach(function(margin) {
+          var control =
+              $('preview-area').querySelector('.margin-control-' + margin);
+          assertNotEquals(null, control);
+          var input = control.querySelector('.margin-control-textbox');
+          assertTrue(input.hasAttribute('aria-label'));
+          assertNotEquals('undefined', input.getAttribute('aria-label'));
+        });
+        return whenAnimationDone('more-settings');
+      });
     });
 
     // Page layout has zero margins. Hide header and footer option.
     test('PageLayoutHasNoMarginsHideHeaderFooter', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(
-          function() {
-            setLocalDestinations();
-            setCapabilities(getCddTemplate('FooDevice'));
+      return setupSettingsAndDestinations().then(function() {
+        setCapabilities(getCddTemplate('FooDevice'));
 
-            var otherOptions = $('other-options-settings');
-            var headerFooter =
-                otherOptions.querySelector('#header-footer-container');
+        var otherOptions = $('other-options-settings');
+        var headerFooter =
+            otherOptions.querySelector('#header-footer-container');
 
-            // Check that options are collapsed (section is visible, because
-            // duplex is available).
-            checkSectionVisible(otherOptions, true);
-            checkElementDisplayed(headerFooter, false);
+        // Check that options are collapsed (section is visible, because
+        // duplex is available).
+        checkSectionVisible(otherOptions, true);
+        checkElementDisplayed(headerFooter, false);
 
-            expandMoreSettings();
+        expandMoreSettings();
 
-            checkElementDisplayed(headerFooter, true);
+        checkElementDisplayed(headerFooter, true);
 
-            printPreview.printTicketStore_.marginsType.updateValue(
-                print_preview.ticket_items.MarginsTypeValue.CUSTOM);
-            printPreview.printTicketStore_.customMargins.updateValue(
-                new print_preview.Margins(0, 0, 0, 0));
+        printPreview.printTicketStore_.marginsType.updateValue(
+            print_preview.ticket_items.MarginsTypeValue.CUSTOM);
+        printPreview.printTicketStore_.customMargins.updateValue(
+            new print_preview.Margins(0, 0, 0, 0));
 
-            checkElementDisplayed(headerFooter, false);
+        checkElementDisplayed(headerFooter, false);
 
-            return whenAnimationDone('more-settings');
-          });
+        return whenAnimationDone('more-settings');
+      });
     });
 
     // Page layout has half-inch margins. Show header and footer option.
     test('PageLayoutHasMarginsShowHeaderFooter', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var otherOptions = $('other-options-settings');
@@ -815,21 +807,18 @@
       });
     });
 
-
     // Page layout has zero top and bottom margins. Hide header and footer
     // option.
     test('ZeroTopAndBottomMarginsHideHeaderFooter', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var otherOptions = $('other-options-settings');
         var headerFooter =
             otherOptions.querySelector('#header-footer-container');
 
-        // Check that options are collapsed (section is visible, because duplex
-        // is available).
+        // Check that options are collapsed (section is visible, because
+        // duplex is available).
         checkSectionVisible(otherOptions, true);
         checkElementDisplayed(headerFooter, false);
 
@@ -851,17 +840,15 @@
     // Page layout has zero top and half-inch bottom margin. Show header and
     // footer option.
     test('ZeroTopAndNonZeroBottomMarginShowHeaderFooter', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var otherOptions = $('other-options-settings');
         var headerFooter =
             otherOptions.querySelector('#header-footer-container');
 
-        // Check that options are collapsed (section is visible, because duplex
-        // is available).
+        // Check that options are collapsed (section is visible, because
+        // duplex is available).
         checkSectionVisible(otherOptions, true);
         checkElementDisplayed(headerFooter, false);
 
@@ -882,9 +869,7 @@
 
     // Check header footer availability with small (label) page size.
     test('SmallPaperSizeHeaderFooter', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.media_size = {
           'option': [
@@ -900,8 +885,8 @@
         var headerFooter =
             otherOptions.querySelector('#header-footer-container');
 
-        // Check that options are collapsed (section is visible, because duplex
-        // is available).
+        // Check that options are collapsed (section is visible, because
+        // duplex is available).
         checkSectionVisible(otherOptions, true);
         checkElementDisplayed(headerFooter, false);
 
@@ -926,10 +911,7 @@
 
     // Test that the color settings, one option, standard monochrome.
     test('ColorSettingsMonochrome', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         // Only one option, standard monochrome.
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
@@ -947,10 +929,7 @@
 
     // Test that the color settings, one option, custom monochrome.
     test('ColorSettingsCustomMonochrome', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         // Only one option, standard monochrome.
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
@@ -969,10 +948,7 @@
 
     // Test that the color settings, one option, standard color.
     test('ColorSettingsColor', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
           'option': [
@@ -989,10 +965,7 @@
 
     // Test that the color settings, one option, custom color.
     test('ColorSettingsCustomColor', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
           'option': [
@@ -1010,10 +983,7 @@
     // Test that the color settings, two options, both standard, defaults to
     // color.
     test('ColorSettingsBothStandardDefaultColor', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
           'option': [
@@ -1026,7 +996,8 @@
         checkSectionVisible($('color-settings'), true);
         expectEquals(
             'color',
-            $('color-settings').querySelector('.color-settings-select').value);
+            $('color-settings').querySelector(
+                '.color-settings-select').value);
 
         return whenAnimationDone('more-settings');
       });
@@ -1035,10 +1006,7 @@
     // Test that the color settings, two options, both standard, defaults to
     // monochrome.
     test('ColorSettingsBothStandardDefaultMonochrome', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
           'option': [
@@ -1051,7 +1019,8 @@
         checkSectionVisible($('color-settings'), true);
         expectEquals(
             'bw',
-            $('color-settings').querySelector('.color-settings-select').value);
+            $('color-settings').querySelector(
+                '.color-settings-select').value);
 
         return whenAnimationDone('more-settings');
       });
@@ -1060,10 +1029,7 @@
     // Test that the color settings, two options, both custom, defaults to
     // color.
     test('ColorSettingsBothCustomDefaultColor', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         device.capabilities.printer.color = {
           'option': [
@@ -1086,9 +1052,7 @@
     // Test to verify that duplex settings are set according to the printer
     // capabilities.
     test('DuplexSettingsTrue', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var otherOptions = $('other-options-settings');
@@ -1104,9 +1068,7 @@
     // Test to verify that duplex settings are set according to the printer
     // capabilities.
     test('DuplexSettingsFalse', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         var device = getCddTemplate('FooDevice');
         delete device.capabilities.printer.duplex;
         setCapabilities(device);
@@ -1127,9 +1089,7 @@
 
     // Test that changing the selected printer updates the preview.
     test('PrinterChangeUpdatesPreview', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         var previewGenerator = mock(print_preview.PreviewGenerator);
@@ -1188,10 +1148,7 @@
 
     // Test custom localized paper names.
     test('CustomPaperNames', function() {
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
-
+      return setupSettingsAndDestinations().then(function() {
         var customLocalizedMediaName = 'Vendor defined localized media name';
         var customMediaName = 'Vendor defined media name';
 
@@ -1240,20 +1197,10 @@
     // search box).
     test('AdvancedSettings1Option', function() {
       var device = getCddTemplateWithAdvancedSettings('FooDevice');
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setupAdvancedSettingsTest(device);
-
-        // Open the advanced settings overlay.
-        openAdvancedSettings();
-
-        // Check that advanced settings close button is now visible,
-        // but not the search box (only 1 capability).
-        var advancedSettingsCloseButton = $('advanced-settings').
-              querySelector('.close-button');
-        checkElementDisplayed(advancedSettingsCloseButton, true);
+      return setupSettingsAndDestinations().then(function() {
+        startAdvancedSettingsTest(device);
         checkElementDisplayed($('advanced-settings').
-             querySelector('.search-box-area'), false);
+            querySelector('.search-box-area'), false);
 
         return whenAnimationDone('more-settings');
       });
@@ -1277,20 +1224,11 @@
               ]
           }
       });
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setupAdvancedSettingsTest(device);
+      return setupSettingsAndDestinations().then(function() {
+        startAdvancedSettingsTest(device);
 
-        // Open the advanced settings overlay.
-        openAdvancedSettings();
-
-        // Check advanced settings is visible and that the search box now
-        // appears.
-        var advancedSettingsCloseButton = $('advanced-settings').
-            querySelector('.close-button');
-        checkElementDisplayed(advancedSettingsCloseButton, true);
         checkElementDisplayed($('advanced-settings').
-            querySelector('.search-box-area'), true);
+          querySelector('.search-box-area'), true);
 
         return whenAnimationDone('more-settings');
       });
@@ -1336,10 +1274,7 @@
     // an error and that the preview dialog can be recovered by selecting a
     // new destination.
     test('InvalidSettingsError', function() {
-      // Setup
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         // Manually enable the print header. This is needed since there is no
@@ -1347,7 +1282,8 @@
         printPreview.printHeader_.isEnabled = true;
 
         // There will be an error message in the preview area since the plugin
-        // is not running. However, it should not be the invalid settings error.
+        // is not running. However, it should not be the invalid settings
+        // error.
         var previewAreaEl = $('preview-area');
         var customMessageEl =
             previewAreaEl.
@@ -1355,7 +1291,8 @@
         expectFalse(customMessageEl.hidden);
         var expectedMessageStart = 'The selected printer is not available or '
             + 'not installed correctly.'
-        expectFalse(customMessageEl.textContent.includes(expectedMessageStart));
+        expectFalse(customMessageEl.textContent.includes(
+            expectedMessageStart));
 
         // Verify that the print button is enabled.
         var printHeader = $('print-header');
@@ -1371,7 +1308,8 @@
         // Should be in an error state, print button disabled, invalid custom
         // error message shown.
         expectFalse(customMessageEl.hidden);
-        expectTrue(customMessageEl.textContent.includes(expectedMessageStart));
+        expectTrue(customMessageEl.textContent.includes(
+            expectedMessageStart));
         expectTrue(printButton.disabled);
 
         // Select a new destination
@@ -1383,8 +1321,8 @@
 
         printPreview.destinationStore_.selectDestination(barDestination);
 
-        // Dispatch events indicating capabilities were fetched and new preview
-        // has loaded.
+        // Dispatch events indicating capabilities were fetched and new
+        // preview has loaded.
         setCapabilities(getCddTemplate('BarDevice'));
         var previewDoneEvent = new Event(
             print_preview.PreviewArea.EventType.PREVIEW_GENERATION_DONE);
@@ -1407,10 +1345,7 @@
           new print_preview.PreviewGenerator(printPreview.destinationStore_,
               printPreview.printTicketStore_, nativeLayer,
               printPreview.documentInfo_);
-
-      setInitialSettings();
-      return nativeLayer.whenCalled('getInitialSettings').then(function() {
-        setLocalDestinations();
+      return setupSettingsAndDestinations().then(function() {
         setCapabilities(getCddTemplate('FooDevice'));
 
         // The first request should generate draft because there was no
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.js
index db9e3de..732be78 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.js
@@ -55,6 +55,14 @@
           source: 'preference',
         },
       ],
+      images: [
+        {
+          embeddingOrigin: 'https://foo-allow.com:443',
+          origin: 'https://foo-allow.com:443',
+          setting: 'allow',
+          source: 'preference',
+        },
+      ],
       javascript: [
         {
           embeddingOrigin: 'https://foo-allow.com:443',
diff --git a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
index ba07822..97633e75b 100644
--- a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
@@ -15,6 +15,7 @@
  *   midiDevices: !Array<!RawSiteException>},
  *   notifications: !Array<!RawSiteException>},
  *   plugins: !Array<!RawSiteException>},
+ *   images: !Array<!RawSiteException>},
  *   popups: !Array<!RawSiteException>},
  *   unsandboxed_plugins: !Array<!RawSiteException>},
  * }}
@@ -45,6 +46,7 @@
     midiDevices: '',
     notifications: '',
     plugins: '',
+    images: '',
     popups: '',
     subresource_filter: '',
     unsandboxed_plugins: '',
@@ -60,6 +62,7 @@
     midiDevices: [],
     notifications: [],
     plugins: [],
+    images: [],
     popups: [],
     subresource_filter: [],
     unsandboxed_plugins: [],
diff --git a/chrome/utility/importer/firefox_importer_unittest_utils_mac.cc b/chrome/utility/importer/firefox_importer_unittest_utils_mac.cc
index ac22dfe..53f3a77 100644
--- a/chrome/utility/importer/firefox_importer_unittest_utils_mac.cc
+++ b/chrome/utility/importer/firefox_importer_unittest_utils_mac.cc
@@ -24,7 +24,6 @@
 #include "content/public/common/content_descriptors.h"
 #include "content/public/common/mojo_channel_switches.h"
 #include "ipc/ipc_channel.h"
-#include "ipc/ipc_descriptors.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_message.h"
 #include "mojo/edk/embedder/embedder.h"
diff --git a/chromeos/components/tether/active_host_unittest.cc b/chromeos/components/tether/active_host_unittest.cc
index 09344d4..12e05c1 100644
--- a/chromeos/components/tether/active_host_unittest.cc
+++ b/chromeos/components/tether/active_host_unittest.cc
@@ -37,13 +37,6 @@
       devices_equal = !other.remote_device;
     }
 
-    LOG(ERROR) << (active_host_status == other.active_host_status);
-    LOG(ERROR) << devices_equal;
-    LOG(ERROR) << (other.tether_network_guid);
-    LOG(ERROR) << (tether_network_guid);
-    LOG(ERROR) << (other.wifi_network_guid);
-    LOG(ERROR) << (wifi_network_guid);
-
     return active_host_status == other.active_host_status && devices_equal &&
            tether_network_guid == other.tether_network_guid &&
            wifi_network_guid == other.wifi_network_guid;
diff --git a/chromeos/components/tether/initializer.cc b/chromeos/components/tether/initializer.cc
index d88ae944..94ebddc 100644
--- a/chromeos/components/tether/initializer.cc
+++ b/chromeos/components/tether/initializer.cc
@@ -193,11 +193,24 @@
                                                       network_state_handler_);
   device_id_tether_network_guid_map_ =
       base::MakeUnique<DeviceIdTetherNetworkGuidMap>();
+  host_scan_cache_ = base::MakeUnique<HostScanCache>(
+      network_state_handler_, active_host_.get(),
+      tether_host_response_recorder_.get(),
+      device_id_tether_network_guid_map_.get());
+  clock_ = base::MakeUnique<base::DefaultClock>();
+  host_scanner_ = base::MakeUnique<HostScanner>(
+      tether_host_fetcher_.get(), ble_connection_manager_.get(),
+      host_scan_device_prioritizer_.get(), tether_host_response_recorder_.get(),
+      notification_presenter_.get(), device_id_tether_network_guid_map_.get(),
+      host_scan_cache_.get(), clock_.get());
+  host_scan_scheduler_ = base::MakeUnique<HostScanScheduler>(
+      network_state_handler_, host_scanner_.get());
   tether_connector_ = base::MakeUnique<TetherConnector>(
       network_state_handler_, wifi_hotspot_connector_.get(), active_host_.get(),
       tether_host_fetcher_.get(), ble_connection_manager_.get(),
       tether_host_response_recorder_.get(),
-      device_id_tether_network_guid_map_.get());
+      device_id_tether_network_guid_map_.get(), host_scan_cache_.get(),
+      notification_presenter_.get());
   network_configuration_remover_ =
       base::MakeUnique<NetworkConfigurationRemover>(
           network_state_handler_, managed_network_configuration_handler_);
@@ -214,18 +227,6 @@
       base::MakeUnique<NetworkConnectionHandlerTetherDelegate>(
           network_connection_handler_, tether_connector_.get(),
           tether_disconnector_.get());
-  host_scan_cache_ = base::MakeUnique<HostScanCache>(
-      network_state_handler_, active_host_.get(),
-      tether_host_response_recorder_.get(),
-      device_id_tether_network_guid_map_.get());
-  clock_ = base::MakeUnique<base::DefaultClock>();
-  host_scanner_ = base::MakeUnique<HostScanner>(
-      tether_host_fetcher_.get(), ble_connection_manager_.get(),
-      host_scan_device_prioritizer_.get(), tether_host_response_recorder_.get(),
-      notification_presenter_.get(), device_id_tether_network_guid_map_.get(),
-      host_scan_cache_.get(), clock_.get());
-  host_scan_scheduler_ = base::MakeUnique<HostScanScheduler>(
-      network_state_handler_, host_scanner_.get());
 
   // Because Initializer is created on each user log in, it's appropriate to
   // call this method now.
diff --git a/chromeos/components/tether/initializer.h b/chromeos/components/tether/initializer.h
index ef17a1a..b6f0b58 100644
--- a/chromeos/components/tether/initializer.h
+++ b/chromeos/components/tether/initializer.h
@@ -124,6 +124,10 @@
       active_host_network_state_updater_;
   std::unique_ptr<DeviceIdTetherNetworkGuidMap>
       device_id_tether_network_guid_map_;
+  std::unique_ptr<HostScanCache> host_scan_cache_;
+  std::unique_ptr<base::DefaultClock> clock_;
+  std::unique_ptr<HostScanner> host_scanner_;
+  std::unique_ptr<HostScanScheduler> host_scan_scheduler_;
   std::unique_ptr<TetherConnector> tether_connector_;
   std::unique_ptr<TetherDisconnector> tether_disconnector_;
   std::unique_ptr<NetworkConfigurationRemover> network_configuration_remover_;
@@ -131,10 +135,6 @@
       network_connection_handler_tether_delegate_;
   std::unique_ptr<TetherNetworkDisconnectionHandler>
       tether_network_disconnection_handler_;
-  std::unique_ptr<HostScanCache> host_scan_cache_;
-  std::unique_ptr<base::DefaultClock> clock_;
-  std::unique_ptr<HostScanner> host_scanner_;
-  std::unique_ptr<HostScanScheduler> host_scan_scheduler_;
 
   base::WeakPtrFactory<Initializer> weak_ptr_factory_;
 
diff --git a/chromeos/components/tether/network_connection_handler_tether_delegate_unittest.cc b/chromeos/components/tether/network_connection_handler_tether_delegate_unittest.cc
index 60160ca..aac1503 100644
--- a/chromeos/components/tether/network_connection_handler_tether_delegate_unittest.cc
+++ b/chromeos/components/tether/network_connection_handler_tether_delegate_unittest.cc
@@ -73,7 +73,9 @@
                         nullptr /* tether_host_fetcher */,
                         nullptr /* connection_manager */,
                         nullptr /* tether_host_response_recorder */,
-                        nullptr /* device_id_tether_network_guid_map */) {}
+                        nullptr /* device_id_tether_network_guid_map */,
+                        nullptr /* host_scan_cache */,
+                        nullptr /* notification_presenter */) {}
   ~MockTetherConnector() override {}
 
   MOCK_METHOD3(
@@ -149,4 +151,4 @@
 
 }  // namespace tether
 
-}  // namespace cryptauth
+}  // namespace chromeos
diff --git a/chromeos/components/tether/tether_connector.cc b/chromeos/components/tether/tether_connector.cc
index 02cd147..7eb23ad 100644
--- a/chromeos/components/tether/tether_connector.cc
+++ b/chromeos/components/tether/tether_connector.cc
@@ -7,9 +7,12 @@
 #include "base/bind.h"
 #include "chromeos/components/tether/active_host.h"
 #include "chromeos/components/tether/device_id_tether_network_guid_map.h"
+#include "chromeos/components/tether/host_scan_cache.h"
+#include "chromeos/components/tether/notification_presenter.h"
 #include "chromeos/components/tether/tether_host_fetcher.h"
 #include "chromeos/components/tether/wifi_hotspot_connector.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
 #include "components/proximity_auth/logging/logging.h"
 
@@ -24,7 +27,9 @@
     TetherHostFetcher* tether_host_fetcher,
     BleConnectionManager* connection_manager,
     TetherHostResponseRecorder* tether_host_response_recorder,
-    DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map)
+    DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map,
+    HostScanCache* host_scan_cache,
+    NotificationPresenter* notification_presenter)
     : network_state_handler_(network_state_handler),
       wifi_hotspot_connector_(wifi_hotspot_connector),
       active_host_(active_host),
@@ -32,6 +37,8 @@
       connection_manager_(connection_manager),
       tether_host_response_recorder_(tether_host_response_recorder),
       device_id_tether_network_guid_map_(device_id_tether_network_guid_map),
+      host_scan_cache_(host_scan_cache),
+      notification_presenter_(notification_presenter),
       weak_ptr_factory_(this) {}
 
 TetherConnector::~TetherConnector() {
@@ -71,6 +78,13 @@
             device_id_pending_connection_));
   }
 
+  if (host_scan_cache_->DoesHostRequireSetup(tether_network_guid)) {
+    const std::string& device_name =
+        network_state_handler_->GetNetworkStateFromGuid(tether_network_guid)
+            ->name();
+    notification_presenter_->NotifySetupRequired(device_name);
+  }
+
   device_id_pending_connection_ = device_id;
   success_callback_ = success_callback;
   error_callback_ = error_callback;
@@ -185,6 +199,8 @@
 
   DCHECK(device_id == tether_host_to_connect->GetDeviceId());
 
+  // TODO (hansberry): Indicate to ConnectTetheringOperation if first-time setup
+  // is required, so that it can adjust its timeout duration.
   connect_tethering_operation_ =
       ConnectTetheringOperation::Factory::NewInstance(
           *tether_host_to_connect, connection_manager_,
@@ -197,6 +213,8 @@
   DCHECK(!device_id_pending_connection_.empty());
   DCHECK(!error_callback_.is_null());
 
+  notification_presenter_->RemoveSetupRequiredNotification();
+
   // Save a copy of the callback before resetting it below.
   network_handler::StringResultCallback error_callback = error_callback_;
 
@@ -215,6 +233,8 @@
   DCHECK(device_id_pending_connection_ == device_id);
   DCHECK(!success_callback_.is_null());
 
+  notification_presenter_->RemoveSetupRequiredNotification();
+
   // Save a copy of the callback before resetting it below.
   base::Closure success_callback = success_callback_;
 
diff --git a/chromeos/components/tether/tether_connector.h b/chromeos/components/tether/tether_connector.h
index 9f7f5919..5498f893 100644
--- a/chromeos/components/tether/tether_connector.h
+++ b/chromeos/components/tether/tether_connector.h
@@ -19,6 +19,8 @@
 class ActiveHost;
 class BleConnectionManager;
 class DeviceIdTetherNetworkGuidMap;
+class HostScanCache;
+class NotificationPresenter;
 class TetherHostFetcher;
 class TetherHostResponseRecorder;
 class WifiHotspotConnector;
@@ -37,7 +39,9 @@
       TetherHostFetcher* tether_host_fetcher,
       BleConnectionManager* connection_manager,
       TetherHostResponseRecorder* tether_host_response_recorder,
-      DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map);
+      DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map,
+      HostScanCache* host_scan_cache,
+      NotificationPresenter* notification_presenter);
   virtual ~TetherConnector();
 
   virtual void ConnectToNetwork(
@@ -78,6 +82,8 @@
   BleConnectionManager* connection_manager_;
   TetherHostResponseRecorder* tether_host_response_recorder_;
   DeviceIdTetherNetworkGuidMap* device_id_tether_network_guid_map_;
+  HostScanCache* host_scan_cache_;
+  NotificationPresenter* notification_presenter_;
 
   std::string device_id_pending_connection_;
   base::Closure success_callback_;
diff --git a/chromeos/components/tether/tether_connector_unittest.cc b/chromeos/components/tether/tether_connector_unittest.cc
index df7e664..4910a09 100644
--- a/chromeos/components/tether/tether_connector_unittest.cc
+++ b/chromeos/components/tether/tether_connector_unittest.cc
@@ -10,6 +10,8 @@
 #include "chromeos/components/tether/device_id_tether_network_guid_map.h"
 #include "chromeos/components/tether/fake_active_host.h"
 #include "chromeos/components/tether/fake_ble_connection_manager.h"
+#include "chromeos/components/tether/fake_host_scan_cache.h"
+#include "chromeos/components/tether/fake_notification_presenter.h"
 #include "chromeos/components/tether/fake_tether_host_fetcher.h"
 #include "chromeos/components/tether/fake_wifi_hotspot_connector.h"
 #include "chromeos/components/tether/mock_tether_host_response_recorder.h"
@@ -131,6 +133,9 @@
         base::MakeUnique<MockTetherHostResponseRecorder>();
     device_id_tether_network_guid_map_ =
         base::MakeUnique<DeviceIdTetherNetworkGuidMap>();
+    fake_host_scan_cache_ = base::MakeUnique<FakeHostScanCache>();
+    fake_notification_presenter_ =
+        base::MakeUnique<FakeNotificationPresenter>();
 
     result_.clear();
 
@@ -139,7 +144,8 @@
         fake_active_host_.get(), fake_tether_host_fetcher_.get(),
         fake_ble_connection_manager_.get(),
         mock_tether_host_response_recorder_.get(),
-        device_id_tether_network_guid_map_.get()));
+        device_id_tether_network_guid_map_.get(), fake_host_scan_cache_.get(),
+        fake_notification_presenter_.get()));
 
     SetUpTetherNetworks();
   }
@@ -164,16 +170,31 @@
     // Add a tether network corresponding to both of the test devices. These
     // networks are expected to be added already before
     // TetherConnector::ConnectToNetwork is called.
+    AddTetherNetwork(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()),
+                     "TetherNetworkName1", "TetherNetworkCarrier1",
+                     85 /* battery_percentage */, 75 /* signal_strength */,
+                     true /* has_connected_to_host */,
+                     false /* setup_required */);
+    AddTetherNetwork(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()),
+                     "TetherNetworkName2", "TetherNetworkCarrier2",
+                     90 /* battery_percentage */, 50 /* signal_strength */,
+                     true /* has_connected_to_host */,
+                     true /* setup_required */);
+  }
+
+  virtual void AddTetherNetwork(const std::string& tether_network_guid,
+                                const std::string& device_name,
+                                const std::string& carrier,
+                                int battery_percentage,
+                                int signal_strength,
+                                bool has_connected_to_host,
+                                bool setup_required) {
     network_state_handler()->AddTetherNetworkState(
-        GetTetherNetworkGuid(test_devices_[0].GetDeviceId()),
-        "TetherNetworkName1", "TetherNetworkCarrier1",
-        85 /* battery_percentage */, 75 /* signal_strength */,
-        true /* has_connected_to_host */);
-    network_state_handler()->AddTetherNetworkState(
-        GetTetherNetworkGuid(test_devices_[1].GetDeviceId()),
-        "TetherNetworkName2", "TetherNetworkCarrier2",
-        90 /* battery_percentage */, 50 /* signal_strength */,
-        true /* has_connected_to_host */);
+        tether_network_guid, device_name, carrier, battery_percentage,
+        signal_strength, has_connected_to_host);
+    fake_host_scan_cache_->SetHostScanResult(tether_network_guid, device_name,
+                                             carrier, battery_percentage,
+                                             signal_strength, setup_required);
   }
 
   void SuccessfullyJoinWifiNetwork() {
@@ -213,6 +234,8 @@
   // TODO(hansberry): Use a fake for this when a real mapping scheme is created.
   std::unique_ptr<DeviceIdTetherNetworkGuidMap>
       device_id_tether_network_guid_map_;
+  std::unique_ptr<FakeHostScanCache> fake_host_scan_cache_;
+  std::unique_ptr<FakeNotificationPresenter> fake_notification_presenter_;
 
   std::string result_;
 
@@ -292,6 +315,30 @@
   EXPECT_EQ(NetworkConnectionHandler::kErrorConnectFailed, GetResultAndReset());
 }
 
+TEST_F(TetherConnectorTest, TestConnectTetheringOperationFails_SetupRequired) {
+  EXPECT_FALSE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  CallConnect(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()));
+
+  EXPECT_TRUE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  fake_tether_host_fetcher_->InvokePendingCallbacks();
+
+  EXPECT_TRUE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  fake_operation_factory_->created_operations()[0]->SendFailedResponse(
+      ConnectTetheringResponse_ResponseCode::
+          ConnectTetheringResponse_ResponseCode_UNKNOWN_ERROR);
+
+  EXPECT_FALSE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  EXPECT_EQ(NetworkConnectionHandler::kErrorConnectFailed, GetResultAndReset());
+}
+
 TEST_F(TetherConnectorTest, TestConnectingToWifiFails) {
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
@@ -363,6 +410,8 @@
   EXPECT_EQ(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()),
             fake_active_host_->GetTetherNetworkGuid());
   EXPECT_TRUE(fake_active_host_->GetWifiNetworkGuid().empty());
+  EXPECT_FALSE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
 
   fake_tether_host_fetcher_->InvokePendingCallbacks();
 
@@ -394,6 +443,34 @@
   EXPECT_EQ(kSuccessResult, GetResultAndReset());
 }
 
+TEST_F(TetherConnectorTest, TestSuccessfulConnection_SetupRequired) {
+  EXPECT_FALSE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  CallConnect(GetTetherNetworkGuid(test_devices_[1].GetDeviceId()));
+
+  EXPECT_TRUE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  fake_tether_host_fetcher_->InvokePendingCallbacks();
+
+  EXPECT_TRUE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  fake_operation_factory_->created_operations()[0]->SendSuccessfulResponse(
+      kSsid, kPassword);
+
+  EXPECT_TRUE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  SuccessfullyJoinWifiNetwork();
+
+  EXPECT_FALSE(
+      fake_notification_presenter_->is_setup_required_notification_shown());
+
+  EXPECT_EQ(kSuccessResult, GetResultAndReset());
+}
+
 TEST_F(TetherConnectorTest,
        TestNewConnectionAttemptDuringFetch_DifferentDevice) {
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
diff --git a/chromeos/components/tether/tether_disconnector_unittest.cc b/chromeos/components/tether/tether_disconnector_unittest.cc
index bb14ccc..b421426 100644
--- a/chromeos/components/tether/tether_disconnector_unittest.cc
+++ b/chromeos/components/tether/tether_disconnector_unittest.cc
@@ -48,7 +48,7 @@
 
 class TestNetworkConnectionHandler : public NetworkConnectionHandler {
  public:
-  TestNetworkConnectionHandler(base::Closure disconnect_callback)
+  explicit TestNetworkConnectionHandler(base::Closure disconnect_callback)
       : disconnect_callback_(disconnect_callback) {}
   ~TestNetworkConnectionHandler() override {}
 
@@ -105,7 +105,9 @@
                         nullptr /* tether_host_fetcher */,
                         nullptr /* connection_manager */,
                         nullptr /* tether_host_response_recorder */,
-                        nullptr /* device_id_tether_network_guid_map */),
+                        nullptr /* device_id_tether_network_guid_map */,
+                        nullptr /* host_scan_cache */,
+                        nullptr /* notification_presenter */),
         should_cancel_successfully_(true) {}
   ~TestTetherConnector() override {}
 
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index dff74c4..8db65350 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -201,6 +201,8 @@
 
     vpn_provider_type_ = vpn_provider_type;
     return true;
+  } else if (key == shill::kTetheringProperty) {
+    return GetStringValue(key, value, &tethering_state_);
   }
   return false;
 }
@@ -364,6 +366,11 @@
   connection_state_ = connection_state;
 }
 
+bool NetworkState::IsUsingMobileData() const {
+  return type() == shill::kTypeCellular || type() == chromeos::kTypeTether ||
+         tethering_state() == shill::kTetheringConfirmedState;
+}
+
 bool NetworkState::IsDynamicWep() const {
   return security_class_ == shill::kSecurityWep &&
          eap_key_mgmt_ == shill::kKeyManagementIEEE8021X;
diff --git a/chromeos/network/network_state.h b/chromeos/network/network_state.h
index d43c562..18dba041 100644
--- a/chromeos/network/network_state.h
+++ b/chromeos/network/network_state.h
@@ -100,6 +100,7 @@
   const std::string& roaming() const { return roaming_; }
   const std::string& payment_url() const { return payment_url_; }
   bool cellular_out_of_credits() const { return cellular_out_of_credits_; }
+  const std::string& tethering_state() const { return tethering_state_; }
 
   // VPN property accessors
   const std::string& vpn_provider_type() const { return vpn_provider_type_; }
@@ -123,6 +124,9 @@
   const std::string& tether_guid() const { return tether_guid_; }
   void set_tether_guid(const std::string& guid) { tether_guid_ = guid; }
 
+  // Returns true if current connection is using mobile data.
+  bool IsUsingMobileData() const;
+
   // Returns true if the network securty is WEP_8021x (Dynamic WEP)
   bool IsDynamicWep() const;
 
@@ -226,6 +230,7 @@
   std::string roaming_;
   std::string payment_url_;
   bool cellular_out_of_credits_ = false;
+  std::string tethering_state_;
 
   // VPN properties, used to construct the display name and to show the correct
   // configuration dialog.
@@ -235,6 +240,7 @@
   // Tether properties.
   std::string carrier_;
   int battery_percentage_;
+
   // Whether the current device has already connected to the tether host device
   // providing the hotspot corresponding to this NetworkState.
   // Note: this means that the current device has already connected to the
diff --git a/components/background_task_scheduler/BUILD.gn b/components/background_task_scheduler/BUILD.gn
index e23f4837..f77a15c 100644
--- a/components/background_task_scheduler/BUILD.gn
+++ b/components/background_task_scheduler/BUILD.gn
@@ -31,6 +31,7 @@
       "android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerGcmNetworkManager.java",
       "android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerJobService.java",
       "android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerPrefs.java",
+      "android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java",
       "android/java/src/org/chromium/components/background_task_scheduler/BundleToPersistableBundleConverter.java",
       "android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java",
       "android/java/src/org/chromium/components/background_task_scheduler/TaskInfo.java",
@@ -68,6 +69,7 @@
       "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskServiceTest.java",
       "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerPrefsTest.java",
       "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerTest.java",
+      "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java",
       "android/junit/src/org/chromium/components/background_task_scheduler/ShadowGcmNetworkManager.java",
       "android/junit/src/org/chromium/components/background_task_scheduler/TestBackgroundTask.java",
     ]
diff --git a/components/background_task_scheduler/OWNERS b/components/background_task_scheduler/OWNERS
index 4d5ae7f..5c8a6c0 100644
--- a/components/background_task_scheduler/OWNERS
+++ b/components/background_task_scheduler/OWNERS
@@ -1,4 +1,5 @@
 awdf@chromium.org
 dtrainor@chromium.org
+fgorski@chromium.org
 nyquist@chromium.org
 
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskService.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskService.java
index 4a9521a..b109980 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskService.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskService.java
@@ -98,6 +98,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
+                BackgroundTaskSchedulerUma.getInstance().reportTaskStarted(taskParams.getTaskId());
                 taskNeedsBackgroundProcessing.set(
                         backgroundTask.onStartTask(ContextUtils.getApplicationContext(), taskParams,
                                 new TaskFinishedCallbackGcmTaskService(waiter)));
@@ -115,6 +116,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
+                BackgroundTaskSchedulerUma.getInstance().reportTaskStopped(taskParams.getTaskId());
                 taskNeedsRescheduling.set(backgroundTask.onStopTask(
                         ContextUtils.getApplicationContext(), taskParams));
             }
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskJobService.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskJobService.java
index b53b599e..a3e6ce49 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskJobService.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskJobService.java
@@ -76,6 +76,8 @@
 
         TaskParameters taskParams =
                 BackgroundTaskSchedulerJobService.getTaskParametersFromJobParameters(params);
+
+        BackgroundTaskSchedulerUma.getInstance().reportTaskStarted(taskParams.getTaskId());
         boolean taskNeedsBackgroundProcessing = backgroundTask.onStartTask(getApplicationContext(),
                 taskParams, new TaskFinishedCallbackJobService(this, backgroundTask, params));
 
@@ -96,6 +98,7 @@
 
         TaskParameters taskParams =
                 BackgroundTaskSchedulerJobService.getTaskParametersFromJobParameters(params);
+        BackgroundTaskSchedulerUma.getInstance().reportTaskStopped(taskParams.getTaskId());
         boolean taskNeedsReschedule =
                 backgroundTask.onStopTask(getApplicationContext(), taskParams);
         mCurrentTasks.remove(params.getJobId());
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskScheduler.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskScheduler.java
index e690273..bf85375 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskScheduler.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskScheduler.java
@@ -77,6 +77,7 @@
     public boolean schedule(Context context, TaskInfo taskInfo) {
         ThreadUtils.assertOnUiThread();
         boolean success = mSchedulerDelegate.schedule(context, taskInfo);
+        BackgroundTaskSchedulerUma.getInstance().reportTaskScheduled(taskInfo.getTaskId(), success);
         if (success) {
             BackgroundTaskSchedulerPrefs.addScheduledTask(taskInfo);
         }
@@ -91,6 +92,7 @@
      */
     public void cancel(Context context, int taskId) {
         ThreadUtils.assertOnUiThread();
+        BackgroundTaskSchedulerUma.getInstance().reportTaskCanceled(taskId);
         BackgroundTaskSchedulerPrefs.removeScheduledTask(taskId);
         mSchedulerDelegate.cancel(context, taskId);
     }
@@ -105,14 +107,19 @@
     public void checkForOSUpgrade(Context context) {
         int oldSdkInt = BackgroundTaskSchedulerPrefs.getLastSdkVersion();
         int newSdkInt = Build.VERSION.SDK_INT;
-        // No OS upgrade detected.
-        if (oldSdkInt == newSdkInt) return;
 
-        // Save the current SDK version to preferences.
-        BackgroundTaskSchedulerPrefs.setLastSdkVersion(newSdkInt);
+        if (oldSdkInt != newSdkInt) {
+            // Save the current SDK version to preferences.
+            BackgroundTaskSchedulerPrefs.setLastSdkVersion(newSdkInt);
+        }
 
-        // Check for OS upgrades forcing delegate change or "just in case" rescheduling.
-        if (!osUpgradeChangesDelegateType(oldSdkInt, newSdkInt)) return;
+        // No OS upgrade detected or OS upgrade does not change delegate.
+        if (oldSdkInt == newSdkInt || !osUpgradeChangesDelegateType(oldSdkInt, newSdkInt)) {
+            BackgroundTaskSchedulerUma.getInstance().flushStats();
+            return;
+        }
+
+        BackgroundTaskSchedulerUma.getInstance().removeCachedStats();
 
         // Explicitly create and invoke old delegate type to cancel all scheduled tasks.
         // All preference entries are kept until reschedule call, which removes then then.
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
new file mode 100644
index 0000000..77252d7c
--- /dev/null
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
@@ -0,0 +1,248 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.background_task_scheduler;
+
+import android.content.SharedPreferences;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class BackgroundTaskSchedulerUma {
+    // BackgroundTaskId defined in tools/metrics/histograms/enums.xml
+    static final int BACKGROUND_TASK_TEST = 0;
+    static final int BACKGROUND_TASK_OMAHA = 1;
+    static final int BACKGROUND_TASK_GCM = 2;
+    static final int BACKGROUND_TASK_NOTIFICATIONS = 3;
+    static final int BACKGROUND_TASK_WEBVIEW_MINIDUMP = 4;
+    static final int BACKGROUND_TASK_CHROME_MINIDUMP = 5;
+    static final int BACKGROUND_TASK_OFFLINE_PAGES = 6;
+    static final int BACKGROUND_TASK_OFFLINE_PREFETCH = 7;
+    // Keep this one at the end and increment appropriately when adding new tasks.
+    static final int BACKGROUND_TASK_COUNT = 8;
+
+    static final String KEY_CACHED_UMA = "bts_cached_uma";
+
+    private static BackgroundTaskSchedulerUma sInstance;
+
+    private static class CachedUmaEntry {
+        private static final String SEPARATOR = ":";
+        private String mEvent;
+        private int mValue;
+        private int mCount;
+
+        /**
+         * Parses a cached UMA entry from a string.
+         *
+         * @param entry A serialized entry from preferences store.
+         * @return A parsed CachedUmaEntry object, or <c>null</c> if parsing failed.
+         */
+        public static CachedUmaEntry parseEntry(String entry) {
+            if (entry == null) return null;
+
+            String[] entryParts = entry.split(SEPARATOR);
+            if (entryParts.length != 3 || entryParts[0].isEmpty() || entryParts[1].isEmpty()
+                    || entryParts[2].isEmpty()) {
+                return null;
+            }
+            int value = -1;
+            int count = -1;
+            try {
+                value = Integer.parseInt(entryParts[1]);
+                count = Integer.parseInt(entryParts[2]);
+            } catch (NumberFormatException e) {
+                return null;
+            }
+            return new CachedUmaEntry(entryParts[0], value, count);
+        }
+
+        /** Returns a string for partial matching of the prefs entry. */
+        public static String getStringForPartialMatching(String event, int value) {
+            return event + SEPARATOR + value + SEPARATOR;
+        }
+
+        public CachedUmaEntry(String event, int value, int count) {
+            mEvent = event;
+            mValue = value;
+            mCount = count;
+        }
+
+        /** Converts cached UMA entry to a string in format: EVENT:VALUE:COUNT. */
+        public String toString() {
+            return mEvent + SEPARATOR + mValue + SEPARATOR + mCount;
+        }
+
+        /** Gets the name of the event (UMA). */
+        public String getEvent() {
+            return mEvent;
+        }
+
+        /** Gets the value of the event (concrete value of the enum). */
+        public int getValue() {
+            return mValue;
+        }
+
+        /** Gets the count of events that happened. */
+        public int getCount() {
+            return mCount;
+        }
+
+        /** Increments the count of the event. */
+        public void increment() {
+            mCount++;
+        }
+    }
+
+    public static BackgroundTaskSchedulerUma getInstance() {
+        if (sInstance == null) {
+            sInstance = new BackgroundTaskSchedulerUma();
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public static void setInstanceForTesting(BackgroundTaskSchedulerUma instance) {
+        sInstance = instance;
+    }
+
+    /** Reports metrics for task scheduling and whether it was successful. */
+    public void reportTaskScheduled(int taskId, boolean success) {
+        if (success) {
+            cacheEvent("Android.BackgroundTaskScheduler.TaskScheduled.Success",
+                    toUmaEnumValueFromTaskId(taskId));
+        } else {
+            cacheEvent("Android.BackgroundTaskScheduler.TaskScheduled.Failure",
+                    toUmaEnumValueFromTaskId(taskId));
+        }
+    }
+
+    /** Reports metrics for task canceling. */
+    public void reportTaskCanceled(int taskId) {
+        cacheEvent(
+                "Android.BackgroundTaskScheduler.TaskCanceled", toUmaEnumValueFromTaskId(taskId));
+    }
+
+    /** Reports metrics for starting a task. */
+    public void reportTaskStarted(int taskId) {
+        cacheEvent("Android.BackgroundTaskScheduler.TaskStarted", toUmaEnumValueFromTaskId(taskId));
+    }
+
+    /** Reports metrics for stopping a task. */
+    public void reportTaskStopped(int taskId) {
+        cacheEvent("Android.BackgroundTaskScheduler.TaskStopped", toUmaEnumValueFromTaskId(taskId));
+    }
+
+    /** Method that actually invokes histogram recording. Extracted for testing. */
+    @VisibleForTesting
+    void recordEnumeratedHistogram(String histogram, int value, int maxCount) {
+        RecordHistogram.recordEnumeratedHistogram(histogram, value, maxCount);
+    }
+
+    /** Records histograms for cached stats. Should only be called when native is initialized. */
+    public void flushStats() {
+        assertNativeIsLoaded();
+        ThreadUtils.assertOnUiThread();
+
+        Set<String> cachedUmaStrings = getCachedUmaEntries(ContextUtils.getAppSharedPreferences());
+
+        for (String cachedUmaString : cachedUmaStrings) {
+            CachedUmaEntry entry = CachedUmaEntry.parseEntry(cachedUmaString);
+            if (entry == null) continue;
+            for (int i = 0; i < entry.getCount(); i++) {
+                recordEnumeratedHistogram(
+                        entry.getEvent(), entry.getValue(), BACKGROUND_TASK_COUNT);
+            }
+        }
+
+        // Once all metrics are reported, we can simply remove the shared preference key.
+        removeCachedStats();
+    }
+
+    /** Removes all of the cached stats without reporting. */
+    public void removeCachedStats() {
+        ThreadUtils.assertOnUiThread();
+        ContextUtils.getAppSharedPreferences().edit().remove(KEY_CACHED_UMA).apply();
+    }
+
+    /** Caches the event to be reported through UMA in shared preferences. */
+    @VisibleForTesting
+    void cacheEvent(String event, int value) {
+        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+        Set<String> cachedUmaStrings = getCachedUmaEntries(prefs);
+        String partialMatch = CachedUmaEntry.getStringForPartialMatching(event, value);
+
+        String existingEntry = null;
+        for (String cachedUmaString : cachedUmaStrings) {
+            if (cachedUmaString.startsWith(partialMatch)) {
+                existingEntry = cachedUmaString;
+                break;
+            }
+        }
+
+        Set<String> setToWriteBack = new HashSet<>(cachedUmaStrings);
+        CachedUmaEntry entry = null;
+        if (existingEntry != null) {
+            entry = CachedUmaEntry.parseEntry(existingEntry);
+            if (entry == null) {
+                entry = new CachedUmaEntry(event, value, 1);
+            }
+            setToWriteBack.remove(existingEntry);
+            entry.increment();
+        } else {
+            entry = new CachedUmaEntry(event, value, 1);
+        }
+
+        setToWriteBack.add(entry.toString());
+        updateCachedUma(prefs, setToWriteBack);
+    }
+
+    @VisibleForTesting
+    static int toUmaEnumValueFromTaskId(int taskId) {
+        switch (taskId) {
+            case TaskIds.TEST:
+                return BACKGROUND_TASK_TEST;
+            case TaskIds.OMAHA_JOB_ID:
+                return BACKGROUND_TASK_OMAHA;
+            case TaskIds.GCM_BACKGROUND_TASK_JOB_ID:
+                return BACKGROUND_TASK_GCM;
+            case TaskIds.NOTIFICATION_SERVICE_JOB_ID:
+                return BACKGROUND_TASK_NOTIFICATIONS;
+            case TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID:
+                return BACKGROUND_TASK_WEBVIEW_MINIDUMP;
+            case TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID:
+                return BACKGROUND_TASK_CHROME_MINIDUMP;
+            case TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID:
+                return BACKGROUND_TASK_OFFLINE_PAGES;
+            case TaskIds.OFFLINE_PAGES_PREFETCH_JOB_ID:
+                return BACKGROUND_TASK_OFFLINE_PREFETCH;
+            default:
+                assert false;
+        }
+        // Returning a value that is not expected to ever be reported.
+        return BACKGROUND_TASK_TEST;
+    }
+
+    @VisibleForTesting
+    static Set<String> getCachedUmaEntries(SharedPreferences prefs) {
+        return prefs.getStringSet(KEY_CACHED_UMA, new HashSet<String>(1));
+    }
+
+    @VisibleForTesting
+    static void updateCachedUma(SharedPreferences prefs, Set<String> cachedUma) {
+        ThreadUtils.assertOnUiThread();
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putStringSet(KEY_CACHED_UMA, cachedUma);
+        editor.apply();
+    }
+
+    void assertNativeIsLoaded() {
+        assert LibraryLoader.isInitialized();
+    }
+}
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
index d5b5b78..e4a39529 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskIds.java
@@ -9,6 +9,8 @@
  * that there is no overlap of task IDs between different users of the BackgroundTaskScheduler.
  */
 public final class TaskIds {
+    // When adding your job id to the list below, remember to make a corresponding update to the
+    // BackgroundTaskSchedulerUma#toUmaEnumValueFromTaskId(int) method.
     public static final int TEST = 0x00008378;
     public static final int OMAHA_JOB_ID = 0x00011684;
 
diff --git a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskServiceTest.java b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskServiceTest.java
index 36018c4..b955ffa1 100644
--- a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskServiceTest.java
+++ b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskGcmTaskServiceTest.java
@@ -6,6 +6,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.os.Build;
@@ -38,6 +41,8 @@
     static boolean sNeedsRescheduling;
     @Mock
     private BackgroundTaskSchedulerDelegate mDelegate;
+    @Mock
+    private BackgroundTaskSchedulerUma mBackgroundTaskSchedulerUma;
 
     @Before
     public void setUp() {
@@ -45,6 +50,7 @@
         ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
         BackgroundTaskSchedulerFactory.setSchedulerForTesting(
                 new BackgroundTaskScheduler(mDelegate));
+        BackgroundTaskSchedulerUma.setInstanceForTesting(mBackgroundTaskSchedulerUma);
         sReturnThroughCallback = false;
         sNeedsRescheduling = false;
         sLastTask = null;
@@ -87,6 +93,8 @@
 
         assertEquals(parameters.getTaskId(), TaskIds.TEST);
         assertEquals(parameters.getExtras().getString("foo"), "bar");
+
+        verify(mBackgroundTaskSchedulerUma, times(1)).reportTaskStarted(eq(TaskIds.TEST));
     }
 
     @Test
diff --git a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerTest.java b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerTest.java
index 269e93f..1fa1b468 100644
--- a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerTest.java
+++ b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerTest.java
@@ -49,6 +49,8 @@
 
     @Mock
     private BackgroundTaskSchedulerDelegate mDelegate;
+    @Mock
+    private BackgroundTaskSchedulerUma mBackgroundTaskSchedulerUma;
     private ShadowGcmNetworkManager mGcmNetworkManager;
 
     @Before
@@ -57,6 +59,7 @@
         ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
         BackgroundTaskSchedulerFactory.setSchedulerForTesting(
                 new BackgroundTaskScheduler(mDelegate));
+        BackgroundTaskSchedulerUma.setInstanceForTesting(mBackgroundTaskSchedulerUma);
         TestBackgroundTask.reset();
 
         // Initialize Google Play Services and GCM Network Manager for upgrade testing.
@@ -75,6 +78,8 @@
         assertTrue(BackgroundTaskSchedulerPrefs.getScheduledTasks().contains(
                 TASK.getBackgroundTaskClass().getName()));
         verify(mDelegate, times(1)).schedule(eq(RuntimeEnvironment.application), eq(TASK));
+        verify(mBackgroundTaskSchedulerUma, times(1))
+                .reportTaskScheduled(eq(TaskIds.TEST), eq(true));
     }
 
     @Test
diff --git a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
new file mode 100644
index 0000000..405ef75
--- /dev/null
+++ b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
@@ -0,0 +1,184 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.background_task_scheduler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.Feature;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+import java.util.Set;
+
+/** Unit tests for {@link BackgroundTaskSchedulerUma}. */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class BackgroundTaskSchedulerUmaTest {
+    @Spy
+    private BackgroundTaskSchedulerUma mUmaSpy;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
+        BackgroundTaskSchedulerUma.setInstanceForTesting(mUmaSpy);
+        doNothing().when(mUmaSpy).assertNativeIsLoaded();
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testToUmaEnumValueFromTaskId() {
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_TEST,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(TaskIds.TEST));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OMAHA,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(TaskIds.OMAHA_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_GCM,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.GCM_BACKGROUND_TASK_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_NOTIFICATIONS,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.NOTIFICATION_SERVICE_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_WEBVIEW_MINIDUMP,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_CHROME_MINIDUMP,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OFFLINE_PAGES,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OFFLINE_PREFETCH,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.OFFLINE_PAGES_PREFETCH_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 8);
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testCacheEvent() {
+        String eventName = "event";
+        int eventValue = 77;
+        mUmaSpy.cacheEvent(eventName, eventValue);
+
+        Set<String> cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.contains("event:77:1"));
+        assertEquals(1, cachedUmaEntries.size());
+
+        mUmaSpy.cacheEvent(eventName, eventValue);
+        mUmaSpy.cacheEvent(eventName, eventValue);
+
+        cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.contains("event:77:3"));
+        assertEquals(1, cachedUmaEntries.size());
+
+        int eventValue2 = 50;
+        mUmaSpy.cacheEvent(eventName, eventValue2);
+
+        cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.contains("event:77:3"));
+        assertTrue(cachedUmaEntries.contains("event:50:1"));
+        assertEquals(2, cachedUmaEntries.size());
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testFlushStats() {
+        doNothing().when(mUmaSpy).recordEnumeratedHistogram(anyString(), anyInt(), anyInt());
+
+        BackgroundTaskSchedulerUma.getInstance().flushStats();
+        verify(mUmaSpy, times(0)).recordEnumeratedHistogram(anyString(), anyInt(), anyInt());
+
+        String eventName = "event";
+        int eventValue = 77;
+        int eventValue2 = 50;
+        mUmaSpy.cacheEvent(eventName, eventValue);
+        mUmaSpy.cacheEvent(eventName, eventValue);
+        mUmaSpy.cacheEvent(eventName, eventValue);
+        mUmaSpy.cacheEvent(eventName, eventValue2);
+
+        BackgroundTaskSchedulerUma.getInstance().flushStats();
+
+        verify(mUmaSpy, times(3))
+                .recordEnumeratedHistogram(eq(eventName), eq(eventValue),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT));
+        verify(mUmaSpy, times(1))
+                .recordEnumeratedHistogram(eq(eventName), eq(eventValue2),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT));
+        Set<String> cachedUmaEntries = BackgroundTaskSchedulerUma.getCachedUmaEntries(
+                ContextUtils.getAppSharedPreferences());
+        assertTrue(cachedUmaEntries.isEmpty());
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testReportTaskScheduledSuccess() {
+        doNothing().when(mUmaSpy).cacheEvent(anyString(), anyInt());
+        BackgroundTaskSchedulerUma.getInstance().reportTaskScheduled(TaskIds.TEST, true);
+        verify(mUmaSpy, times(1))
+                .cacheEvent(eq("Android.BackgroundTaskScheduler.TaskScheduled.Success"),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_TEST));
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testReportTaskScheduledFailure() {
+        doNothing().when(mUmaSpy).cacheEvent(anyString(), anyInt());
+        BackgroundTaskSchedulerUma.getInstance().reportTaskScheduled(
+                TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID, false);
+        verify(mUmaSpy, times(1))
+                .cacheEvent(eq("Android.BackgroundTaskScheduler.TaskScheduled.Failure"),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OFFLINE_PAGES));
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testReportTaskCanceled() {
+        doNothing().when(mUmaSpy).cacheEvent(anyString(), anyInt());
+        BackgroundTaskSchedulerUma.getInstance().reportTaskCanceled(
+                TaskIds.OFFLINE_PAGES_PREFETCH_JOB_ID);
+        verify(mUmaSpy, times(1))
+                .cacheEvent(eq("Android.BackgroundTaskScheduler.TaskCanceled"),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OFFLINE_PREFETCH));
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testReportTaskStarted() {
+        doNothing().when(mUmaSpy).cacheEvent(anyString(), anyInt());
+        BackgroundTaskSchedulerUma.getInstance().reportTaskStarted(TaskIds.OMAHA_JOB_ID);
+        verify(mUmaSpy, times(1))
+                .cacheEvent(eq("Android.BackgroundTaskScheduler.TaskStarted"),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_OMAHA));
+    }
+
+    @Test
+    @Feature({"BackgroundTaskScheduler"})
+    public void testReportTaskStopped() {
+        doNothing().when(mUmaSpy).cacheEvent(anyString(), anyInt());
+        BackgroundTaskSchedulerUma.getInstance().reportTaskStopped(
+                TaskIds.GCM_BACKGROUND_TASK_JOB_ID);
+        verify(mUmaSpy, times(1))
+                .cacheEvent(eq("Android.BackgroundTaskScheduler.TaskStopped"),
+                        eq(BackgroundTaskSchedulerUma.BACKGROUND_TASK_GCM));
+    }
+}
diff --git a/components/feature_engagement_tracker/DEPS b/components/feature_engagement_tracker/DEPS
index 925a7c7..48edfa45a 100644
--- a/components/feature_engagement_tracker/DEPS
+++ b/components/feature_engagement_tracker/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "-content",
   "+jni",
+  "+components/flags_ui",
   "+components/keyed_service",
   "+components/leveldb_proto",
 ]
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
index 3403c93..9d92884 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/metrics/user_metrics.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/feature_engagement_tracker/internal/availability_model_impl.h"
@@ -40,6 +41,11 @@
 // Creates a FeatureEngagementTrackerImpl that is usable for a demo mode.
 std::unique_ptr<FeatureEngagementTracker>
 CreateDemoModeFeatureEngagementTracker() {
+  // GetFieldTrialParamValueByFeature returns an empty string if the param is
+  // not set.
+  std::string chosen_feature_name = base::GetFieldTrialParamValueByFeature(
+      kIPHDemoMode, kIPHDemoModeFeatureChoiceParam);
+
   std::unique_ptr<EditableConfiguration> configuration =
       base::MakeUnique<EditableConfiguration>();
 
@@ -47,8 +53,14 @@
   // OnceConditionValidator acknowledges that thet meet conditions once.
   std::vector<const base::Feature*> features = GetAllFeatures();
   for (auto* feature : features) {
+    // If a particular feature has been chosen to use with demo mode, only
+    // mark that feature with a valid configuration.
+    bool valid_config = chosen_feature_name.empty()
+                            ? true
+                            : chosen_feature_name == feature->name;
+
     FeatureConfig feature_config;
-    feature_config.valid = true;
+    feature_config.valid = valid_config;
     feature_config.trigger.name = feature->name + std::string("_trigger");
     configuration->SetConfiguration(feature, feature_config);
   }
diff --git a/components/feature_engagement_tracker/public/BUILD.gn b/components/feature_engagement_tracker/public/BUILD.gn
index 3be36c7..2bbd588 100644
--- a/components/feature_engagement_tracker/public/BUILD.gn
+++ b/components/feature_engagement_tracker/public/BUILD.gn
@@ -18,6 +18,7 @@
 
   deps = [
     "//base",
+    "//components/flags_ui",
     "//components/keyed_service/core",
   ]
 
diff --git a/components/feature_engagement_tracker/public/feature_list.cc b/components/feature_engagement_tracker/public/feature_list.cc
index 6d600c4..73f043c 100644
--- a/components/feature_engagement_tracker/public/feature_list.cc
+++ b/components/feature_engagement_tracker/public/feature_list.cc
@@ -6,16 +6,55 @@
 
 #include "base/feature_list.h"
 #include "components/feature_engagement_tracker/public/feature_constants.h"
+#include "components/flags_ui/feature_entry.h"
 
 namespace feature_engagement_tracker {
 
 namespace {
 
+// Defines a const flags_ui::FeatureEntry::FeatureParam for the given
+// base::Feature. The constant name will be on the form
+// kFooFeature --> kFooFeatureVariation. This is intended to be used with
+// VARIATION_ENTRY below to be able to insert it into an array of
+// flags_ui::FeatureEntry::FeatureVariation.
+#define DEFINE_VARIATION_PARAM(base_feature)                               \
+  const flags_ui::FeatureEntry::FeatureParam base_feature##Variation[] = { \
+      {kIPHDemoModeFeatureChoiceParam, base_feature.name}}
+
+// Defines a single flags_ui::FeatureEntry::FeatureVariation entry, fully
+// enclosed. This is intended to be used with the declaration of
+// |kIPHDemoModeChoiceVariations| below.
+#define VARIATION_ENTRY(base_feature)                                \
+  {                                                                  \
+    base_feature##Variation[0].param_value, base_feature##Variation, \
+        arraysize(base_feature##Variation), nullptr                  \
+  }
+
+// Whenever a feature is added to |kAllFeatures|, it should also be added as
+// DEFINE_VARIATION_PARAM below, and also added to the
+// |kIPHDemoModeChoiceVariations| array.
 const base::Feature* kAllFeatures[] = {
     &kIPHDataSaverPreview, &kIPHDownloadPageFeature, &kIPHDownloadHomeFeature};
 
+// Defines a flags_ui::FeatureEntry::FeatureParam for each feature.
+DEFINE_VARIATION_PARAM(kIPHDataSaverPreview);
+DEFINE_VARIATION_PARAM(kIPHDownloadPageFeature);
+DEFINE_VARIATION_PARAM(kIPHDownloadHomeFeature);
+
 }  // namespace
 
+const char kIPHDemoModeFeatureChoiceParam[] = "chosen_feature";
+
+const flags_ui::FeatureEntry::FeatureVariation kIPHDemoModeChoiceVariations[] =
+    {
+        VARIATION_ENTRY(kIPHDataSaverPreview),
+        VARIATION_ENTRY(kIPHDownloadPageFeature),
+        VARIATION_ENTRY(kIPHDownloadHomeFeature),
+        // Note: When changing the number of entries in this array, the constant
+        // kIPHDemoModeChoiceVariationsLen in feature_list.h must also be
+        // updated to reflect the real size.
+};
+
 std::vector<const base::Feature*> GetAllFeatures() {
   return std::vector<const base::Feature*>(
       kAllFeatures, kAllFeatures + arraysize(kAllFeatures));
diff --git a/components/feature_engagement_tracker/public/feature_list.h b/components/feature_engagement_tracker/public/feature_list.h
index ba4a578..0cb72bd2 100644
--- a/components/feature_engagement_tracker/public/feature_list.h
+++ b/components/feature_engagement_tracker/public/feature_list.h
@@ -8,10 +8,27 @@
 #include <vector>
 
 #include "base/feature_list.h"
+#include "components/flags_ui/feature_entry.h"
 
 namespace feature_engagement_tracker {
 using FeatureVector = std::vector<const base::Feature*>;
 
+// The param name for the FeatureVariation configuration, which is used by
+// chrome://flags to set the variable name for the selected feature. The
+// FeatureEngagementTracker backend will then read this to figure out which
+// feature (if any) was selected by the end user.
+extern const char kIPHDemoModeFeatureChoiceParam[];
+
+// The length of kIPHDemoModeChoiceVariations. This must be updated whenever
+// new features are added to the demo mode selection.
+constexpr size_t kIPHDemoModeChoiceVariationsLen = 3;
+
+// Defines the array of which features should be listed in the chrome://flags
+// UI to be able to select them alone for demo-mode. The features listed here
+// are possible to enable on their own in demo mode.
+extern const flags_ui::FeatureEntry::FeatureVariation
+    kIPHDemoModeChoiceVariations[kIPHDemoModeChoiceVariationsLen];
+
 // Returns all the features that are in use for engagement tracking.
 FeatureVector GetAllFeatures();
 
diff --git a/components/history_strings.grdp b/components/history_strings.grdp
index f7da1b48..6864dc5 100644
--- a/components/history_strings.grdp
+++ b/components/history_strings.grdp
@@ -45,7 +45,7 @@
     More from this site
   </message>
   <message name="IDS_HISTORY_NO_RESULTS" desc="Text shown when no history entries are found.">
-    No history entries found
+    Your browsing history appears here
   </message>
   <message name="IDS_HISTORY_NO_SEARCH_RESULTS" desc="Text shown when no history search results have been found">
     No search results found
diff --git a/components/nacl/loader/nacl_helper_linux.cc b/components/nacl/loader/nacl_helper_linux.cc
index fbce5f9..9de0eb40 100644
--- a/components/nacl/loader/nacl_helper_linux.cc
+++ b/components/nacl/loader/nacl_helper_linux.cc
@@ -39,7 +39,6 @@
 #include "content/public/common/mojo_channel_switches.h"
 #include "content/public/common/send_zygote_child_ping_linux.h"
 #include "content/public/common/zygote_fork_delegate_linux.h"
-#include "ipc/ipc_descriptors.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "sandbox/linux/services/credentials.h"
 #include "sandbox/linux/services/namespace_sandbox.h"
diff --git a/components/omnibox/browser/clipboard_url_provider.cc b/components/omnibox/browser/clipboard_url_provider.cc
index 3fe1074..f2f12d3 100644
--- a/components/omnibox/browser/clipboard_url_provider.cc
+++ b/components/omnibox/browser/clipboard_url_provider.cc
@@ -4,6 +4,8 @@
 
 #include "components/omnibox/browser/clipboard_url_provider.h"
 
+#include <algorithm>
+
 #include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
@@ -24,7 +26,8 @@
     : AutocompleteProvider(AutocompleteProvider::TYPE_CLIPBOARD_URL),
       client_(client),
       clipboard_content_(clipboard_content),
-      history_url_provider_(history_url_provider) {
+      history_url_provider_(history_url_provider),
+      current_url_suggested_times_(0) {
   DCHECK(clipboard_content_);
 }
 
@@ -39,10 +42,15 @@
     return;
 
   GURL url;
-  // If the clipboard does not contain any URL, or the URL on the page is the
-  // same as the URL in the clipboard, early return.
-  if (!clipboard_content_->GetRecentURLFromClipboard(&url) ||
-      url == input.current_url())
+  // The clipboard does not contain a URL worth suggesting.
+  if (!clipboard_content_->GetRecentURLFromClipboard(&url)) {
+    current_url_suggested_ = GURL();
+    current_url_suggested_times_ = 0;
+    return;
+  }
+  // The URL on the page is the same as the URL in the clipboard.  Don't
+  // bother suggesting it.
+  if (url == input.current_url())
     return;
 
   DCHECK(url.is_valid());
@@ -62,6 +70,18 @@
                         !matches_.empty());
   UMA_HISTOGRAM_LONG_TIMES_100("Omnibox.ClipboardSuggestionShownAge",
                                clipboard_content_->GetClipboardContentAge());
+  // Record the number of times the currently-offered URL has been suggested.
+  // This only works over this run of Chrome; if the URL was in the clipboard
+  // on a previous run, those offerings will not be counted.
+  if (url == current_url_suggested_) {
+    current_url_suggested_times_++;
+  } else {
+    current_url_suggested_ = url;
+    current_url_suggested_times_ = 1;
+  }
+  UMA_HISTOGRAM_SPARSE_SLOWLY(
+      "Omnibox.ClipboardSuggestionShownNumTimes",
+      std::min(current_url_suggested_times_, static_cast<size_t>(20)));
 
   // Add the clipboard match. The relevance is 800 to beat ZeroSuggest results.
   AutocompleteMatch match(this, 800, false, AutocompleteMatchType::CLIPBOARD);
@@ -80,3 +100,17 @@
 
   matches_.push_back(match);
 }
+
+void ClipboardURLProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
+  // If a URL wasn't suggested on this most recent focus event, don't bother
+  // setting |times_returned_results_in_session|, as in effect this URL has
+  // never been suggested during the current session.  (For the purpose of
+  // this provider, we define a session as intervals between when a URL
+  // clipboard suggestion changes.)
+  if (current_url_suggested_times_ == 0)
+    return;
+  provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
+  metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
+  new_entry.set_provider(AsOmniboxEventProviderType());
+  new_entry.set_times_returned_results_in_session(current_url_suggested_times_);
+}
diff --git a/components/omnibox/browser/clipboard_url_provider.h b/components/omnibox/browser/clipboard_url_provider.h
index ac2762ac..0b54d3e4 100644
--- a/components/omnibox/browser/clipboard_url_provider.h
+++ b/components/omnibox/browser/clipboard_url_provider.h
@@ -22,6 +22,7 @@
 
   // AutocompleteProvider implementation.
   void Start(const AutocompleteInput& input, bool minimal_changes) override;
+  void AddProviderInfo(ProvidersInfo* provider_info) const override;
 
  private:
   ~ClipboardURLProvider() override;
@@ -32,6 +33,11 @@
   // Used for efficiency when creating the verbatim match.  Can be NULL.
   HistoryURLProvider* history_url_provider_;
 
+  // The current URL suggested and the number of times it has been offered.
+  // Used for recording metrics.
+  GURL current_url_suggested_;
+  size_t current_url_suggested_times_;
+
   DISALLOW_COPY_AND_ASSIGN(ClipboardURLProvider);
 };
 
diff --git a/content/app/android/child_process_service_impl.cc b/content/app/android/child_process_service_impl.cc
index 5f5fca29..08be4443 100644
--- a/content/app/android/child_process_service_impl.cc
+++ b/content/app/android/child_process_service_impl.cc
@@ -23,7 +23,6 @@
 #include "content/public/common/content_switches.h"
 #include "gpu/ipc/common/android/scoped_surface_request_conduit.h"
 #include "gpu/ipc/common/gpu_surface_lookup.h"
-#include "ipc/ipc_descriptors.h"
 #include "jni/ChildProcessServiceImpl_jni.h"
 #include "services/service_manager/embedder/shared_file_util.h"
 #include "services/service_manager/embedder/switches.h"
diff --git a/content/app/content_main_runner.cc b/content/app/content_main_runner.cc
index a677c9d..7bdb162 100644
--- a/content/app/content_main_runner.cc
+++ b/content/app/content_main_runner.cc
@@ -54,7 +54,6 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
 #include "content/public/common/sandbox_init.h"
-#include "ipc/ipc_descriptors.h"
 #include "media/base/media.h"
 #include "ppapi/features/features.h"
 #include "services/service_manager/embedder/switches.h"
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 3458260a..42c7e04 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -1262,6 +1262,19 @@
   // BrowserContext.
   DCHECK_EQ(new_instance->GetBrowserContext(), browser_context);
 
+  // If |new_instance| is a new SiteInstance for a subframe with an isolated
+  // origin, set its process reuse policy so that such subframes are
+  // consolidated into existing processes for that isolated origin.
+  SiteInstanceImpl* new_instance_impl =
+      static_cast<SiteInstanceImpl*>(new_instance.get());
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  if (!frame_tree_node_->IsMainFrame() && !new_instance_impl->HasProcess() &&
+      new_instance_impl->HasSite() &&
+      policy->IsIsolatedOrigin(url::Origin(new_instance_impl->GetSiteURL()))) {
+    new_instance_impl->set_process_reuse_policy(
+        SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE);
+  }
+
   return new_instance;
 }
 
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 95dab99..d44a1ba 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -255,6 +255,88 @@
   EXPECT_FALSE(child->current_frame_host()->IsCrossProcessSubframe());
 }
 
+// Check that a new isolated origin subframe will attempt to reuse an existing
+// process for that isolated origin, even across BrowsingInstances.  Also check
+// that main frame navigations to an isolated origin keep using the default
+// process model and do not reuse existing processes.
+IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, SubframeReusesExistingProcess) {
+  GURL top_url(
+      embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), top_url));
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  FrameTreeNode* child = root->child_at(0);
+
+  // Open an unrelated tab in a separate BrowsingInstance, and navigate it to
+  // to an isolated origin.  This SiteInstance should have a default process
+  // reuse policy - only subframes attempt process reuse.
+  GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com",
+                                                   "/page_with_iframe.html"));
+  Shell* second_shell = CreateBrowser();
+  EXPECT_TRUE(NavigateToURL(second_shell, isolated_url));
+  scoped_refptr<SiteInstanceImpl> second_shell_instance =
+      static_cast<SiteInstanceImpl*>(
+          second_shell->web_contents()->GetMainFrame()->GetSiteInstance());
+  EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance(
+      root->current_frame_host()->GetSiteInstance()));
+  RenderProcessHost* isolated_process = second_shell_instance->GetProcess();
+  EXPECT_EQ(SiteInstanceImpl::ProcessReusePolicy::DEFAULT,
+            second_shell_instance->process_reuse_policy());
+
+  // Now navigate the first tab's subframe to an isolated origin.  See that it
+  // reuses the existing |isolated_process|.
+  NavigateIframeToURL(web_contents(), "test_iframe", isolated_url);
+  EXPECT_EQ(isolated_url, child->current_url());
+  EXPECT_EQ(isolated_process, child->current_frame_host()->GetProcess());
+  EXPECT_EQ(
+      SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE,
+      child->current_frame_host()->GetSiteInstance()->process_reuse_policy());
+
+  EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe());
+  EXPECT_EQ(isolated_url.GetOrigin(),
+            child->current_frame_host()->GetSiteInstance()->GetSiteURL());
+
+  // The subframe's SiteInstance should still be different from second_shell's
+  // SiteInstance, and they should be in separate BrowsingInstances.
+  EXPECT_NE(second_shell_instance,
+            child->current_frame_host()->GetSiteInstance());
+  EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance(
+      child->current_frame_host()->GetSiteInstance()));
+
+  // Navigate the second tab to a normal URL with a same-site subframe.  This
+  // leaves only the first tab's subframe in the isolated origin process.
+  EXPECT_TRUE(NavigateToURL(second_shell, top_url));
+  EXPECT_NE(isolated_process,
+            second_shell->web_contents()->GetMainFrame()->GetProcess());
+
+  // Navigate the second tab's subframe to an isolated origin, and check that
+  // this new subframe reuses the isolated process of the subframe in the first
+  // tab, even though the two are in separate BrowsingInstances.
+  NavigateIframeToURL(second_shell->web_contents(), "test_iframe",
+                      isolated_url);
+  FrameTreeNode* second_subframe =
+      static_cast<WebContentsImpl*>(second_shell->web_contents())
+          ->GetFrameTree()
+          ->root()
+          ->child_at(0);
+  EXPECT_EQ(isolated_process,
+            second_subframe->current_frame_host()->GetProcess());
+  EXPECT_NE(child->current_frame_host()->GetSiteInstance(),
+            second_subframe->current_frame_host()->GetSiteInstance());
+
+  // Open a third, unrelated tab, navigate it to an isolated origin, and check
+  // that its main frame doesn't share a process with the existing isolated
+  // subframes.
+  Shell* third_shell = CreateBrowser();
+  EXPECT_TRUE(NavigateToURL(third_shell, isolated_url));
+  SiteInstanceImpl* third_shell_instance = static_cast<SiteInstanceImpl*>(
+      third_shell->web_contents()->GetMainFrame()->GetSiteInstance());
+  EXPECT_NE(third_shell_instance,
+            second_subframe->current_frame_host()->GetSiteInstance());
+  EXPECT_NE(third_shell_instance,
+            child->current_frame_host()->GetSiteInstance());
+  EXPECT_NE(third_shell_instance->GetProcess(), isolated_process);
+}
+
 // Check that isolated origins can access cookies.  This requires cookie checks
 // on the IO thread to be aware of isolated origins.
 IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, Cookies) {
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index 8eb3248c..3828cf0 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -31,6 +31,7 @@
 #include "content/public/browser/web_ui.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/media_log_event.h"
+#include "media/base/watch_time_keys.h"
 #include "media/filters/gpu_video_decoder.h"
 
 #if !defined(OS_ANDROID)
@@ -463,20 +464,20 @@
 
 MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler(
     content::MediaInternals* media_internals)
-    : watch_time_keys_(media::MediaLog::GetWatchTimeKeys()),
-      watch_time_power_keys_(media::MediaLog::GetWatchTimePowerKeys()),
-      mtbr_keys_({{media::MediaLog::kWatchTimeAudioSrc,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioSrc},
-                  {media::MediaLog::kWatchTimeAudioMse,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioMse},
-                  {media::MediaLog::kWatchTimeAudioEme,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioEme},
-                  {media::MediaLog::kWatchTimeAudioVideoSrc,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioVideoSrc},
-                  {media::MediaLog::kWatchTimeAudioVideoMse,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioVideoMse},
-                  {media::MediaLog::kWatchTimeAudioVideoEme,
-                   media::MediaLog::kMeanTimeBetweenRebuffersAudioVideoEme}},
+    : watch_time_keys_(media::GetWatchTimeKeys()),
+      watch_time_power_keys_(media::GetWatchTimePowerKeys()),
+      mtbr_keys_({{media::kWatchTimeAudioSrc,
+                   media::kMeanTimeBetweenRebuffersAudioSrc},
+                  {media::kWatchTimeAudioMse,
+                   media::kMeanTimeBetweenRebuffersAudioMse},
+                  {media::kWatchTimeAudioEme,
+                   media::kMeanTimeBetweenRebuffersAudioEme},
+                  {media::kWatchTimeAudioVideoSrc,
+                   media::kMeanTimeBetweenRebuffersAudioVideoSrc},
+                  {media::kWatchTimeAudioVideoMse,
+                   media::kMeanTimeBetweenRebuffersAudioVideoMse},
+                  {media::kWatchTimeAudioVideoEme,
+                   media::kMeanTimeBetweenRebuffersAudioVideoEme}},
                  base::KEEP_FIRST_OF_DUPES),
       media_internals_(media_internals) {}
 
@@ -574,24 +575,23 @@
             base::TimeDelta::FromSecondsD(it.value().GetDouble());
       }
 
-      if (event.params.HasKey(media::MediaLog::kUnderflowCount)) {
-        event.params.GetInteger(media::MediaLog::kUnderflowCount,
+      if (event.params.HasKey(media::kWatchTimeUnderflowCount)) {
+        event.params.GetInteger(media::kWatchTimeUnderflowCount,
                                 &player_info.underflow_count);
       }
 
-      if (event.params.HasKey(media::MediaLog::kWatchTimeFinalize)) {
+      if (event.params.HasKey(media::kWatchTimeFinalize)) {
         bool should_finalize;
-        DCHECK(event.params.GetBoolean(media::MediaLog::kWatchTimeFinalize,
+        DCHECK(event.params.GetBoolean(media::kWatchTimeFinalize,
                                        &should_finalize) &&
                should_finalize);
         FinalizeWatchTime(player_info.has_video, player_info.origin_url,
                           &player_info.underflow_count,
                           &player_info.watch_time_info,
                           FinalizeType::EVERYTHING);
-      } else if (event.params.HasKey(
-                     media::MediaLog::kWatchTimeFinalizePower)) {
+      } else if (event.params.HasKey(media::kWatchTimeFinalizePower)) {
         bool should_finalize;
-        DCHECK(event.params.GetBoolean(media::MediaLog::kWatchTimeFinalizePower,
+        DCHECK(event.params.GetBoolean(media::kWatchTimeFinalizePower,
                                        &should_finalize) &&
                should_finalize);
         FinalizeWatchTime(player_info.has_video, player_info.origin_url,
diff --git a/content/browser/media/media_internals_unittest.cc b/content/browser/media/media_internals_unittest.cc
index d6ed6be..1773565 100644
--- a/content/browser/media/media_internals_unittest.cc
+++ b/content/browser/media/media_internals_unittest.cc
@@ -21,6 +21,7 @@
 #include "media/base/audio_parameters.h"
 #include "media/base/channel_layout.h"
 #include "media/base/media_log.h"
+#include "media/base/watch_time_keys.h"
 #include "media/blink/watch_time_reporter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -344,8 +345,8 @@
         media_log_(new DirectMediaLog(render_process_id_)),
         histogram_tester_(new base::HistogramTester()),
         test_recorder_(new ukm::TestUkmRecorder()),
-        watch_time_keys_(media::MediaLog::GetWatchTimeKeys()),
-        watch_time_power_keys_(media::MediaLog::GetWatchTimePowerKeys()) {
+        watch_time_keys_(media::GetWatchTimeKeys()),
+        watch_time_power_keys_(media::GetWatchTimePowerKeys()) {
     media_log_->AddEvent(media_log_->CreateCreatedEvent(kTestOrigin));
   }
 
@@ -438,13 +439,12 @@
   CycleWatchTimeReporter();
   wtr_.reset();
 
-  ExpectWatchTime(
-      {media::MediaLog::kWatchTimeAudioAll, media::MediaLog::kWatchTimeAudioMse,
-       media::MediaLog::kWatchTimeAudioEme, media::MediaLog::kWatchTimeAudioAc,
-       media::MediaLog::kWatchTimeAudioEmbeddedExperience},
-      kWatchTimeLate);
-  ExpectMtbrTime({media::MediaLog::kMeanTimeBetweenRebuffersAudioMse,
-                  media::MediaLog::kMeanTimeBetweenRebuffersAudioEme},
+  ExpectWatchTime({media::kWatchTimeAudioAll, media::kWatchTimeAudioMse,
+                   media::kWatchTimeAudioEme, media::kWatchTimeAudioAc,
+                   media::kWatchTimeAudioEmbeddedExperience},
+                  kWatchTimeLate);
+  ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioMse,
+                  media::kMeanTimeBetweenRebuffersAudioEme},
                  kWatchTimeLate / 2);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
@@ -472,14 +472,13 @@
   CycleWatchTimeReporter();
   wtr_.reset();
 
-  ExpectWatchTime({media::MediaLog::kWatchTimeAudioVideoAll,
-                   media::MediaLog::kWatchTimeAudioVideoSrc,
-                   media::MediaLog::kWatchTimeAudioVideoEme,
-                   media::MediaLog::kWatchTimeAudioVideoAc,
-                   media::MediaLog::kWatchTimeAudioVideoEmbeddedExperience},
-                  kWatchTimeLate);
-  ExpectMtbrTime({media::MediaLog::kMeanTimeBetweenRebuffersAudioVideoSrc,
-                  media::MediaLog::kMeanTimeBetweenRebuffersAudioVideoEme},
+  ExpectWatchTime(
+      {media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
+       media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
+       media::kWatchTimeAudioVideoEmbeddedExperience},
+      kWatchTimeLate);
+  ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioVideoSrc,
+                  media::kMeanTimeBetweenRebuffersAudioVideoEme},
                  kWatchTimeLate / 2);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
@@ -514,18 +513,17 @@
   CycleWatchTimeReporter();
 
   // This should finalize the power watch time on battery.
-  ExpectWatchTime({media::MediaLog::kWatchTimeAudioVideoBattery}, kWatchTime2);
+  ExpectWatchTime({media::kWatchTimeAudioVideoBattery}, kWatchTime2);
   ResetHistogramTester();
   wtr_.reset();
 
   std::vector<base::StringPiece> normal_keys = {
-      media::MediaLog::kWatchTimeAudioVideoAll,
-      media::MediaLog::kWatchTimeAudioVideoSrc,
-      media::MediaLog::kWatchTimeAudioVideoEme,
-      media::MediaLog::kWatchTimeAudioVideoEmbeddedExperience};
+      media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
+      media::kWatchTimeAudioVideoEme,
+      media::kWatchTimeAudioVideoEmbeddedExperience};
 
   for (auto key : watch_time_keys_) {
-    if (key == media::MediaLog::kWatchTimeAudioVideoAc) {
+    if (key == media::kWatchTimeAudioVideoAc) {
       histogram_tester_->ExpectUniqueSample(
           key.as_string(), (kWatchTime3 - kWatchTime2).InMilliseconds(), 1);
       continue;
@@ -576,13 +574,12 @@
   CycleWatchTimeReporter();
   wtr_.reset();
 
-  ExpectWatchTime(
-      {media::MediaLog::kWatchTimeAudioVideoBackgroundAll,
-       media::MediaLog::kWatchTimeAudioVideoBackgroundSrc,
-       media::MediaLog::kWatchTimeAudioVideoBackgroundEme,
-       media::MediaLog::kWatchTimeAudioVideoBackgroundAc,
-       media::MediaLog::kWatchTimeAudioVideoBackgroundEmbeddedExperience},
-      kWatchTimeLate);
+  ExpectWatchTime({media::kWatchTimeAudioVideoBackgroundAll,
+                   media::kWatchTimeAudioVideoBackgroundSrc,
+                   media::kWatchTimeAudioVideoBackgroundEme,
+                   media::kWatchTimeAudioVideoBackgroundAc,
+                   media::kWatchTimeAudioVideoBackgroundEmbeddedExperience},
+                  kWatchTimeLate);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
   ExpectUkmWatchTime(0, 4, kWatchTimeLate);
@@ -609,12 +606,11 @@
   media_log_->AddEvent(
       media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
 
-  ExpectWatchTime({media::MediaLog::kWatchTimeAudioVideoAll,
-                   media::MediaLog::kWatchTimeAudioVideoSrc,
-                   media::MediaLog::kWatchTimeAudioVideoEme,
-                   media::MediaLog::kWatchTimeAudioVideoAc,
-                   media::MediaLog::kWatchTimeAudioVideoEmbeddedExperience},
-                  kWatchTimeLate);
+  ExpectWatchTime(
+      {media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
+       media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
+       media::kWatchTimeAudioVideoEmbeddedExperience},
+      kWatchTimeLate);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
   ExpectUkmWatchTime(0, 4, kWatchTimeLate);
@@ -641,12 +637,11 @@
   // Also verify that if UKM has already been destructed, we don't crash.
   test_recorder_.reset();
   internals_->OnProcessTerminatedForTesting(render_process_id_);
-  ExpectWatchTime({media::MediaLog::kWatchTimeAudioVideoAll,
-                   media::MediaLog::kWatchTimeAudioVideoSrc,
-                   media::MediaLog::kWatchTimeAudioVideoEme,
-                   media::MediaLog::kWatchTimeAudioVideoAc,
-                   media::MediaLog::kWatchTimeAudioVideoEmbeddedExperience},
-                  kWatchTimeLate);
+  ExpectWatchTime(
+      {media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
+       media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
+       media::kWatchTimeAudioVideoEmbeddedExperience},
+      kWatchTimeLate);
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
index d167547..1155857 100644
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
@@ -72,6 +72,7 @@
     MediaStreamType stream_type,
     const media::VideoCaptureParams& params,
     base::WeakPtr<media::VideoFrameReceiver> receiver,
+    base::OnceClosure /* connection_lost_cb */,
     Callbacks* callbacks,
     base::OnceClosure done_cb) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.h b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.h
index ad5166f1..8b810b1 100644
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.h
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.h
@@ -31,6 +31,7 @@
                          MediaStreamType stream_type,
                          const media::VideoCaptureParams& params,
                          base::WeakPtr<media::VideoFrameReceiver> receiver,
+                         base::OnceClosure connection_lost_cb,
                          Callbacks* callbacks,
                          base::OnceClosure done_cb) override;
 
diff --git a/content/browser/renderer_host/media/mock_video_capture_provider.h b/content/browser/renderer_host/media/mock_video_capture_provider.h
index 7c17595..afb659c 100644
--- a/content/browser/renderer_host/media/mock_video_capture_provider.h
+++ b/content/browser/renderer_host/media/mock_video_capture_provider.h
@@ -30,11 +30,12 @@
   MockVideoCaptureDeviceLauncher();
   ~MockVideoCaptureDeviceLauncher() override;
 
-  MOCK_METHOD6(DoLaunchDeviceAsync,
+  MOCK_METHOD7(DoLaunchDeviceAsync,
                void(const std::string& device_id,
                     MediaStreamType stream_type,
                     const media::VideoCaptureParams& params,
                     base::WeakPtr<media::VideoFrameReceiver>* receiver,
+                    base::OnceClosure* connection_lost_cb,
                     Callbacks* callbacks,
                     base::OnceClosure* done_cb));
 
@@ -44,10 +45,11 @@
                          MediaStreamType stream_type,
                          const media::VideoCaptureParams& params,
                          base::WeakPtr<media::VideoFrameReceiver> receiver,
+                         base::OnceClosure connection_lost_cb,
                          Callbacks* callbacks,
                          base::OnceClosure done_cb) override {
-    DoLaunchDeviceAsync(device_id, stream_type, params, &receiver, callbacks,
-                        &done_cb);
+    DoLaunchDeviceAsync(device_id, stream_type, params, &receiver,
+                        &connection_lost_cb, callbacks, &done_cb);
   }
 };
 
diff --git a/content/browser/renderer_host/media/service_launched_video_capture_device.cc b/content/browser/renderer_host/media/service_launched_video_capture_device.cc
index af54de3..71b8d14 100644
--- a/content/browser/renderer_host/media/service_launched_video_capture_device.cc
+++ b/content/browser/renderer_host/media/service_launched_video_capture_device.cc
@@ -7,8 +7,10 @@
 namespace content {
 
 ServiceLaunchedVideoCaptureDevice::ServiceLaunchedVideoCaptureDevice(
-    video_capture::mojom::DevicePtr device)
-    : device_(std::move(device)) {
+    video_capture::mojom::DevicePtr device,
+    base::OnceClosure connection_lost_cb)
+    : device_(std::move(device)),
+      connection_lost_cb_(std::move(connection_lost_cb)) {
   // Unretained |this| is safe, because |this| owns |device_|.
   device_.set_connection_error_handler(
       base::Bind(&ServiceLaunchedVideoCaptureDevice::OnLostConnectionToDevice,
@@ -71,7 +73,7 @@
 
 void ServiceLaunchedVideoCaptureDevice::OnLostConnectionToDevice() {
   DCHECK(sequence_checker_.CalledOnValidSequence());
-  NOTIMPLEMENTED();
+  base::ResetAndReturn(&connection_lost_cb_).Run();
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/media/service_launched_video_capture_device.h b/content/browser/renderer_host/media/service_launched_video_capture_device.h
index 35acd7b..c01194f2 100644
--- a/content/browser/renderer_host/media/service_launched_video_capture_device.h
+++ b/content/browser/renderer_host/media/service_launched_video_capture_device.h
@@ -14,8 +14,8 @@
 // service.
 class ServiceLaunchedVideoCaptureDevice : public LaunchedVideoCaptureDevice {
  public:
-  explicit ServiceLaunchedVideoCaptureDevice(
-      video_capture::mojom::DevicePtr device);
+  ServiceLaunchedVideoCaptureDevice(video_capture::mojom::DevicePtr device,
+                                    base::OnceClosure connection_lost_cb);
   ~ServiceLaunchedVideoCaptureDevice() override;
 
   // LaunchedVideoCaptureDevice implementation.
@@ -40,6 +40,7 @@
   void OnLostConnectionToDevice();
 
   video_capture::mojom::DevicePtr device_;
+  base::OnceClosure connection_lost_cb_;
   base::SequenceChecker sequence_checker_;
 };
 
diff --git a/content/browser/renderer_host/media/service_video_capture_device_launcher.cc b/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
index 522570a..cbd9f49 100644
--- a/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/service_video_capture_device_launcher.cc
@@ -19,6 +19,7 @@
     const media::VideoCaptureParams& params,
     video_capture::mojom::DevicePtr device,
     base::WeakPtr<media::VideoFrameReceiver> receiver,
+    base::OnceClosure connection_lost_cb,
     VideoCaptureDeviceLauncher::Callbacks* callbacks,
     base::OnceClosure done_cb) {
   if (abort_requested) {
@@ -38,7 +39,8 @@
       std::move(receiver_adapter), mojo::MakeRequest(&receiver_proxy));
   device->Start(params, std::move(receiver_proxy));
   callbacks->OnDeviceLaunched(
-      base::MakeUnique<ServiceLaunchedVideoCaptureDevice>(std::move(device)));
+      base::MakeUnique<ServiceLaunchedVideoCaptureDevice>(
+          std::move(device), std::move(connection_lost_cb)));
   base::ResetAndReturn(&done_cb).Run();
 }
 
@@ -71,6 +73,7 @@
     MediaStreamType stream_type,
     const media::VideoCaptureParams& params,
     base::WeakPtr<media::VideoFrameReceiver> receiver,
+    base::OnceClosure connection_lost_cb,
     Callbacks* callbacks,
     base::OnceClosure done_cb) {
   DCHECK(sequence_checker_.CalledOnValidSequence());
@@ -110,7 +113,7 @@
               // that |this| stays alive.
               &ServiceVideoCaptureDeviceLauncher::OnCreateDeviceCallback,
               base::Unretained(this), params, base::Passed(&device),
-              std::move(receiver)));
+              std::move(receiver), base::Passed(&connection_lost_cb)));
   state_ = State::DEVICE_START_IN_PROGRESS;
 }
 
@@ -124,6 +127,7 @@
     const media::VideoCaptureParams& params,
     video_capture::mojom::DevicePtr device,
     base::WeakPtr<media::VideoFrameReceiver> receiver,
+    base::OnceClosure connection_lost_cb,
     video_capture::mojom::DeviceAccessResultCode result_code) {
   DCHECK(sequence_checker_.CalledOnValidSequence());
   DCHECK(callbacks_);
@@ -135,9 +139,9 @@
   callbacks_ = nullptr;
   switch (result_code) {
     case video_capture::mojom::DeviceAccessResultCode::SUCCESS:
-      ConcludeLaunchDeviceWithSuccess(abort_requested, params,
-                                      std::move(device), std::move(receiver),
-                                      callbacks, std::move(done_cb_));
+      ConcludeLaunchDeviceWithSuccess(
+          abort_requested, params, std::move(device), std::move(receiver),
+          std::move(connection_lost_cb), callbacks, std::move(done_cb_));
       return;
     case video_capture::mojom::DeviceAccessResultCode::ERROR_DEVICE_NOT_FOUND:
     case video_capture::mojom::DeviceAccessResultCode::NOT_INITIALIZED:
diff --git a/content/browser/renderer_host/media/service_video_capture_device_launcher.h b/content/browser/renderer_host/media/service_video_capture_device_launcher.h
index 30fcf31..446d206c 100644
--- a/content/browser/renderer_host/media/service_video_capture_device_launcher.h
+++ b/content/browser/renderer_host/media/service_video_capture_device_launcher.h
@@ -25,6 +25,7 @@
                          MediaStreamType stream_type,
                          const media::VideoCaptureParams& params,
                          base::WeakPtr<media::VideoFrameReceiver> receiver,
+                         base::OnceClosure connection_lost_cb,
                          Callbacks* callbacks,
                          base::OnceClosure done_cb) override;
   void AbortLaunch() override;
@@ -42,6 +43,7 @@
       const media::VideoCaptureParams& params,
       video_capture::mojom::DevicePtr device,
       base::WeakPtr<media::VideoFrameReceiver> receiver,
+      base::OnceClosure connection_lost_cb,
       video_capture::mojom::DeviceAccessResultCode result_code);
 
   void OnConnectionLostWhileWaitingForCallback();
diff --git a/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc b/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
index 65dd139b..866eb75d 100644
--- a/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
+++ b/content/browser/renderer_host/media/service_video_capture_device_launcher_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread.h"
+#include "content/browser/renderer_host/media/service_launched_video_capture_device.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/video_capture/public/interfaces/device_factory.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -78,7 +79,8 @@
   std::unique_ptr<mojo::Binding<video_capture::mojom::DeviceFactory>>
       factory_binding_;
   std::unique_ptr<ServiceVideoCaptureDeviceLauncher> launcher_;
-  base::MockCallback<base::OnceClosure> done_cb;
+  base::MockCallback<base::OnceClosure> connection_lost_cb_;
+  base::MockCallback<base::OnceClosure> done_cb_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ServiceVideoCaptureDeviceLauncherTest);
@@ -109,14 +111,16 @@
   EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(1);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed()).Times(0);
-  EXPECT_CALL(done_cb, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
+  EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
     run_loop.Quit();
   }));
 
   // Exercise
   launcher_->LaunchDeviceAsync(
       kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
-      kNullReceiver, &mock_callbacks_, done_cb.Get());
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
 
   run_loop.Run();
 }
@@ -165,14 +169,16 @@
   EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(1);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed()).Times(0);
-  EXPECT_CALL(done_cb, Run()).WillOnce(InvokeWithoutArgs([&step_2_run_loop]() {
+  EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&step_2_run_loop]() {
     step_2_run_loop.Quit();
   }));
 
   // Exercise
   launcher_->LaunchDeviceAsync(
       kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
-      kNullReceiver, &mock_callbacks_, done_cb.Get());
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
   step_1_run_loop.Run();
   launcher_->AbortLaunch();
 
@@ -208,14 +214,16 @@
   EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed()).Times(1);
-  EXPECT_CALL(done_cb, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
+  EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
     run_loop.Quit();
   }));
 
   // Exercise
   launcher_->LaunchDeviceAsync(
       kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
-      kNullReceiver, &mock_callbacks_, done_cb.Get());
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
 
   run_loop.Run();
 }
@@ -227,7 +235,8 @@
   EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed()).Times(1);
-  EXPECT_CALL(done_cb, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
+  EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
     run_loop.Quit();
   }));
 
@@ -235,7 +244,8 @@
   device_factory_.reset();
   launcher_->LaunchDeviceAsync(
       kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
-      kNullReceiver, &mock_callbacks_, done_cb.Get());
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
 
   run_loop.Run();
 }
@@ -260,14 +270,18 @@
   EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_)).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchAborted()).Times(0);
   EXPECT_CALL(mock_callbacks_, OnDeviceLaunchFailed()).Times(1);
-  EXPECT_CALL(done_cb, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
+  // Note: |connection_lost_cb_| is only meant to be called when the connection
+  // to a successfully-launched device is lost, which is not the case here.
+  EXPECT_CALL(connection_lost_cb_, Run()).Times(0);
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&run_loop]() {
     run_loop.Quit();
   }));
 
   // Exercise
   launcher_->LaunchDeviceAsync(
       kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
-      kNullReceiver, &mock_callbacks_, done_cb.Get());
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
 
   run_loop.Run();
 
@@ -280,4 +294,54 @@
   create_device_cb.Run(arbitrary_result_code);
 }
 
+TEST_F(ServiceVideoCaptureDeviceLauncherTest,
+       ConnectionLostAfterSuccessfulLaunch) {
+  video_capture::mojom::DeviceRequest device_request_owned_by_service;
+  EXPECT_CALL(mock_device_factory_, DoCreateDevice(kStubDeviceId, _, _))
+      .WillOnce(Invoke([&device_request_owned_by_service](
+                           const std::string& device_id,
+                           video_capture::mojom::DeviceRequest* device_request,
+                           const video_capture::mojom::DeviceFactory::
+                               CreateDeviceCallback& callback) {
+        // The service holds on to the |device_request|.
+        device_request_owned_by_service = std::move(*device_request);
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE,
+            base::Bind(
+                [](const video_capture::mojom::DeviceFactory::
+                       CreateDeviceCallback& callback) {
+                  callback.Run(
+                      video_capture::mojom::DeviceAccessResultCode::SUCCESS);
+                },
+                callback));
+      }));
+  std::unique_ptr<LaunchedVideoCaptureDevice> launched_device;
+  EXPECT_CALL(mock_callbacks_, DoOnDeviceLaunched(_))
+      .WillOnce(
+          Invoke([&launched_device](
+                     std::unique_ptr<LaunchedVideoCaptureDevice>* device) {
+            // We must keep the launched device alive, because otherwise it will
+            // no longer listen for connection errors.
+            launched_device = std::move(*device);
+          }));
+  base::RunLoop step_1_run_loop;
+  EXPECT_CALL(done_cb_, Run()).WillOnce(InvokeWithoutArgs([&step_1_run_loop]() {
+    step_1_run_loop.Quit();
+  }));
+  // Exercise step 1
+  launcher_->LaunchDeviceAsync(
+      kStubDeviceId, content::MEDIA_DEVICE_VIDEO_CAPTURE, kArbitraryParams,
+      kNullReceiver, connection_lost_cb_.Get(), &mock_callbacks_,
+      done_cb_.Get());
+  step_1_run_loop.Run();
+
+  base::RunLoop step_2_run_loop;
+  EXPECT_CALL(connection_lost_cb_, Run()).WillOnce(Invoke([&step_2_run_loop]() {
+    step_2_run_loop.Quit();
+  }));
+  // Exercise step 2: The service cuts/loses the connection
+  device_request_owned_by_service = nullptr;
+  step_2_run_loop.Run();
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_controller.cc b/content/browser/renderer_host/media/video_capture_controller.cc
index d67f4ecf..fa67f31 100644
--- a/content/browser/renderer_host/media/video_capture_controller.cc
+++ b/content/browser/renderer_host/media/video_capture_controller.cc
@@ -498,7 +498,6 @@
     entry.set_consumer_feedback_observer(launched_device_.get());
   if (device_launch_observer_) {
     device_launch_observer_->OnDeviceLaunched(this);
-    device_launch_observer_ = nullptr;
   }
 }
 
@@ -518,15 +517,25 @@
   }
 }
 
+void VideoCaptureController::OnDeviceConnectionLost() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (device_launch_observer_) {
+    device_launch_observer_->OnDeviceConnectionLost(this);
+    device_launch_observer_ = nullptr;
+  }
+}
+
 void VideoCaptureController::CreateAndStartDeviceAsync(
     const media::VideoCaptureParams& params,
     VideoCaptureDeviceLaunchObserver* observer,
     base::OnceClosure done_cb) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   device_launch_observer_ = observer;
-  device_launcher_->LaunchDeviceAsync(device_id_, stream_type_, params,
-                                      GetWeakPtrForIOThread(), this,
-                                      std::move(done_cb));
+  device_launcher_->LaunchDeviceAsync(
+      device_id_, stream_type_, params, GetWeakPtrForIOThread(),
+      base::Bind(&VideoCaptureController::OnDeviceConnectionLost,
+                 GetWeakPtrForIOThread()),
+      this, std::move(done_cb));
 }
 
 void VideoCaptureController::ReleaseDeviceAsync(base::OnceClosure done_cb) {
diff --git a/content/browser/renderer_host/media/video_capture_controller.h b/content/browser/renderer_host/media/video_capture_controller.h
index e1224f2..04c5540 100644
--- a/content/browser/renderer_host/media/video_capture_controller.h
+++ b/content/browser/renderer_host/media/video_capture_controller.h
@@ -120,6 +120,8 @@
   void OnDeviceLaunchFailed() override;
   void OnDeviceLaunchAborted() override;
 
+  void OnDeviceConnectionLost();
+
   void CreateAndStartDeviceAsync(const media::VideoCaptureParams& params,
                                  VideoCaptureDeviceLaunchObserver* callbacks,
                                  base::OnceClosure done_cb);
diff --git a/content/browser/renderer_host/media/video_capture_device_launch_observer.h b/content/browser/renderer_host/media/video_capture_device_launch_observer.h
index 49812ba..33e4b1b 100644
--- a/content/browser/renderer_host/media/video_capture_device_launch_observer.h
+++ b/content/browser/renderer_host/media/video_capture_device_launch_observer.h
@@ -17,6 +17,7 @@
   virtual void OnDeviceLaunched(VideoCaptureController* controller) = 0;
   virtual void OnDeviceLaunchFailed(VideoCaptureController* controller) = 0;
   virtual void OnDeviceLaunchAborted() = 0;
+  virtual void OnDeviceConnectionLost(VideoCaptureController* controller) = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_manager.cc b/content/browser/renderer_host/media/video_capture_manager.cc
index bc60336..cc170527 100644
--- a/content/browser/renderer_host/media/video_capture_manager.cc
+++ b/content/browser/renderer_host/media/video_capture_manager.cc
@@ -355,6 +355,15 @@
   ProcessDeviceStartRequestQueue();
 }
 
+void VideoCaptureManager::OnDeviceConnectionLost(
+    VideoCaptureController* controller) {
+  const std::string log_message = base::StringPrintf(
+      "Lost connection to device %d.", controller->serial_id());
+  DLOG(WARNING) << log_message;
+  controller->OnLog(log_message);
+  controller->OnError();
+}
+
 void VideoCaptureManager::ConnectClient(
     media::VideoCaptureSessionId session_id,
     const media::VideoCaptureParams& params,
@@ -430,6 +439,10 @@
 
   // If controller has no more clients, delete controller and device.
   DestroyControllerIfNoClients(controller);
+
+  if (controllers_.empty()) {
+    video_capture_provider_->Uninitialize();
+  }
 }
 
 void VideoCaptureManager::PauseCaptureForClient(
diff --git a/content/browser/renderer_host/media/video_capture_manager.h b/content/browser/renderer_host/media/video_capture_manager.h
index be614cfeb..de07dac 100644
--- a/content/browser/renderer_host/media/video_capture_manager.h
+++ b/content/browser/renderer_host/media/video_capture_manager.h
@@ -183,6 +183,7 @@
   void OnDeviceLaunched(VideoCaptureController* controller) override;
   void OnDeviceLaunchFailed(VideoCaptureController* controller) override;
   void OnDeviceLaunchAborted() override;
+  void OnDeviceConnectionLost(VideoCaptureController* controller) override;
 
   // Retrieves camera calibration information for a particular device. Returns
   // nullopt_t if the |device_id| is not found or camera calibration information
diff --git a/content/browser/renderer_host/media/video_capture_provider.h b/content/browser/renderer_host/media/video_capture_provider.h
index 10135040..81fc0f1 100644
--- a/content/browser/renderer_host/media/video_capture_provider.h
+++ b/content/browser/renderer_host/media/video_capture_provider.h
@@ -41,6 +41,7 @@
       MediaStreamType stream_type,
       const media::VideoCaptureParams& params,
       base::WeakPtr<media::VideoFrameReceiver> receiver,
+      base::OnceClosure connection_lost_cb,
       Callbacks* callbacks,
       base::OnceClosure done_cb) = 0;
 
diff --git a/content/browser/renderer_host/media/video_capture_provider_switcher.cc b/content/browser/renderer_host/media/video_capture_provider_switcher.cc
index eaeb799..3cc09af 100644
--- a/content/browser/renderer_host/media/video_capture_provider_switcher.cc
+++ b/content/browser/renderer_host/media/video_capture_provider_switcher.cc
@@ -24,6 +24,7 @@
                          MediaStreamType stream_type,
                          const media::VideoCaptureParams& params,
                          base::WeakPtr<media::VideoFrameReceiver> receiver,
+                         base::OnceClosure connection_lost_cb,
                          Callbacks* callbacks,
                          base::OnceClosure done_cb) override {
     if (stream_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
@@ -33,8 +34,8 @@
           base::Bind(&VideoCaptureDeviceLauncher::AbortLaunch,
                      base::Unretained(media_device_launcher_.get()));
       return media_device_launcher_->LaunchDeviceAsync(
-          device_id, stream_type, params, std::move(receiver), callbacks,
-          std::move(done_cb));
+          device_id, stream_type, params, std::move(receiver),
+          std::move(connection_lost_cb), callbacks, std::move(done_cb));
     }
     // Use of Unretained() is safe, because |other_types_launcher_| is owned by
     // |this|.
@@ -42,8 +43,8 @@
         base::Bind(&VideoCaptureDeviceLauncher::AbortLaunch,
                    base::Unretained(other_types_launcher_.get()));
     return other_types_launcher_->LaunchDeviceAsync(
-        device_id, stream_type, params, std::move(receiver), callbacks,
-        std::move(done_cb));
+        device_id, stream_type, params, std::move(receiver),
+        std::move(connection_lost_cb), callbacks, std::move(done_cb));
   }
 
   void AbortLaunch() override {
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 1b77386..dd6b845 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -593,6 +593,10 @@
                              cc::CompositorFrame frame) override;
   void DidNotProduceFrame(const cc::BeginFrameAck& ack) override;
 
+  // Signals that a frame with token |frame_token| was finished processing. If
+  // there are any queued messages belonging to it, they will be processed.
+  void DidProcessFrame(uint32_t frame_token);
+
  protected:
   // ---------------------------------------------------------------------------
   // The following method is overridden by RenderViewHost to send upwards to
@@ -754,10 +758,6 @@
   // Used for UMA logging how long the renderer was unresponsive.
   void LogHangMonitorUnresponsive();
 
-  // Signals that a frame with token |frame_token| was finished processing. If
-  // there are any queued messages belonging to it, they will be processed.
-  void DidProcessFrame(uint32_t frame_token);
-
   // Once both the frame and its swap messages arrive, we call this method to
   // process the messages. Virtual for tests.
   virtual void ProcessSwapMessages(std::vector<IPC::Message> messages);
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index df540b7..af376b9 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1281,6 +1281,9 @@
         is_mobile_optimized);
   }
 
+  if (host_ && frame_metadata.frame_token)
+    host_->DidProcessFrame(frame_metadata.frame_token);
+
   // This is a subset of OnSwapCompositorFrame() used in the synchronous
   // compositor flow.
   OnFrameMetadataUpdated(frame_metadata.Clone(), false);
diff --git a/content/browser/webrtc/webrtc_image_capture_browsertest.cc b/content/browser/webrtc/webrtc_image_capture_browsertest.cc
index d5641934..ca3751d 100644
--- a/content/browser/webrtc/webrtc_image_capture_browsertest.cc
+++ b/content/browser/webrtc/webrtc_image_capture_browsertest.cc
@@ -39,9 +39,10 @@
 // platforms where the ImageCaptureCode is landed, https://crbug.com/656810
 static struct TargetCamera {
   bool use_fake;
-} const kTestParameters[] = {{true},
-#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_ANDROID)
-                             {false}
+} const kTestParameters[] = {
+    {true},
+#if defined(OS_LINUX)
+    {false}
 #endif
 };
 
diff --git a/content/common/zygote_commands_linux.h b/content/common/zygote_commands_linux.h
index c67593d..24f3bf6 100644
--- a/content/common/zygote_commands_linux.h
+++ b/content/common/zygote_commands_linux.h
@@ -8,7 +8,6 @@
 #include <stddef.h>
 
 #include "base/posix/global_descriptors.h"
-#include "ipc/ipc_descriptors.h"
 
 namespace content {
 
diff --git a/content/public/common/content_descriptors.h b/content/public/common/content_descriptors.h
index 0e7b01c..35bf7ab 100644
--- a/content/public/common/content_descriptors.h
+++ b/content/public/common/content_descriptors.h
@@ -6,12 +6,11 @@
 #define CONTENT_PUBLIC_COMMON_CONTENT_DESCRIPTORS_H_
 
 #include "build/build_config.h"
-#include "ipc/ipc_descriptors.h"
 
 // This is a list of global descriptor keys to be used with the
 // base::GlobalDescriptors object (see base/posix/global_descriptors.h)
 enum {
-  kCrashDumpSignal = kIPCDescriptorMax,
+  kCrashDumpSignal = 0,
   kSandboxIPCChannel,  // https://chromium.googlesource.com/chromium/src/+/master/docs/linux_sandbox_ipc.md
   kMojoIPCChannel,
   kFieldTrialDescriptor,
diff --git a/content/renderer/android/synchronous_compositor_frame_sink.cc b/content/renderer/android/synchronous_compositor_frame_sink.cc
index 51e5c73..a5d7fb8 100644
--- a/content/renderer/android/synchronous_compositor_frame_sink.cc
+++ b/content/renderer/android/synchronous_compositor_frame_sink.cc
@@ -323,7 +323,6 @@
 
   sync_client_->SubmitCompositorFrame(compositor_frame_sink_id_,
                                       std::move(submit_frame));
-  DeliverMessages();
   did_submit_frame_ = true;
 }
 
@@ -343,12 +342,14 @@
   DCHECK(CalledOnValidThread());
   TRACE_EVENT0("renderer", "SynchronousCompositorFrameSink::FallbackTickFired");
   base::AutoReset<bool> in_fallback_tick(&fallback_tick_running_, true);
+  frame_swap_message_queue_->NotifyFramesAreDiscarded(true);
   SkBitmap bitmap;
   bitmap.allocN32Pixels(1, 1);
   bitmap.eraseColor(0);
   SkCanvas canvas(bitmap);
   fallback_tick_pending_ = false;
   DemandDrawSw(&canvas);
+  frame_swap_message_queue_->NotifyFramesAreDiscarded(false);
 }
 
 void SynchronousCompositorFrameSink::Invalidate() {
diff --git a/content/renderer/gpu/frame_swap_message_queue.cc b/content/renderer/gpu/frame_swap_message_queue.cc
index cf3107b8..3f43b95d 100644
--- a/content/renderer/gpu/frame_swap_message_queue.cc
+++ b/content/renderer/gpu/frame_swap_message_queue.cc
@@ -112,9 +112,11 @@
 
 }  // namespace
 
-FrameSwapMessageQueue::FrameSwapMessageQueue()
+FrameSwapMessageQueue::FrameSwapMessageQueue(int32_t routing_id)
     : visual_state_queue_(new VisualStateQueue()),
-      swap_queue_(new SwapQueue()) {
+      swap_queue_(new SwapQueue()),
+      routing_id_(routing_id) {
+  DETACH_FROM_THREAD(impl_thread_checker_);
 }
 
 FrameSwapMessageQueue::~FrameSwapMessageQueue() {
@@ -210,4 +212,15 @@
   return ++last_used_frame_token_;
 }
 
+void FrameSwapMessageQueue::NotifyFramesAreDiscarded(
+    bool frames_are_discarded) {
+  DCHECK_CALLED_ON_VALID_THREAD(impl_thread_checker_);
+  frames_are_discarded_ = frames_are_discarded;
+}
+
+bool FrameSwapMessageQueue::AreFramesDiscarded() {
+  DCHECK_CALLED_ON_VALID_THREAD(impl_thread_checker_);
+  return frames_are_discarded_;
+}
+
 }  // namespace content
diff --git a/content/renderer/gpu/frame_swap_message_queue.h b/content/renderer/gpu/frame_swap_message_queue.h
index 0b9e1c1..1dccf1d 100644
--- a/content/renderer/gpu/frame_swap_message_queue.h
+++ b/content/renderer/gpu/frame_swap_message_queue.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
 #include "cc/output/swap_promise.h"
 #include "content/common/content_export.h"
 #include "content/renderer/message_delivery_policy.h"
@@ -37,7 +38,7 @@
     virtual ~SendMessageScope() {}
   };
 
-  FrameSwapMessageQueue();
+  explicit FrameSwapMessageQueue(int32_t routing_id);
 
   // Queues message to be returned on a matching DrainMessages call.
   //
@@ -98,6 +99,11 @@
 
   uint32_t AllocateFrameToken();
 
+  int32_t routing_id() const { return routing_id_; }
+
+  void NotifyFramesAreDiscarded(bool frames_are_discarded);
+  bool AreFramesDiscarded();
+
  private:
   friend class base::RefCountedThreadSafe<FrameSwapMessageQueue>;
 
@@ -110,6 +116,9 @@
   std::unique_ptr<FrameSwapMessageSubQueue> swap_queue_;
   std::vector<std::unique_ptr<IPC::Message>> next_drain_messages_;
   uint32_t last_used_frame_token_ = 0;
+  int32_t routing_id_ = 0;
+  bool frames_are_discarded_ = false;
+  THREAD_CHECKER(impl_thread_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(FrameSwapMessageQueue);
 };
diff --git a/content/renderer/gpu/frame_swap_message_queue_unittest.cc b/content/renderer/gpu/frame_swap_message_queue_unittest.cc
index 54e4f7d..75d0e423 100644
--- a/content/renderer/gpu/frame_swap_message_queue_unittest.cc
+++ b/content/renderer/gpu/frame_swap_message_queue_unittest.cc
@@ -18,7 +18,7 @@
       : first_message_(41, 1, IPC::Message::PRIORITY_NORMAL),
         second_message_(42, 2, IPC::Message::PRIORITY_NORMAL),
         third_message_(43, 3, IPC::Message::PRIORITY_NORMAL),
-        queue_(new FrameSwapMessageQueue()) {}
+        queue_(new FrameSwapMessageQueue(0)) {}
 
  protected:
   void QueueNextSwapMessage(std::unique_ptr<IPC::Message> msg) {
diff --git a/content/renderer/gpu/queue_message_swap_promise.cc b/content/renderer/gpu/queue_message_swap_promise.cc
index 22745027..ad4b91e 100644
--- a/content/renderer/gpu/queue_message_swap_promise.cc
+++ b/content/renderer/gpu/queue_message_swap_promise.cc
@@ -5,6 +5,7 @@
 #include "content/renderer/gpu/queue_message_swap_promise.h"
 
 #include "base/command_line.h"
+#include "content/common/view_messages.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/gpu/frame_swap_message_queue.h"
@@ -48,7 +49,22 @@
   DCHECK(!completed_);
 #endif
   message_queue_->DidSwap(source_frame_number_);
-  // The OutputSurface will take care of the Drain+Send.
+
+  if (!message_queue_->AreFramesDiscarded()) {
+    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
+        send_message_scope = message_queue_->AcquireSendMessageScope();
+    std::vector<std::unique_ptr<IPC::Message>> messages;
+    message_queue_->DrainMessages(&messages);
+    std::vector<IPC::Message> messages_to_send;
+    FrameSwapMessageQueue::TransferMessages(&messages, &messages_to_send);
+    if (!messages_to_send.empty()) {
+      metadata->frame_token = message_queue_->AllocateFrameToken();
+      message_sender_->Send(new ViewHostMsg_FrameSwapMessages(
+          message_queue_->routing_id(), metadata->frame_token,
+          messages_to_send));
+    }
+  }
+
   PromiseCompleted();
 }
 
diff --git a/content/renderer/gpu/queue_message_swap_promise_unittest.cc b/content/renderer/gpu/queue_message_swap_promise_unittest.cc
index 18d3b2c..a02cc38 100644
--- a/content/renderer/gpu/queue_message_swap_promise_unittest.cc
+++ b/content/renderer/gpu/queue_message_swap_promise_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "cc/output/swap_promise.h"
+#include "content/common/view_messages.h"
 #include "content/renderer/gpu/frame_swap_message_queue.h"
 #include "content/renderer/gpu/render_widget_compositor.h"
 #include "content/renderer/render_widget.h"
@@ -39,16 +40,34 @@
   TestSyncMessageFilter() : IPC::SyncMessageFilter(nullptr) {}
 
   bool Send(IPC::Message* message) override {
-    messages_.push_back(base::WrapUnique(message));
+    if (message->type() == ViewHostMsg_FrameSwapMessages::ID) {
+      ViewHostMsg_FrameSwapMessages::Param param;
+      ViewHostMsg_FrameSwapMessages::Read(message, &param);
+      std::vector<IPC::Message> messages = std::get<1>(param);
+      last_swap_messages_.clear();
+      for (const IPC::Message& message : messages) {
+        last_swap_messages_.push_back(base::MakeUnique<IPC::Message>(message));
+      }
+      delete message;
+    } else {
+      direct_send_messages_.push_back(base::WrapUnique(message));
+    }
     return true;
   }
 
-  std::vector<std::unique_ptr<IPC::Message>>& messages() { return messages_; }
+  std::vector<std::unique_ptr<IPC::Message>>& last_swap_messages() {
+    return last_swap_messages_;
+  }
+
+  const std::vector<std::unique_ptr<IPC::Message>>& direct_send_messages() {
+    return direct_send_messages_;
+  }
 
  private:
   ~TestSyncMessageFilter() override {}
 
-  std::vector<std::unique_ptr<IPC::Message>> messages_;
+  std::vector<std::unique_ptr<IPC::Message>> direct_send_messages_;
+  std::vector<std::unique_ptr<IPC::Message>> last_swap_messages_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSyncMessageFilter);
 };
@@ -61,7 +80,7 @@
 class QueueMessageSwapPromiseTest : public testing::Test {
  public:
   QueueMessageSwapPromiseTest()
-      : frame_swap_message_queue_(new FrameSwapMessageQueue()),
+      : frame_swap_message_queue_(new FrameSwapMessageQueue(0)),
         sync_message_filter_(new TestSyncMessageFilter()) {}
 
   ~QueueMessageSwapPromiseTest() override {}
@@ -76,7 +95,11 @@
   }
 
   const std::vector<std::unique_ptr<IPC::Message>>& DirectSendMessages() {
-    return sync_message_filter_->messages();
+    return sync_message_filter_->direct_send_messages();
+  }
+
+  std::vector<std::unique_ptr<IPC::Message>>& LastSwapMessages() {
+    return sync_message_filter_->last_swap_messages();
   }
 
   std::vector<std::unique_ptr<IPC::Message>>& NextSwapMessages() {
@@ -100,6 +123,10 @@
     return false;
   }
 
+  bool LastSwapHasMessage(const IPC::Message& message) {
+    return ContainsMessage(LastSwapMessages(), message);
+  }
+
   bool NextSwapHasMessage(const IPC::Message& message) {
     return ContainsMessage(NextSwapMessages(), message);
   }
@@ -118,7 +145,7 @@
     for (const auto& promise : promises_) {
       if (promise.get()) {
         promise->DidActivate();
-        promise->WillSwap(NULL);
+        promise->WillSwap(&dummy_metadata_);
         promise->DidSwap();
       }
     }
@@ -133,6 +160,7 @@
   scoped_refptr<TestSyncMessageFilter> sync_message_filter_;
   std::vector<IPC::Message> messages_;
   std::vector<std::unique_ptr<cc::SwapPromise>> promises_;
+  cc::CompositorFrameMetadata dummy_metadata_;
 
  private:
   std::vector<std::unique_ptr<IPC::Message>> next_swap_messages_;
@@ -149,13 +177,12 @@
 
   ASSERT_TRUE(promises_[0].get());
   promises_[0]->DidActivate();
-  promises_[0]->WillSwap(NULL);
+  promises_[0]->WillSwap(&dummy_metadata_);
   promises_[0]->DidSwap();
 
   EXPECT_TRUE(DirectSendMessages().empty());
-  EXPECT_FALSE(frame_swap_message_queue_->Empty());
-  // frame_swap_message_queue_->WillSwap(1);
-  EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
+  EXPECT_TRUE(frame_swap_message_queue_->Empty());
+  EXPECT_TRUE(LastSwapHasMessage(messages_[0]));
 }
 
 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyNeedsAtMostOnePromise) {
@@ -181,7 +208,7 @@
 
   promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
   EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
-  EXPECT_TRUE(NextSwapMessages().empty());
+  EXPECT_TRUE(LastSwapMessages().empty());
   EXPECT_TRUE(frame_swap_message_queue_->Empty());
 }
 
@@ -194,7 +221,7 @@
 
   promises_[0]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
   EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
-  EXPECT_TRUE(NextSwapMessages().empty());
+  EXPECT_TRUE(LastSwapMessages().empty());
   EXPECT_TRUE(frame_swap_message_queue_->Empty());
 }
 
@@ -207,6 +234,7 @@
 
   promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS);
   EXPECT_TRUE(DirectSendMessages().empty());
+  EXPECT_TRUE(LastSwapMessages().empty());
   EXPECT_FALSE(frame_swap_message_queue_->Empty());
   frame_swap_message_queue_->DidSwap(2);
   EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
@@ -254,11 +282,11 @@
   QueueMessages(data, arraysize(data));
 
   promises_[0]->DidActivate();
-  promises_[0]->WillSwap(NULL);
+  promises_[0]->WillSwap(&dummy_metadata_);
   promises_[0]->DidSwap();
   ASSERT_FALSE(promises_[1].get());
   std::vector<std::unique_ptr<IPC::Message>> messages;
-  messages.swap(NextSwapMessages());
+  messages.swap(LastSwapMessages());
   EXPECT_EQ(2u, messages.size());
   EXPECT_TRUE(ContainsMessage(messages, messages_[0]));
   EXPECT_TRUE(ContainsMessage(messages, messages_[1]));
diff --git a/content/renderer/gpu/renderer_compositor_frame_sink.cc b/content/renderer/gpu/renderer_compositor_frame_sink.cc
index 7c14ba38..90a0846 100644
--- a/content/renderer/gpu/renderer_compositor_frame_sink.cc
+++ b/content/renderer/gpu/renderer_compositor_frame_sink.cc
@@ -16,7 +16,6 @@
 #include "cc/output/managed_memory_policy.h"
 #include "content/common/view_messages.h"
 #include "content/public/common/content_switches.h"
-#include "content/renderer/gpu/frame_swap_message_queue.h"
 #include "content/renderer/render_thread_impl.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
@@ -34,8 +33,7 @@
     gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
     cc::SharedBitmapManager* shared_bitmap_manager,
     cc::mojom::MojoCompositorFrameSinkPtrInfo sink_info,
-    cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request,
-    scoped_refptr<FrameSwapMessageQueue> swap_frame_message_queue)
+    cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request)
     : ClientCompositorFrameSink(std::move(context_provider),
                                 std::move(worker_context_provider),
                                 gpu_memory_buffer_manager,
@@ -47,10 +45,8 @@
       compositor_frame_sink_filter_(
           RenderThreadImpl::current()->compositor_message_filter()),
       message_sender_(RenderThreadImpl::current()->sync_message_filter()),
-      frame_swap_message_queue_(swap_frame_message_queue),
       routing_id_(routing_id) {
   DCHECK(compositor_frame_sink_filter_);
-  DCHECK(frame_swap_message_queue_);
   DCHECK(message_sender_);
 }
 
@@ -59,8 +55,7 @@
     std::unique_ptr<cc::SyntheticBeginFrameSource> synthetic_begin_frame_source,
     scoped_refptr<cc::VulkanContextProvider> vulkan_context_provider,
     cc::mojom::MojoCompositorFrameSinkPtrInfo sink_info,
-    cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request,
-    scoped_refptr<FrameSwapMessageQueue> swap_frame_message_queue)
+    cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request)
     : ClientCompositorFrameSink(std::move(vulkan_context_provider),
                                 std::move(synthetic_begin_frame_source),
                                 std::move(sink_info),
@@ -69,10 +64,8 @@
       compositor_frame_sink_filter_(
           RenderThreadImpl::current()->compositor_message_filter()),
       message_sender_(RenderThreadImpl::current()->sync_message_filter()),
-      frame_swap_message_queue_(swap_frame_message_queue),
       routing_id_(routing_id) {
   DCHECK(compositor_frame_sink_filter_);
-  DCHECK(frame_swap_message_queue_);
   DCHECK(message_sender_);
 }
 
@@ -102,22 +95,6 @@
 
 void RendererCompositorFrameSink::SubmitCompositorFrame(
     cc::CompositorFrame frame) {
-  {
-    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
-        send_message_scope =
-            frame_swap_message_queue_->AcquireSendMessageScope();
-    std::vector<std::unique_ptr<IPC::Message>> messages;
-    frame_swap_message_queue_->DrainMessages(&messages);
-    std::vector<IPC::Message> messages_to_send;
-    FrameSwapMessageQueue::TransferMessages(&messages, &messages_to_send);
-    if (!messages_to_send.empty()) {
-      frame.metadata.frame_token =
-          frame_swap_message_queue_->AllocateFrameToken();
-      message_sender_->Send(new ViewHostMsg_FrameSwapMessages(
-          routing_id_, frame.metadata.frame_token, messages_to_send));
-    }
-    // ~send_message_scope.
-  }
   auto new_surface_properties =
       RenderWidgetSurfaceProperties::FromCompositorFrame(frame);
   ClientCompositorFrameSink::SubmitCompositorFrame(std::move(frame));
diff --git a/content/renderer/gpu/renderer_compositor_frame_sink.h b/content/renderer/gpu/renderer_compositor_frame_sink.h
index 58a20a4..6db24f0b 100644
--- a/content/renderer/gpu/renderer_compositor_frame_sink.h
+++ b/content/renderer/gpu/renderer_compositor_frame_sink.h
@@ -41,7 +41,6 @@
 }
 
 namespace content {
-class FrameSwapMessageQueue;
 
 // This class can be created only on the main thread, but then becomes pinned
 // to a fixed thread when BindToClient is called.
@@ -57,16 +56,14 @@
       gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
       cc::SharedBitmapManager* shared_bitmap_manager,
       cc::mojom::MojoCompositorFrameSinkPtrInfo sink_info,
-      cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request,
-      scoped_refptr<FrameSwapMessageQueue> swap_frame_message_queue);
+      cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request);
   RendererCompositorFrameSink(
       int32_t routing_id,
       std::unique_ptr<cc::SyntheticBeginFrameSource>
           synthetic_begin_frame_source,
       scoped_refptr<cc::VulkanContextProvider> vulkan_context_provider,
       cc::mojom::MojoCompositorFrameSinkPtrInfo sink_info,
-      cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request,
-      scoped_refptr<FrameSwapMessageQueue> swap_frame_message_queue);
+      cc::mojom::MojoCompositorFrameSinkClientRequest sink_client_request);
   ~RendererCompositorFrameSink() override;
 
   // Overriden from viz::ClientCompositorFrameSink.
@@ -105,7 +102,6 @@
       compositor_frame_sink_filter_handler_;
   scoped_refptr<RendererCompositorFrameSinkProxy> compositor_frame_sink_proxy_;
   scoped_refptr<IPC::SyncMessageFilter> message_sender_;
-  scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
   int routing_id_;
 
   RenderWidgetSurfaceProperties current_surface_properties_;
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 604b384b..7d56be9 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1912,7 +1912,7 @@
       callback.Run(base::MakeUnique<RendererCompositorFrameSink>(
           routing_id, std::move(synthetic_begin_frame_source),
           std::move(vulkan_context_provider), std::move(sink_info),
-          std::move(client_request), std::move(frame_swap_message_queue)));
+          std::move(client_request)));
       return;
     }
   }
@@ -1940,7 +1940,7 @@
     callback.Run(base::MakeUnique<RendererCompositorFrameSink>(
         routing_id, std::move(synthetic_begin_frame_source), nullptr, nullptr,
         nullptr, shared_bitmap_manager(), std::move(sink_info),
-        std::move(client_request), std::move(frame_swap_message_queue)));
+        std::move(client_request)));
     return;
   }
 
@@ -2013,7 +2013,7 @@
       routing_id, std::move(synthetic_begin_frame_source),
       std::move(context_provider), std::move(worker_context_provider),
       GetGpuMemoryBufferManager(), nullptr, std::move(sink_info),
-      std::move(client_request), std::move(frame_swap_message_queue))));
+      std::move(client_request))));
 }
 
 AssociatedInterfaceRegistry*
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 40f6f5a..0b676bf3 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -370,7 +370,7 @@
       device_scale_factor_(screen_info_.device_scale_factor),
       monitor_composition_info_(false),
       popup_origin_scale_for_emulation_(0.f),
-      frame_swap_message_queue_(new FrameSwapMessageQueue()),
+      frame_swap_message_queue_(new FrameSwapMessageQueue(routing_id_)),
       resizing_mode_selector_(new ResizingModeSelector()),
       has_host_context_menu_location_(false),
       has_added_input_handler_(false),
diff --git a/content/shell/browser/shell_views.cc b/content/shell/browser/shell_views.cc
index 6bfe64c..b15581e9 100644
--- a/content/shell/browser/shell_views.cc
+++ b/content/shell/browser/shell_views.cc
@@ -113,7 +113,7 @@
  private:
   // Initialize the UI control contained in shell window
   void InitShellWindow() {
-    set_background(views::Background::CreateStandardPanelBackground());
+    SetBackground(views::CreateStandardPanelBackground());
 
     views::GridLayout* layout = new views::GridLayout(this);
     SetLayoutManager(layout);
diff --git a/ios/chrome/browser/web/window_open_by_dom_egtest.mm b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
index 077be28..631be43 100644
--- a/ios/chrome/browser/web/window_open_by_dom_egtest.mm
+++ b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
@@ -200,6 +200,17 @@
       assertWithMatcher:grey_notNil()];
 }
 
+// Tests opening a child window using the following link
+// <a href="data:text/html,<script>window.location='about:newtab';</script>"
+//    target="_blank">
+// TODO(crbug.com/687863): Enable this test.
+- (void)DISABLED_testWindowOpenWithAboutNewTabScript {
+  TapWebViewElementWithId("webScenarioWindowOpenWithAboutNewTabScript");
+  AssertMainTabCount(2);
+  [[EarlGrey selectElementWithMatcher:OmniboxText("about:newtab")]
+      assertWithMatcher:grey_notNil()];
+}
+
 // Tests that closing the current window using DOM fails.
 - (void)testCloseWindowNotOpenByDOM {
   TapWebViewElementWithId("webScenarioWindowClose");
diff --git a/ios/chrome/test/ocmock/BUILD.gn b/ios/chrome/test/ocmock/BUILD.gn
index 1b83041..6c079a2 100644
--- a/ios/chrome/test/ocmock/BUILD.gn
+++ b/ios/chrome/test/ocmock/BUILD.gn
@@ -7,8 +7,6 @@
   sources = [
     "OCMockObject+BreakpadControllerTesting.h",
     "OCMockObject+BreakpadControllerTesting.mm",
-    "scoped_mock_object.h",
-    "scoped_verifying_mock_object.h",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/test/ocmock/scoped_mock_object.h b/ios/chrome/test/ocmock/scoped_mock_object.h
deleted file mode 100644
index f41ad8d..0000000
--- a/ios/chrome/test/ocmock/scoped_mock_object.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_TEST_OCMOCK_SCOPED_MOCK_OBJECT_H_
-#define IOS_CHROME_TEST_OCMOCK_SCOPED_MOCK_OBJECT_H_
-
-#import "base/mac/scoped_nsobject.h"
-#import "third_party/ocmock/OCMock/OCMock.h"
-
-// Helper class that constructs an OCMock and manages ownership of it.
-template <typename NST>
-class ScopedMockObject : public base::scoped_nsobject<id> {
- public:
-  ScopedMockObject()
-      : base::scoped_nsobject<id>(
-            [[OCMockObject mockForClass:[NST class]] retain]) {}
-};
-
-#endif  // IOS_CHROME_TEST_OCMOCK_SCOPED_MOCK_OBJECT_H_
diff --git a/ios/chrome/test/ocmock/scoped_verifying_mock_object.h b/ios/chrome/test/ocmock/scoped_verifying_mock_object.h
deleted file mode 100644
index cd0e0e41..0000000
--- a/ios/chrome/test/ocmock/scoped_verifying_mock_object.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_TEST_OCMOCK_SCOPED_VERIFYING_MOCK_OBJECT_H_
-#define IOS_CHROME_TEST_OCMOCK_SCOPED_VERIFYING_MOCK_OBJECT_H_
-
-#import "ios/chrome/test/ocmock/scoped_mock_object.h"
-#include "third_party/ocmock/gtest_support.h"
-
-// Helper class that constructs an OCMock and automatically verifies the mock
-// upon destruction.
-template <typename NST>
-class ScopedVerifyingMockObject : public ScopedMockObject<NST> {
- public:
-  ScopedVerifyingMockObject() {}
-
-  ~ScopedVerifyingMockObject() {
-    EXPECT_OCMOCK_VERIFY(ScopedMockObject<NST>::get());
-  }
-};
-
-#endif  // IOS_CHROME_TEST_OCMOCK_SCOPED_VERIFYING_MOCK_OBJECT_H_
diff --git a/ios/testing/data/http_server_files/window_open.html b/ios/testing/data/http_server_files/window_open.html
index c604f38..77ccf862 100644
--- a/ios/testing/data/http_server_files/window_open.html
+++ b/ios/testing/data/http_server_files/window_open.html
@@ -128,6 +128,18 @@
     <td>about:blank opened in a new window, with an href and a preventDefault<br></td>
   </tr>
 
+  <tr id="_webScenarioWindowOpenWithAboutNewTabScript">
+    <td>
+      <a href="data:text/html,<script>window.location='about:newtab';</script>"
+         target="_blank"
+         name="webScenarioWindowOpenWithAboutNewTabScript"
+         id="webScenarioWindowOpenWithAboutNewTabScript">
+        webScenarioWindowOpenWithAboutNewTabScript
+      </a>
+    </td>
+    <td>about:blank opened in a new window, using "window.location='about:newtab" script<br></td>
+  </tr>
+
   <tr id="_webScenarioWindowOpenAndSetLocation">
     <td>
       <script>
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 08f22bf..314f5b2 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -302,9 +302,6 @@
   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
   // location changes (client redirects) in practice.
   GURL _lastRegisteredRequestURL;
-  // Last URL change reported to webDidStartLoadingURL. Used to detect page
-  // location changes in practice.
-  GURL _URLOnStartLoading;
   // Page loading phase.
   web::LoadPhase _loadPhase;
   // The web::PageDisplayState recorded when the page starts loading.
@@ -1636,7 +1633,6 @@
           if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
             return;
           base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
-          strongSelf.get()->_URLOnStartLoading = URL;
           strongSelf.get()->_lastRegisteredRequestURL = URL;
         }];
 }
@@ -2742,7 +2738,6 @@
     return NO;
   }
   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
-  _URLOnStartLoading = pushURL;
   _lastRegisteredRequestURL = pushURL;
 
   // If the user interacted with the page, categorize it as a link navigation.
@@ -2809,7 +2804,6 @@
     return NO;
   }
   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
-  _URLOnStartLoading = replaceURL;
   _lastRegisteredRequestURL = replaceURL;
   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
   NSString* replaceStateJS = [self javaScriptToReplaceWebViewURL:replaceURL
@@ -2872,7 +2866,6 @@
 
 - (void)didStartLoadingURL:(const GURL&)URL {
   _loadPhase = web::PAGE_LOADING;
-  _URLOnStartLoading = URL;
   _displayStateOnStartLoading = self.pageDisplayState;
 
   self.userInteractionRegistered = NO;
@@ -4019,8 +4012,6 @@
       [_webView addGestureRecognizer:recognizer];
     }
 
-    _URLOnStartLoading = _defaultURL;
-
     // WKWebViews with invalid or empty frames have exhibited rendering bugs, so
     // resize the view to match the container view upon creation.
     [_webView setFrame:[_containerView bounds]];
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index a3aaaa3..99e93cf 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -40,7 +40,6 @@
     "ipc_channel_proxy.h",
     "ipc_channel_reader.cc",
     "ipc_channel_reader.h",
-    "ipc_descriptors.h",
     "ipc_export.h",
     "ipc_listener.h",
     "ipc_logging.cc",
diff --git a/ipc/ipc_descriptors.h b/ipc/ipc_descriptors.h
deleted file mode 100644
index 7a4a7b19..0000000
--- a/ipc/ipc_descriptors.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2009 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 IPC_IPC_DESCRIPTORS_H_
-#define IPC_IPC_DESCRIPTORS_H_
-
-// This is a list of global descriptor keys to be used with the
-// base::GlobalDescriptors object (see base/posix/global_descriptors.h)
-enum {
-  // The first key that can be use to register descriptors.
-  kIPCDescriptorMax = 0
-};
-
-#endif  // IPC_IPC_DESCRIPTORS_H_
diff --git a/mash/catalog_viewer/catalog_viewer.cc b/mash/catalog_viewer/catalog_viewer.cc
index 75abf03a..c3ca399 100644
--- a/mash/catalog_viewer/catalog_viewer.cc
+++ b/mash/catalog_viewer/catalog_viewer.cc
@@ -51,7 +51,7 @@
         capability_(new views::Textfield) {
     constexpr int kPadding = 5;
     SetBorder(views::CreateEmptyBorder(gfx::Insets(kPadding)));
-    set_background(views::Background::CreateStandardPanelBackground());
+    SetBackground(views::CreateStandardPanelBackground());
 
     views::GridLayout* layout = new views::GridLayout(this);
     SetLayoutManager(layout);
diff --git a/mash/example/window_type_launcher/window_type_launcher.cc b/mash/example/window_type_launcher/window_type_launcher.cc
index 558a42c..021135e4 100644
--- a/mash/example/window_type_launcher/window_type_launcher.cc
+++ b/mash/example/window_type_launcher/window_type_launcher.cc
@@ -59,7 +59,7 @@
   };
 
   explicit WindowDelegateView(uint32_t traits) : traits_(traits) {
-    set_background(views::Background::CreateSolidBackground(SK_ColorRED));
+    SetBackground(views::CreateSolidBackground(SK_ColorRED));
   }
   ~WindowDelegateView() override {}
 
diff --git a/mash/quick_launch/quick_launch.cc b/mash/quick_launch/quick_launch.cc
index cd784e6..9948a895 100644
--- a/mash/quick_launch/quick_launch.cc
+++ b/mash/quick_launch/quick_launch.cc
@@ -42,7 +42,7 @@
         connector_(connector),
         prompt_(new views::Textfield),
         catalog_(std::move(catalog)) {
-    set_background(views::Background::CreateStandardPanelBackground());
+    SetBackground(views::CreateStandardPanelBackground());
     prompt_->set_controller(this);
     AddChildView(prompt_);
 
diff --git a/mash/task_viewer/task_viewer.cc b/mash/task_viewer/task_viewer.cc
index 13e20f6..cdc03e76 100644
--- a/mash/task_viewer/task_viewer.cc
+++ b/mash/task_viewer/task_viewer.cc
@@ -65,7 +65,7 @@
 
     table_view_ = new views::TableView(this, GetColumns(), views::TEXT_ONLY,
                                        false);
-    set_background(views::Background::CreateStandardPanelBackground());
+    SetBackground(views::CreateStandardPanelBackground());
 
     table_view_parent_ = table_view_->CreateParentIfNecessary();
     AddChildView(table_view_parent_);
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index ee1193f..a4c5a309e 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -269,6 +269,8 @@
     "video_util.h",
     "wall_clock_time_source.cc",
     "wall_clock_time_source.h",
+    "watch_time_keys.cc",
+    "watch_time_keys.h",
   ]
 
   allow_circular_includes_from = []
diff --git a/media/base/media_log.cc b/media/base/media_log.cc
index 7e732c3f..abba917 100644
--- a/media/base/media_log.cc
+++ b/media/base/media_log.cc
@@ -17,99 +17,6 @@
 // unique IDs.
 static base::StaticAtomicSequenceNumber g_media_log_count;
 
-// Audio+video watch time metrics.
-const char MediaLog::kWatchTimeAudioVideoAll[] =
-    "Media.WatchTime.AudioVideo.All";
-const char MediaLog::kWatchTimeAudioVideoMse[] =
-    "Media.WatchTime.AudioVideo.MSE";
-const char MediaLog::kWatchTimeAudioVideoEme[] =
-    "Media.WatchTime.AudioVideo.EME";
-const char MediaLog::kWatchTimeAudioVideoSrc[] =
-    "Media.WatchTime.AudioVideo.SRC";
-const char MediaLog::kWatchTimeAudioVideoBattery[] =
-    "Media.WatchTime.AudioVideo.Battery";
-const char MediaLog::kWatchTimeAudioVideoAc[] = "Media.WatchTime.AudioVideo.AC";
-const char MediaLog::kWatchTimeAudioVideoEmbeddedExperience[] =
-    "Media.WatchTime.AudioVideo.EmbeddedExperience";
-
-// Audio only "watch time" metrics.
-const char MediaLog::kWatchTimeAudioAll[] = "Media.WatchTime.Audio.All";
-const char MediaLog::kWatchTimeAudioMse[] = "Media.WatchTime.Audio.MSE";
-const char MediaLog::kWatchTimeAudioEme[] = "Media.WatchTime.Audio.EME";
-const char MediaLog::kWatchTimeAudioSrc[] = "Media.WatchTime.Audio.SRC";
-const char MediaLog::kWatchTimeAudioBattery[] = "Media.WatchTime.Audio.Battery";
-const char MediaLog::kWatchTimeAudioAc[] = "Media.WatchTime.Audio.AC";
-const char MediaLog::kWatchTimeAudioEmbeddedExperience[] =
-    "Media.WatchTime.Audio.EmbeddedExperience";
-
-// Audio+video background watch time metrics.
-const char MediaLog::kWatchTimeAudioVideoBackgroundAll[] =
-    "Media.WatchTime.AudioVideo.Background.All";
-const char MediaLog::kWatchTimeAudioVideoBackgroundMse[] =
-    "Media.WatchTime.AudioVideo.Background.MSE";
-const char MediaLog::kWatchTimeAudioVideoBackgroundEme[] =
-    "Media.WatchTime.AudioVideo.Background.EME";
-const char MediaLog::kWatchTimeAudioVideoBackgroundSrc[] =
-    "Media.WatchTime.AudioVideo.Background.SRC";
-const char MediaLog::kWatchTimeAudioVideoBackgroundBattery[] =
-    "Media.WatchTime.AudioVideo.Background.Battery";
-const char MediaLog::kWatchTimeAudioVideoBackgroundAc[] =
-    "Media.WatchTime.AudioVideo.Background.AC";
-const char MediaLog::kWatchTimeAudioVideoBackgroundEmbeddedExperience[] =
-    "Media.WatchTime.AudioVideo.Background.EmbeddedExperience";
-
-const char MediaLog::kWatchTimeFinalize[] = "FinalizeWatchTime";
-const char MediaLog::kWatchTimeFinalizePower[] = "FinalizePowerWatchTime";
-
-const char MediaLog::kUnderflowCount[] = "UnderflowCount";
-
-const char MediaLog::kMeanTimeBetweenRebuffersAudioSrc[] =
-    "Media.MeanTimeBetweenRebuffers.Audio.SRC";
-const char MediaLog::kMeanTimeBetweenRebuffersAudioMse[] =
-    "Media.MeanTimeBetweenRebuffers.Audio.MSE";
-const char MediaLog::kMeanTimeBetweenRebuffersAudioEme[] =
-    "Media.MeanTimeBetweenRebuffers.Audio.EME";
-const char MediaLog::kMeanTimeBetweenRebuffersAudioVideoSrc[] =
-    "Media.MeanTimeBetweenRebuffers.AudioVideo.SRC";
-const char MediaLog::kMeanTimeBetweenRebuffersAudioVideoMse[] =
-    "Media.MeanTimeBetweenRebuffers.AudioVideo.MSE";
-const char MediaLog::kMeanTimeBetweenRebuffersAudioVideoEme[] =
-    "Media.MeanTimeBetweenRebuffers.AudioVideo.EME";
-
-base::flat_set<base::StringPiece> MediaLog::GetWatchTimeKeys() {
-  return base::flat_set<base::StringPiece>(
-      {kWatchTimeAudioAll,
-       kWatchTimeAudioMse,
-       kWatchTimeAudioEme,
-       kWatchTimeAudioSrc,
-       kWatchTimeAudioBattery,
-       kWatchTimeAudioAc,
-       kWatchTimeAudioEmbeddedExperience,
-       kWatchTimeAudioVideoAll,
-       kWatchTimeAudioVideoMse,
-       kWatchTimeAudioVideoEme,
-       kWatchTimeAudioVideoSrc,
-       kWatchTimeAudioVideoBattery,
-       kWatchTimeAudioVideoAc,
-       kWatchTimeAudioVideoEmbeddedExperience,
-       kWatchTimeAudioVideoBackgroundAll,
-       kWatchTimeAudioVideoBackgroundMse,
-       kWatchTimeAudioVideoBackgroundEme,
-       kWatchTimeAudioVideoBackgroundSrc,
-       kWatchTimeAudioVideoBackgroundBattery,
-       kWatchTimeAudioVideoBackgroundAc,
-       kWatchTimeAudioVideoBackgroundEmbeddedExperience},
-      base::KEEP_FIRST_OF_DUPES);
-}
-
-base::flat_set<base::StringPiece> MediaLog::GetWatchTimePowerKeys() {
-  return base::flat_set<base::StringPiece>(
-      {kWatchTimeAudioBattery, kWatchTimeAudioAc, kWatchTimeAudioVideoBattery,
-       kWatchTimeAudioVideoAc, kWatchTimeAudioVideoBackgroundBattery,
-       kWatchTimeAudioVideoBackgroundAc},
-      base::KEEP_FIRST_OF_DUPES);
-}
-
 std::string MediaLog::MediaLogLevelToString(MediaLogLevel level) {
   switch (level) {
     case MEDIALOG_ERROR:
diff --git a/media/base/media_log.h b/media/base/media_log.h
index 9f508159..9899018 100644
--- a/media/base/media_log.h
+++ b/media/base/media_log.h
@@ -12,7 +12,6 @@
 #include <sstream>
 #include <string>
 
-#include "base/containers/flat_set.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "media/base/buffering_state.h"
@@ -107,49 +106,6 @@
   void SetDoubleProperty(const std::string& key, double value);
   void SetBooleanProperty(const std::string& key, bool value);
 
-  // Histogram names used for reporting; also double as MediaLog key names.
-  // NOTE: If you add to this list you must update GetWatchTimeKeys() and if
-  // necessary, GetWatchTimePowerKeys().
-  static const char kWatchTimeAudioAll[];
-  static const char kWatchTimeAudioMse[];
-  static const char kWatchTimeAudioEme[];
-  static const char kWatchTimeAudioSrc[];
-  static const char kWatchTimeAudioBattery[];
-  static const char kWatchTimeAudioAc[];
-  static const char kWatchTimeAudioEmbeddedExperience[];
-  static const char kWatchTimeAudioVideoAll[];
-  static const char kWatchTimeAudioVideoMse[];
-  static const char kWatchTimeAudioVideoEme[];
-  static const char kWatchTimeAudioVideoSrc[];
-  static const char kWatchTimeAudioVideoBattery[];
-  static const char kWatchTimeAudioVideoAc[];
-  static const char kWatchTimeAudioVideoEmbeddedExperience[];
-  static const char kWatchTimeAudioVideoBackgroundAll[];
-  static const char kWatchTimeAudioVideoBackgroundMse[];
-  static const char kWatchTimeAudioVideoBackgroundEme[];
-  static const char kWatchTimeAudioVideoBackgroundSrc[];
-  static const char kWatchTimeAudioVideoBackgroundBattery[];
-  static const char kWatchTimeAudioVideoBackgroundAc[];
-  static const char kWatchTimeAudioVideoBackgroundEmbeddedExperience[];
-
-  // Markers which signify the watch time should be finalized immediately.
-  static const char kWatchTimeFinalize[];
-  static const char kWatchTimeFinalizePower[];
-
-  // Count of the number of underflow events during a media session.
-  static const char kUnderflowCount[];
-
-  // UMA keys for MTBR samples.
-  static const char kMeanTimeBetweenRebuffersAudioSrc[];
-  static const char kMeanTimeBetweenRebuffersAudioMse[];
-  static const char kMeanTimeBetweenRebuffersAudioEme[];
-  static const char kMeanTimeBetweenRebuffersAudioVideoSrc[];
-  static const char kMeanTimeBetweenRebuffersAudioVideoMse[];
-  static const char kMeanTimeBetweenRebuffersAudioVideoEme[];
-
-  static base::flat_set<base::StringPiece> GetWatchTimeKeys();
-  static base::flat_set<base::StringPiece> GetWatchTimePowerKeys();
-
  private:
   // A unique (to this process) id for this MediaLog.
   int32_t id_;
diff --git a/media/base/watch_time_keys.cc b/media/base/watch_time_keys.cc
new file mode 100644
index 0000000..6ead6c2
--- /dev/null
+++ b/media/base/watch_time_keys.cc
@@ -0,0 +1,97 @@
+// 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/base/watch_time_keys.h"
+
+namespace media {
+
+// Audio+video watch time metrics.
+const char kWatchTimeAudioVideoAll[] = "Media.WatchTime.AudioVideo.All";
+const char kWatchTimeAudioVideoMse[] = "Media.WatchTime.AudioVideo.MSE";
+const char kWatchTimeAudioVideoEme[] = "Media.WatchTime.AudioVideo.EME";
+const char kWatchTimeAudioVideoSrc[] = "Media.WatchTime.AudioVideo.SRC";
+const char kWatchTimeAudioVideoBattery[] = "Media.WatchTime.AudioVideo.Battery";
+const char kWatchTimeAudioVideoAc[] = "Media.WatchTime.AudioVideo.AC";
+const char kWatchTimeAudioVideoEmbeddedExperience[] =
+    "Media.WatchTime.AudioVideo.EmbeddedExperience";
+
+// Audio only "watch time" metrics.
+const char kWatchTimeAudioAll[] = "Media.WatchTime.Audio.All";
+const char kWatchTimeAudioMse[] = "Media.WatchTime.Audio.MSE";
+const char kWatchTimeAudioEme[] = "Media.WatchTime.Audio.EME";
+const char kWatchTimeAudioSrc[] = "Media.WatchTime.Audio.SRC";
+const char kWatchTimeAudioBattery[] = "Media.WatchTime.Audio.Battery";
+const char kWatchTimeAudioAc[] = "Media.WatchTime.Audio.AC";
+const char kWatchTimeAudioEmbeddedExperience[] =
+    "Media.WatchTime.Audio.EmbeddedExperience";
+
+// Audio+video background watch time metrics.
+const char kWatchTimeAudioVideoBackgroundAll[] =
+    "Media.WatchTime.AudioVideo.Background.All";
+const char kWatchTimeAudioVideoBackgroundMse[] =
+    "Media.WatchTime.AudioVideo.Background.MSE";
+const char kWatchTimeAudioVideoBackgroundEme[] =
+    "Media.WatchTime.AudioVideo.Background.EME";
+const char kWatchTimeAudioVideoBackgroundSrc[] =
+    "Media.WatchTime.AudioVideo.Background.SRC";
+const char kWatchTimeAudioVideoBackgroundBattery[] =
+    "Media.WatchTime.AudioVideo.Background.Battery";
+const char kWatchTimeAudioVideoBackgroundAc[] =
+    "Media.WatchTime.AudioVideo.Background.AC";
+const char kWatchTimeAudioVideoBackgroundEmbeddedExperience[] =
+    "Media.WatchTime.AudioVideo.Background.EmbeddedExperience";
+
+const char kWatchTimeFinalize[] = "FinalizeWatchTime";
+const char kWatchTimeFinalizePower[] = "FinalizePowerWatchTime";
+
+const char kWatchTimeUnderflowCount[] = "UnderflowCount";
+
+const char kMeanTimeBetweenRebuffersAudioSrc[] =
+    "Media.MeanTimeBetweenRebuffers.Audio.SRC";
+const char kMeanTimeBetweenRebuffersAudioMse[] =
+    "Media.MeanTimeBetweenRebuffers.Audio.MSE";
+const char kMeanTimeBetweenRebuffersAudioEme[] =
+    "Media.MeanTimeBetweenRebuffers.Audio.EME";
+const char kMeanTimeBetweenRebuffersAudioVideoSrc[] =
+    "Media.MeanTimeBetweenRebuffers.AudioVideo.SRC";
+const char kMeanTimeBetweenRebuffersAudioVideoMse[] =
+    "Media.MeanTimeBetweenRebuffers.AudioVideo.MSE";
+const char kMeanTimeBetweenRebuffersAudioVideoEme[] =
+    "Media.MeanTimeBetweenRebuffers.AudioVideo.EME";
+
+base::flat_set<base::StringPiece> GetWatchTimeKeys() {
+  return base::flat_set<base::StringPiece>(
+      {kWatchTimeAudioAll,
+       kWatchTimeAudioMse,
+       kWatchTimeAudioEme,
+       kWatchTimeAudioSrc,
+       kWatchTimeAudioBattery,
+       kWatchTimeAudioAc,
+       kWatchTimeAudioEmbeddedExperience,
+       kWatchTimeAudioVideoAll,
+       kWatchTimeAudioVideoMse,
+       kWatchTimeAudioVideoEme,
+       kWatchTimeAudioVideoSrc,
+       kWatchTimeAudioVideoBattery,
+       kWatchTimeAudioVideoAc,
+       kWatchTimeAudioVideoEmbeddedExperience,
+       kWatchTimeAudioVideoBackgroundAll,
+       kWatchTimeAudioVideoBackgroundMse,
+       kWatchTimeAudioVideoBackgroundEme,
+       kWatchTimeAudioVideoBackgroundSrc,
+       kWatchTimeAudioVideoBackgroundBattery,
+       kWatchTimeAudioVideoBackgroundAc,
+       kWatchTimeAudioVideoBackgroundEmbeddedExperience},
+      base::KEEP_FIRST_OF_DUPES);
+}
+
+base::flat_set<base::StringPiece> GetWatchTimePowerKeys() {
+  return base::flat_set<base::StringPiece>(
+      {kWatchTimeAudioBattery, kWatchTimeAudioAc, kWatchTimeAudioVideoBattery,
+       kWatchTimeAudioVideoAc, kWatchTimeAudioVideoBackgroundBattery,
+       kWatchTimeAudioVideoBackgroundAc},
+      base::KEEP_FIRST_OF_DUPES);
+}
+
+}  // namespace media
diff --git a/media/base/watch_time_keys.h b/media/base/watch_time_keys.h
new file mode 100644
index 0000000..2854131
--- /dev/null
+++ b/media/base/watch_time_keys.h
@@ -0,0 +1,61 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_WATCH_TIME_KEYS_H_
+#define MEDIA_BASE_WATCH_TIME_KEYS_H_
+
+#include "base/containers/flat_set.h"
+#include "base/strings/string_piece.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Histogram names used for reporting; also double as MediaLog key names.
+// NOTE: If you add to this list you must update GetWatchTimeKeys() and if
+// necessary, GetWatchTimePowerKeys().
+MEDIA_EXPORT extern const char kWatchTimeAudioAll[];
+MEDIA_EXPORT extern const char kWatchTimeAudioMse[];
+MEDIA_EXPORT extern const char kWatchTimeAudioEme[];
+MEDIA_EXPORT extern const char kWatchTimeAudioSrc[];
+MEDIA_EXPORT extern const char kWatchTimeAudioBattery[];
+MEDIA_EXPORT extern const char kWatchTimeAudioAc[];
+MEDIA_EXPORT extern const char kWatchTimeAudioEmbeddedExperience[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoAll[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoMse[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoEme[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoSrc[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBattery[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoAc[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoEmbeddedExperience[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundAll[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundMse[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundEme[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundSrc[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundBattery[];
+MEDIA_EXPORT extern const char kWatchTimeAudioVideoBackgroundAc[];
+MEDIA_EXPORT extern const char
+    kWatchTimeAudioVideoBackgroundEmbeddedExperience[];
+// **** If adding any line above this see the toplevel comment! ****
+
+// Markers which signify the watch time should be finalized immediately.
+MEDIA_EXPORT extern const char kWatchTimeFinalize[];
+MEDIA_EXPORT extern const char kWatchTimeFinalizePower[];
+
+// Count of the number of underflow events during a media session.
+MEDIA_EXPORT extern const char kWatchTimeUnderflowCount[];
+
+// UMA keys for MTBR samples.
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioSrc[];
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioMse[];
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioEme[];
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioVideoSrc[];
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioVideoMse[];
+MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioVideoEme[];
+
+MEDIA_EXPORT base::flat_set<base::StringPiece> GetWatchTimeKeys();
+MEDIA_EXPORT base::flat_set<base::StringPiece> GetWatchTimePowerKeys();
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_WATCH_TIME_KEYS_H_
diff --git a/media/blink/watch_time_reporter.cc b/media/blink/watch_time_reporter.cc
index 7ca8dc1..7bd76741 100644
--- a/media/blink/watch_time_reporter.cc
+++ b/media/blink/watch_time_reporter.cc
@@ -5,6 +5,7 @@
 #include "media/blink/watch_time_reporter.h"
 
 #include "base/power_monitor/power_monitor.h"
+#include "media/base/watch_time_keys.h"
 
 namespace media {
 
@@ -271,14 +272,13 @@
   std::unique_ptr<MediaLogEvent> log_event =
       media_log_->CreateEvent(MediaLogEvent::Type::WATCH_TIME_UPDATE);
 
-#define RECORD_WATCH_TIME(key, value)                                         \
-  do {                                                                        \
-    log_event->params.SetDoubleWithoutPathExpansion(                          \
-        has_video_                                                            \
-            ? MediaLog::kWatchTimeAudioVideo##key                             \
-            : (is_background_ ? MediaLog::kWatchTimeAudioVideoBackground##key \
-                              : MediaLog::kWatchTimeAudio##key),              \
-        value.InSecondsF());                                                  \
+#define RECORD_WATCH_TIME(key, value)                                      \
+  do {                                                                     \
+    log_event->params.SetDoubleWithoutPathExpansion(                       \
+        has_video_ ? kWatchTimeAudioVideo##key                             \
+                   : (is_background_ ? kWatchTimeAudioVideoBackground##key \
+                                     : kWatchTimeAudio##key),              \
+        value.InSecondsF());                                               \
   } while (0)
 
   // Only report watch time after some minimum amount has elapsed. Don't update
@@ -339,16 +339,16 @@
       }
     }
 
-    log_event->params.SetInteger(MediaLog::kUnderflowCount, underflow_count_);
+    log_event->params.SetInteger(kWatchTimeUnderflowCount, underflow_count_);
     pending_underflow_events_.clear();
   }
 
   // Always send finalize, even if we don't currently have any data, it's
   // harmless to send since nothing will be logged if we've already finalized.
   if (is_finalizing)
-    log_event->params.SetBoolean(MediaLog::kWatchTimeFinalize, true);
+    log_event->params.SetBoolean(kWatchTimeFinalize, true);
   else if (is_power_change_pending)
-    log_event->params.SetBoolean(MediaLog::kWatchTimeFinalizePower, true);
+    log_event->params.SetBoolean(kWatchTimeFinalizePower, true);
 
   if (!log_event->params.empty())
     media_log_->AddEvent(std::move(log_event));
diff --git a/media/blink/watch_time_reporter_unittest.cc b/media/blink/watch_time_reporter_unittest.cc
index 7464ed0..5a65d106 100644
--- a/media/blink/watch_time_reporter_unittest.cc
+++ b/media/blink/watch_time_reporter_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/run_loop.h"
 #include "base/test/test_message_loop.h"
 #include "media/base/mock_media_log.h"
+#include "media/base/watch_time_keys.h"
 #include "media/blink/watch_time_reporter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -17,23 +18,21 @@
 
 constexpr gfx::Size kSizeJustRight = gfx::Size(201, 201);
 
-#define EXPECT_WATCH_TIME(key, value)                                      \
-  do {                                                                     \
-    EXPECT_CALL(                                                           \
-        media_log_,                                                        \
-        OnWatchTimeUpdate(has_video_ ? MediaLog::kWatchTimeAudioVideo##key \
-                                     : MediaLog::kWatchTimeAudio##key,     \
-                          value))                                          \
-        .RetiresOnSaturation();                                            \
+#define EXPECT_WATCH_TIME(key, value)                                    \
+  do {                                                                   \
+    EXPECT_CALL(media_log_,                                              \
+                OnWatchTimeUpdate(has_video_ ? kWatchTimeAudioVideo##key \
+                                             : kWatchTimeAudio##key,     \
+                                  value))                                \
+        .RetiresOnSaturation();                                          \
   } while (0)
 
-#define EXPECT_BACKGROUND_WATCH_TIME(key, value)                           \
-  do {                                                                     \
-    DCHECK(has_video_);                                                    \
-    EXPECT_CALL(media_log_,                                                \
-                OnWatchTimeUpdate(                                         \
-                    MediaLog::kWatchTimeAudioVideoBackground##key, value)) \
-        .RetiresOnSaturation();                                            \
+#define EXPECT_BACKGROUND_WATCH_TIME(key, value)                               \
+  do {                                                                         \
+    DCHECK(has_video_);                                                        \
+    EXPECT_CALL(media_log_,                                                    \
+                OnWatchTimeUpdate(kWatchTimeAudioVideoBackground##key, value)) \
+        .RetiresOnSaturation();                                                \
   } while (0)
 
 #define EXPECT_WATCH_TIME_FINALIZED() \
@@ -59,7 +58,7 @@
            it.Advance()) {
         bool finalize;
         if (it.value().GetAsBoolean(&finalize)) {
-          if (it.key() == MediaLog::kWatchTimeFinalize)
+          if (it.key() == kWatchTimeFinalize)
             OnWatchTimeFinalized();
           else
             OnPowerWatchTimeFinalized();
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index f9db61f3..b6fc0c97 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -534,6 +534,7 @@
 }
 
 void ChunkDemuxer::SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) {
+  base::AutoLock auto_lock(lock_);
   DCHECK(!cb.is_null());
   for (const auto& stream : audio_streams_)
     stream->SetStreamStatusChangeCB(cb);
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 6b6dbf8..ac40a7a 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -793,6 +793,8 @@
       "http/bidirectional_stream_impl.h",
       "http/bidirectional_stream_request_info.cc",
       "http/bidirectional_stream_request_info.h",
+      "http/broken_alternative_services.cc",
+      "http/broken_alternative_services.h",
       "http/des.cc",
       "http/des.h",
       "http/failing_http_transaction_factory.cc",
@@ -4436,6 +4438,7 @@
     "ftp/ftp_network_transaction_unittest.cc",
     "ftp/ftp_util_unittest.cc",
     "http/bidirectional_stream_unittest.cc",
+    "http/broken_alternative_services_unittest.cc",
     "http/des_unittest.cc",
     "http/http_auth_cache_unittest.cc",
     "http/http_auth_challenge_tokenizer_unittest.cc",
diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc
index c33515e..4a963be 100644
--- a/net/dns/host_resolver_impl_unittest.cc
+++ b/net/dns/host_resolver_impl_unittest.cc
@@ -2407,7 +2407,14 @@
   EXPECT_TRUE(requests_[2]->HasOneAddress("192.168.0.3", 80));
 }
 
-TEST_F(HostResolverImplDnsTest, NoIPv6OnWifi) {
+// TODO(crbug.com/728808): This test causes HttpNetworkTransactionTests to crash
+// on iOS.
+#if defined(OS_IOS)
+#define MAYBE_NoIPv6OnWifi DISABLED_NoIPv6OnWifi
+#else
+#define MAYBE_NoIPv6OnWifi NoIPv6OnWifi
+#endif
+TEST_F(HostResolverImplDnsTest, MAYBE_NoIPv6OnWifi) {
   test::ScopedMockNetworkChangeNotifier notifier;
   CreateSerialResolver();  // To guarantee order of resolutions.
   resolver_->SetNoIPv6OnWifi(true);
diff --git a/net/http/broken_alternative_services.cc b/net/http/broken_alternative_services.cc
new file mode 100644
index 0000000..d660f25
--- /dev/null
+++ b/net/http/broken_alternative_services.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 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 "net/http/broken_alternative_services.h"
+
+#include "base/memory/singleton.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "net/http/http_server_properties_impl.h"
+
+namespace net {
+
+namespace {
+
+// Initial delay for broken alternative services.
+const uint64_t kBrokenAlternativeProtocolDelaySecs = 300;
+// Subsequent failures result in exponential (base 2) backoff.
+// Limit binary shift to limit delay to approximately 2 days.
+const int kBrokenDelayMaxShift = 9;
+
+base::TimeDelta ComputeBrokenAlternativeServiceExpirationDelay(
+    int broken_count) {
+  DCHECK_GE(broken_count, 0);
+  if (broken_count > kBrokenDelayMaxShift)
+    broken_count = kBrokenDelayMaxShift;
+  return base::TimeDelta::FromSeconds(kBrokenAlternativeProtocolDelaySecs) *
+         (1 << broken_count);
+}
+
+}  // namespace
+
+BrokenAlternativeServices::BrokenAlternativeServices(Delegate* delegate,
+                                                     base::TickClock* clock)
+    : delegate_(delegate),
+      clock_(clock),
+      recently_broken_alternative_services_(
+          RecentlyBrokenAlternativeServices::NO_AUTO_EVICT),
+      weak_ptr_factory_(this) {
+  DCHECK(delegate_);
+  DCHECK(clock_);
+}
+
+BrokenAlternativeServices::~BrokenAlternativeServices() {}
+
+void BrokenAlternativeServices::MarkAlternativeServiceBroken(
+    const AlternativeService& alternative_service) {
+  // Empty host means use host of origin, callers are supposed to substitute.
+  DCHECK(!alternative_service.host.empty());
+  DCHECK_NE(kProtoUnknown, alternative_service.protocol);
+
+  auto it = recently_broken_alternative_services_.Get(alternative_service);
+  int broken_count = 0;
+  if (it == recently_broken_alternative_services_.end()) {
+    recently_broken_alternative_services_.Put(alternative_service, 1);
+  } else {
+    broken_count = it->second++;
+  }
+  base::TimeTicks expiration =
+      clock_->NowTicks() +
+      ComputeBrokenAlternativeServiceExpirationDelay(broken_count);
+  // Return if alternative service is already in expiration queue.
+  BrokenAlternativeServiceList::iterator list_it;
+  if (!AddToBrokenAlternativeServiceListAndMap(alternative_service, expiration,
+                                               &list_it)) {
+    return;
+  }
+
+  // If this is now the first entry in the list (i.e. |alternative_service| is
+  // the next alt svc to expire), schedule an expiration task for it.
+  if (list_it == broken_alternative_service_list_.begin()) {
+    ScheduleBrokenAlternateProtocolMappingsExpiration();
+  }
+}
+
+void BrokenAlternativeServices::MarkAlternativeServiceRecentlyBroken(
+    const AlternativeService& alternative_service) {
+  DCHECK_NE(kProtoUnknown, alternative_service.protocol);
+  if (recently_broken_alternative_services_.Get(alternative_service) ==
+      recently_broken_alternative_services_.end()) {
+    recently_broken_alternative_services_.Put(alternative_service, 1);
+  }
+}
+
+bool BrokenAlternativeServices::IsAlternativeServiceBroken(
+    const AlternativeService& alternative_service) const {
+  // Empty host means use host of origin, callers are supposed to substitute.
+  DCHECK(!alternative_service.host.empty());
+  return broken_alternative_service_map_.find(alternative_service) !=
+         broken_alternative_service_map_.end();
+}
+
+bool BrokenAlternativeServices::WasAlternativeServiceRecentlyBroken(
+    const AlternativeService& alternative_service) {
+  return recently_broken_alternative_services_.Get(alternative_service) !=
+         recently_broken_alternative_services_.end();
+}
+
+void BrokenAlternativeServices::ConfirmAlternativeService(
+    const AlternativeService& alternative_service) {
+  DCHECK_NE(kProtoUnknown, alternative_service.protocol);
+
+  // Remove |alternative_service| from |alternative_service_list_| and
+  // |alternative_service_map_|.
+  auto map_it = broken_alternative_service_map_.find(alternative_service);
+  if (map_it != broken_alternative_service_map_.end()) {
+    broken_alternative_service_list_.erase(map_it->second);
+    broken_alternative_service_map_.erase(map_it);
+  }
+
+  auto it = recently_broken_alternative_services_.Get(alternative_service);
+  if (it != recently_broken_alternative_services_.end()) {
+    recently_broken_alternative_services_.Erase(it);
+  }
+}
+
+bool BrokenAlternativeServices::AddToBrokenAlternativeServiceListAndMap(
+    const AlternativeService& alternative_service,
+    base::TimeTicks expiration,
+    BrokenAlternativeServiceList::iterator* it) {
+  DCHECK(it);
+
+  auto map_it = broken_alternative_service_map_.find(alternative_service);
+  if (map_it != broken_alternative_service_map_.end())
+    return false;
+
+  // Iterate from end of |broken_alternative_service_list_| to find where to
+  // insert it to keep the list sorted by expiration time.
+  auto list_it = broken_alternative_service_list_.end();
+  while (list_it != broken_alternative_service_list_.begin()) {
+    --list_it;
+    if (list_it->expiration <= expiration) {
+      ++list_it;
+      break;
+    }
+  }
+
+  // Insert |alternative_service| into the list and the map
+  list_it = broken_alternative_service_list_.insert(
+      list_it, BrokenAltSvcExpireInfo(alternative_service, expiration));
+  broken_alternative_service_map_.insert(
+      std::make_pair(alternative_service, list_it));
+
+  *it = list_it;
+  return true;
+}
+
+void BrokenAlternativeServices::ExpireBrokenAlternateProtocolMappings() {
+  base::TimeTicks now = clock_->NowTicks();
+
+  while (!broken_alternative_service_list_.empty()) {
+    auto it = broken_alternative_service_list_.begin();
+    if (now < it->expiration) {
+      break;
+    }
+
+    delegate_->OnExpireBrokenAlternativeService(it->alternative_service);
+
+    broken_alternative_service_map_.erase(it->alternative_service);
+    broken_alternative_service_list_.erase(it);
+  }
+
+  if (!broken_alternative_service_list_.empty())
+    ScheduleBrokenAlternateProtocolMappingsExpiration();
+}
+
+void BrokenAlternativeServices ::
+    ScheduleBrokenAlternateProtocolMappingsExpiration() {
+  DCHECK(!broken_alternative_service_list_.empty());
+  base::TimeTicks now = clock_->NowTicks();
+  base::TimeTicks when = broken_alternative_service_list_.front().expiration;
+  base::TimeDelta delay = when > now ? when - now : base::TimeDelta();
+  expiration_timer_.Stop();
+  expiration_timer_.Start(
+      FROM_HERE, delay,
+      base::Bind(
+          &BrokenAlternativeServices ::ExpireBrokenAlternateProtocolMappings,
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+}  // namespace net
diff --git a/net/http/broken_alternative_services.h b/net/http/broken_alternative_services.h
new file mode 100644
index 0000000..6a51222e
--- /dev/null
+++ b/net/http/broken_alternative_services.h
@@ -0,0 +1,145 @@
+// Copyright (c) 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 NET_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_
+#define NET_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_
+
+#include <list>
+#include <unordered_map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "net/http/http_server_properties.h"
+
+namespace base {
+class TickClock;
+}
+
+namespace net {
+
+struct AlternativeServiceHash {
+  size_t operator()(const net::AlternativeService& entry) const {
+    return entry.protocol ^ std::hash<std::string>()(entry.host) ^ entry.port;
+  }
+};
+
+// This class tracks HTTP alternative services that have been marked as broken.
+// The brokenness of an alt-svc will expire after some time according to an
+// exponential back-off formula: each time an alt-svc is marked broken, the
+// expiration delay will be some constant multiple of its previous expiration
+// delay. This prevents broken alt-svcs from being retried too often by the
+// network stack.
+class NET_EXPORT_PRIVATE BrokenAlternativeServices {
+ public:
+  // Delegate to be used by owner so it can be notified when the brokenness of
+  // an AlternativeService expires.
+  class NET_EXPORT Delegate {
+   public:
+    // Called when a broken alternative service's expiration time is reached.
+    virtual void OnExpireBrokenAlternativeService(
+        const AlternativeService& expired_alternative_service) = 0;
+    virtual ~Delegate() {}
+  };
+
+  // |delegate| will be notified when a broken alternative service expires. It
+  // must not be null.
+  // |clock| is used for setting expiration times and scheduling the
+  // expiration of broken alternative services. It must not be null.
+  // |delegate| and |clock| are both unowned and must outlive this.
+  BrokenAlternativeServices(Delegate* delegate, base::TickClock* clock);
+
+  BrokenAlternativeServices(const BrokenAlternativeServices&) = delete;
+  void operator=(const BrokenAlternativeServices&) = delete;
+
+  ~BrokenAlternativeServices();
+
+  // Marks |alternative_service| as broken until after some expiration delay
+  // (determined by how many times it's been marked broken before). Being broken
+  // will cause IsAlternativeServiceBroken(alternative_service) to return true
+  // until the expiration time is reached, or until
+  // ConfirmAlternativeService(alternative_service) is called.
+  void MarkAlternativeServiceBroken(
+      const AlternativeService& alternative_service);
+
+  // Marks |alternative_service| as recently broken. Being recently broken will
+  // cause WasAlternativeServiceRecentlyBroken(alternative_service) to return
+  // true until ConfirmAlternativeService(alternative_service) is called.
+  void MarkAlternativeServiceRecentlyBroken(
+      const AlternativeService& alternative_service);
+
+  // Returns true if MarkAlternativeServiceBroken(alternative_service) has been
+  // called, the expiration time has not been reached, and
+  // ConfirmAlternativeService(alternative_service) has not been called
+  // afterwards.
+  bool IsAlternativeServiceBroken(
+      const AlternativeService& alternative_service) const;
+
+  // Returns true if MarkAlternativeServiceRecentlyBroken(alternative_service)
+  // or MarkAlternativeServiceBroken(alternative_service) has been called and
+  // ConfirmAlternativeService(alternative_service) has not been called
+  // afterwards (even if brokenness of |alternative_service| has expired).
+  bool WasAlternativeServiceRecentlyBroken(
+      const AlternativeService& alternative_service);
+
+  // Marks |alternative_service| as not broken and not recently broken.
+  void ConfirmAlternativeService(const AlternativeService& alternative_service);
+
+ private:
+  // TODO (wangyix): modify HttpServerPropertiesImpl unit tests so this
+  // friendness is no longer required.
+  friend class HttpServerPropertiesImplPeer;
+
+  // A pair containing a broken AlternativeService and the expiration time of
+  // its brokenness.
+  struct BrokenAltSvcExpireInfo {
+    BrokenAltSvcExpireInfo(const AlternativeService& alt_svc,
+                           base::TimeTicks expire)
+        : alternative_service(alt_svc), expiration(expire) {}
+
+    AlternativeService alternative_service;
+    base::TimeTicks expiration;
+  };
+
+  typedef std::list<BrokenAltSvcExpireInfo> BrokenAlternativeServiceList;
+
+  typedef std::unordered_map<AlternativeService,
+                             BrokenAlternativeServiceList::iterator,
+                             AlternativeServiceHash>
+      BrokenAlternativeServiceMap;
+
+  // Inserts |alternative_service| and its |expiration| time into
+  // |broken_alternative_service_list_| and |broken_alternative_service_map_|.
+  // |it| is the position in |broken_alternative_service_list_| where it was
+  // inserted.
+  bool AddToBrokenAlternativeServiceListAndMap(
+      const AlternativeService& alternative_service,
+      base::TimeTicks expiration,
+      BrokenAlternativeServiceList::iterator* it);
+
+  void ExpireBrokenAlternateProtocolMappings();
+  void ScheduleBrokenAlternateProtocolMappingsExpiration();
+
+  Delegate* delegate_;      // Unowned
+  base::TickClock* clock_;  // Unowned
+
+  // List of <broken alt svc, expiration time> pairs sorted by expiration time.
+  BrokenAlternativeServiceList broken_alternative_service_list_;
+  // A map from broken alt-svcs to their iterator pointing to that alt-svc's
+  // position in |broken_alternative_service_list_|.
+  BrokenAlternativeServiceMap broken_alternative_service_map_;
+
+  // Maps broken alternative services to how many times they've been marked
+  // broken.
+  RecentlyBrokenAlternativeServices recently_broken_alternative_services_;
+
+  // Used for scheduling the task that expires the brokenness of alternative
+  // services.
+  base::OneShotTimer expiration_timer_;
+
+  base::WeakPtrFactory<BrokenAlternativeServices> weak_ptr_factory_;
+};
+
+}  // namespace net
+
+#endif  // NET_HTTP_BROKEN_ALTERNATIVE_SERVICES_H_
diff --git a/net/http/broken_alternative_services_unittest.cc b/net/http/broken_alternative_services_unittest.cc
new file mode 100644
index 0000000..0c58582
--- /dev/null
+++ b/net/http/broken_alternative_services_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright (c) 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 "net/http/broken_alternative_services.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/time/tick_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class BrokenAlternativeServicesTest
+    : public BrokenAlternativeServices::Delegate,
+      public ::testing::Test {
+ public:
+  BrokenAlternativeServicesTest()
+      : test_task_runner_(new base::TestMockTimeTaskRunner()),
+        test_task_runner_context_(test_task_runner_),
+        broken_services_clock_(test_task_runner_->GetMockTickClock()),
+        broken_services_(this, broken_services_clock_.get()) {}
+
+  // BrokenAlternativeServices::Delegate implementation
+  void OnExpireBrokenAlternativeService(
+      const AlternativeService& expired_alternative_service) override {
+    expired_alt_svcs_.push_back(expired_alternative_service);
+  }
+
+  // All tests will run inside the scope of |test_task_runner_context_|, which
+  // means any task posted to the main message loop will run on
+  // |test_task_runner_|.
+  scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
+  base::TestMockTimeTaskRunner::ScopedContext test_task_runner_context_;
+
+  std::unique_ptr<base::TickClock> broken_services_clock_;
+  BrokenAlternativeServices broken_services_;
+
+  std::vector<AlternativeService> expired_alt_svcs_;
+};
+
+TEST_F(BrokenAlternativeServicesTest, MarkBroken) {
+  const AlternativeService alternative_service1(kProtoHTTP2, "foo", 443);
+  const AlternativeService alternative_service2(kProtoHTTP2, "foo", 1234);
+
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service2);
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  broken_services_.ConfirmAlternativeService(alternative_service1);
+
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  broken_services_.ConfirmAlternativeService(alternative_service2);
+
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  EXPECT_EQ(0u, expired_alt_svcs_.size());
+}
+
+TEST_F(BrokenAlternativeServicesTest, MarkRecentlyBroken) {
+  const AlternativeService alternative_service(kProtoHTTP2, "foo", 443);
+
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+  EXPECT_FALSE(broken_services_.WasAlternativeServiceRecentlyBroken(
+      alternative_service));
+
+  broken_services_.MarkAlternativeServiceRecentlyBroken(alternative_service);
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+  EXPECT_TRUE(broken_services_.WasAlternativeServiceRecentlyBroken(
+      alternative_service));
+
+  broken_services_.ConfirmAlternativeService(alternative_service);
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+  EXPECT_FALSE(broken_services_.WasAlternativeServiceRecentlyBroken(
+      alternative_service));
+}
+
+TEST_F(BrokenAlternativeServicesTest, ExpireBrokenAlternateProtocolMappings) {
+  AlternativeService alternative_service(kProtoQUIC, "foo", 443);
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+
+  // |broken_services_| should have posted task to expire the brokenness of
+  // |alternative_service|.
+  EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+
+  // Advance time until one time quantum before |alternative_service1|'s
+  // brokenness expires
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5) -
+                                   base::TimeDelta::FromInternalValue(1));
+
+  // Ensure |alternative_service| is still marked broken.
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  EXPECT_EQ(0u, expired_alt_svcs_.size());
+  EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+
+  // Advance time by one time quantum.
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+
+  // Ensure |alternative_service| brokenness has expired but is still
+  // considered recently broken
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+  EXPECT_FALSE(test_task_runner_->HasPendingTask());
+  EXPECT_EQ(1u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service, expired_alt_svcs_[0]);
+  EXPECT_TRUE(broken_services_.WasAlternativeServiceRecentlyBroken(
+      alternative_service));
+}
+
+TEST_F(BrokenAlternativeServicesTest, ExponentialBackoff) {
+  // Tests the exponential backoff of the computed expiration delay when an
+  // alt svc is marked broken. After being marked broken 10 times, the max
+  // expiration delay will have been reached and exponential backoff will no
+  // longer apply.
+
+  AlternativeService alternative_service(kProtoQUIC, "foo", 443);
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(10) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(20) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(40) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(80) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(160) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(320) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(640) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(1280) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(2560) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+
+  // Max expiration delay has been reached; subsequent expiration delays from
+  // this point forward should not increase further.
+  broken_services_.MarkAlternativeServiceBroken(alternative_service);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(2560) -
+                                   base::TimeDelta::FromInternalValue(1));
+  EXPECT_TRUE(broken_services_.IsAlternativeServiceBroken(alternative_service));
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service));
+}
+
+TEST_F(BrokenAlternativeServicesTest, RemoveExpiredBrokenAltSvc) {
+  // This test will mark broken an alternative service A that has already been
+  // marked broken many times, then immediately mark another alternative service
+  // B as broken for the first time. Because A's been marked broken many times
+  // already, its brokenness will be scheduled to expire much further in the
+  // future than B, even though it was marked broken before B. This test makes
+  // sure that even though A was marked broken before B, B's brokenness should
+  // expire before A.
+
+  AlternativeService alternative_service1(kProtoQUIC, "foo", 443);
+  AlternativeService alternative_service2(kProtoQUIC, "bar", 443);
+
+  // Repeately mark |alternative_service1| broken and let brokenness expire.
+  // Do this a few times.
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5));
+  EXPECT_EQ(1u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service1, expired_alt_svcs_.back());
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(10));
+  EXPECT_EQ(2u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service1, expired_alt_svcs_.back());
+
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(20));
+  EXPECT_EQ(3u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service1, expired_alt_svcs_.back());
+
+  expired_alt_svcs_.clear();
+
+  // Mark |alternative_service1| broken (will be given longer expiration delay),
+  // then mark |alternative_service2| broken (will be given shorter expiration
+  // delay).
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  broken_services_.MarkAlternativeServiceBroken(alternative_service2);
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+
+  // Advance time until one time quantum before |alternative_service2|'s
+  // brokenness expires.
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5) -
+                                   base::TimeDelta::FromInternalValue(1));
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+  EXPECT_EQ(0u, expired_alt_svcs_.size());
+
+  // Advance time by one time quantum. |alternative_service2| should no longer
+  // be broken.
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+  EXPECT_EQ(1u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service2, expired_alt_svcs_[0]);
+
+  // Advance time until one time quantum before |alternative_service1|'s
+  // brokenness expires
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(40) -
+                                   base::TimeDelta::FromMinutes(5) -
+                                   base::TimeDelta::FromInternalValue(1));
+
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+  EXPECT_EQ(1u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service2, expired_alt_svcs_[0]);
+
+  // Advance time by one time quantum.  |alternative_service1| should no longer
+  // be broken.
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromInternalValue(1));
+
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+  EXPECT_EQ(2u, expired_alt_svcs_.size());
+  EXPECT_EQ(alternative_service2, expired_alt_svcs_[0]);
+  EXPECT_EQ(alternative_service1, expired_alt_svcs_[1]);
+}
+
+TEST_F(BrokenAlternativeServicesTest, ScheduleExpireTaskAfterExpire) {
+  // This test will check that when a broken alt svc expires, an expiration task
+  // is scheduled for the next broken alt svc in the expiration queue.
+
+  AlternativeService alternative_service1(kProtoQUIC, "foo", 443);
+  AlternativeService alternative_service2(kProtoQUIC, "bar", 443);
+
+  // Mark |alternative_service1| broken and let brokenness expire. This will
+  // increase its expiration delay the next time it's marked broken.
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(test_task_runner_->HasPendingTask());
+
+  // Mark |alternative_service1| and |alternative_service2| broken and
+  // let |alternative_service2|'s brokenness expire.
+  broken_services_.MarkAlternativeServiceBroken(alternative_service1);
+  broken_services_.MarkAlternativeServiceBroken(alternative_service2);
+
+  test_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(5));
+  EXPECT_FALSE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service2));
+  EXPECT_TRUE(
+      broken_services_.IsAlternativeServiceBroken(alternative_service1));
+
+  // Make sure an expiration task has been scheduled for expiring the brokenness
+  // of |alternative_service1|.
+  EXPECT_TRUE(test_task_runner_->HasPendingTask());
+}
+
+}  // namespace
+
+}  // namespace net
\ No newline at end of file
diff --git a/net/http/http_server_properties_impl.cc b/net/http/http_server_properties_impl.cc
index 7cc4a32..abf4bf0 100644
--- a/net/http/http_server_properties_impl.cc
+++ b/net/http/http_server_properties_impl.cc
@@ -20,25 +20,17 @@
 
 namespace net {
 
-namespace {
-
-// Initial delay for broken alternative services.
-const uint64_t kBrokenAlternativeProtocolDelaySecs = 300;
-// Subsequent failures result in exponential (base 2) backoff.
-// Limit binary shift to limit delay to approximately 2 days.
-const int kBrokenDelayMaxShift = 9;
-
-}  // namespace
-
 HttpServerPropertiesImpl::HttpServerPropertiesImpl()
-    : spdy_servers_map_(SpdyServersMap::NO_AUTO_EVICT),
+    : HttpServerPropertiesImpl(&broken_alternative_services_clock_) {}
+
+HttpServerPropertiesImpl::HttpServerPropertiesImpl(
+    base::TickClock* broken_alternative_services_clock)
+    : broken_alternative_services_(this, broken_alternative_services_clock),
+      spdy_servers_map_(SpdyServersMap::NO_AUTO_EVICT),
       alternative_service_map_(AlternativeServiceMap::NO_AUTO_EVICT),
-      recently_broken_alternative_services_(
-          RecentlyBrokenAlternativeServices::NO_AUTO_EVICT),
       server_network_stats_map_(ServerNetworkStatsMap::NO_AUTO_EVICT),
       quic_server_info_map_(QuicServerInfoMap::NO_AUTO_EVICT),
-      max_server_configs_stored_in_properties_(kMaxQuicServersToPersist),
-      weak_ptr_factory_(this) {
+      max_server_configs_stored_in_properties_(kMaxQuicServersToPersist) {
   canonical_suffixes_.push_back(".ggpht.com");
   canonical_suffixes_.push_back(".c.youtube.com");
   canonical_suffixes_.push_back(".googlevideo.com");
@@ -454,71 +446,31 @@
 
 void HttpServerPropertiesImpl::MarkAlternativeServiceBroken(
     const AlternativeService& alternative_service) {
-  // Empty host means use host of origin, callers are supposed to substitute.
-  DCHECK(!alternative_service.host.empty());
-  if (alternative_service.protocol == kProtoUnknown) {
-    LOG(DFATAL) << "Trying to mark unknown alternate protocol broken.";
-    return;
-  }
-  auto it = recently_broken_alternative_services_.Get(alternative_service);
-  int shift = 0;
-  if (it == recently_broken_alternative_services_.end()) {
-    recently_broken_alternative_services_.Put(alternative_service, 1);
-  } else {
-    shift = it->second++;
-  }
-  if (shift > kBrokenDelayMaxShift)
-    shift = kBrokenDelayMaxShift;
-  base::TimeDelta delay =
-      base::TimeDelta::FromSeconds(kBrokenAlternativeProtocolDelaySecs);
-  base::TimeTicks when = base::TimeTicks::Now() + delay * (1 << shift);
-  auto result = broken_alternative_services_.insert(
-      std::make_pair(alternative_service, when));
-  // Return if alternative service is already in expiration queue.
-  if (!result.second) {
-    return;
-  }
-
-  // If this is the only entry in the list, schedule an expiration task.
-  // Otherwise it will be rescheduled automatically when the pending task runs.
-  if (broken_alternative_services_.size() == 1) {
-    ScheduleBrokenAlternateProtocolMappingsExpiration();
-  }
+  broken_alternative_services_.MarkAlternativeServiceBroken(
+      alternative_service);
 }
 
 void HttpServerPropertiesImpl::MarkAlternativeServiceRecentlyBroken(
     const AlternativeService& alternative_service) {
-  if (recently_broken_alternative_services_.Get(alternative_service) ==
-      recently_broken_alternative_services_.end()) {
-    recently_broken_alternative_services_.Put(alternative_service, 1);
-  }
+  broken_alternative_services_.MarkAlternativeServiceRecentlyBroken(
+      alternative_service);
 }
 
 bool HttpServerPropertiesImpl::IsAlternativeServiceBroken(
     const AlternativeService& alternative_service) const {
-  // Empty host means use host of origin, callers are supposed to substitute.
-  DCHECK(!alternative_service.host.empty());
-  return base::ContainsKey(broken_alternative_services_, alternative_service);
+  return broken_alternative_services_.IsAlternativeServiceBroken(
+      alternative_service);
 }
 
 bool HttpServerPropertiesImpl::WasAlternativeServiceRecentlyBroken(
     const AlternativeService& alternative_service) {
-  if (alternative_service.protocol == kProtoUnknown)
-    return false;
-
-  return recently_broken_alternative_services_.Get(alternative_service) !=
-         recently_broken_alternative_services_.end();
+  return broken_alternative_services_.WasAlternativeServiceRecentlyBroken(
+      alternative_service);
 }
 
 void HttpServerPropertiesImpl::ConfirmAlternativeService(
     const AlternativeService& alternative_service) {
-  if (alternative_service.protocol == kProtoUnknown)
-    return;
-  broken_alternative_services_.erase(alternative_service);
-  auto it = recently_broken_alternative_services_.Get(alternative_service);
-  if (it != recently_broken_alternative_services_.end()) {
-    recently_broken_alternative_services_.Erase(it);
-  }
+  broken_alternative_services_.ConfirmAlternativeService(alternative_service);
 }
 
 const AlternativeServiceMap& HttpServerPropertiesImpl::alternative_service_map()
@@ -712,65 +664,37 @@
   canonical_host_to_origin_map_.erase(canonical->first);
 }
 
-void HttpServerPropertiesImpl::ExpireBrokenAlternateProtocolMappings() {
-  base::TimeTicks now = base::TimeTicks::Now();
-  while (!broken_alternative_services_.empty()) {
-    BrokenAlternativeServices::iterator it =
-        broken_alternative_services_.begin();
-    if (now < it->second) {
-      break;
-    }
-
-    const AlternativeService expired_alternative_service = it->first;
-    broken_alternative_services_.erase(it);
-
-    // Remove every occurrence of |expired_alternative_service| from
-    // |alternative_service_map_|.
-    for (AlternativeServiceMap::iterator map_it =
-             alternative_service_map_.begin();
-         map_it != alternative_service_map_.end();) {
-      for (AlternativeServiceInfoVector::iterator it = map_it->second.begin();
-           it != map_it->second.end();) {
-        AlternativeService alternative_service(it->alternative_service);
-        // Empty hostname in map means hostname of key: substitute before
-        // comparing to |expired_alternative_service|.
-        if (alternative_service.host.empty()) {
-          alternative_service.host = map_it->first.host();
-        }
-        if (alternative_service == expired_alternative_service) {
-          it = map_it->second.erase(it);
-          continue;
-        }
-        ++it;
+void HttpServerPropertiesImpl::OnExpireBrokenAlternativeService(
+    const AlternativeService& expired_alternative_service) {
+  // Remove every occurrence of |expired_alternative_service| from
+  // |alternative_service_map_|.
+  for (AlternativeServiceMap::iterator map_it =
+           alternative_service_map_.begin();
+       map_it != alternative_service_map_.end();) {
+    for (AlternativeServiceInfoVector::iterator it = map_it->second.begin();
+         it != map_it->second.end();) {
+      AlternativeService alternative_service(it->alternative_service);
+      // Empty hostname in map means hostname of key: substitute before
+      // comparing to |expired_alternative_service|.
+      if (alternative_service.host.empty()) {
+        alternative_service.host = map_it->first.host();
       }
-      // If an origin has an empty list of alternative services, then remove it
-      // from both |canonical_host_to_origin_map_| and
-      // |alternative_service_map_|.
-      if (map_it->second.empty()) {
-        RemoveCanonicalHost(map_it->first);
-        map_it = alternative_service_map_.Erase(map_it);
+      if (alternative_service == expired_alternative_service) {
+        it = map_it->second.erase(it);
         continue;
       }
-      ++map_it;
+      ++it;
     }
+    // If an origin has an empty list of alternative services, then remove it
+    // from both |canonical_host_to_origin_map_| and
+    // |alternative_service_map_|.
+    if (map_it->second.empty()) {
+      RemoveCanonicalHost(map_it->first);
+      map_it = alternative_service_map_.Erase(map_it);
+      continue;
+    }
+    ++map_it;
   }
-  ScheduleBrokenAlternateProtocolMappingsExpiration();
-}
-
-void
-HttpServerPropertiesImpl::ScheduleBrokenAlternateProtocolMappingsExpiration() {
-  if (broken_alternative_services_.empty()) {
-    return;
-  }
-  base::TimeTicks now = base::TimeTicks::Now();
-  base::TimeTicks when = broken_alternative_services_.front().second;
-  base::TimeDelta delay = when > now ? when - now : base::TimeDelta();
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::Bind(
-          &HttpServerPropertiesImpl::ExpireBrokenAlternateProtocolMappings,
-          weak_ptr_factory_.GetWeakPtr()),
-      delay);
 }
 
 }  // namespace net
diff --git a/net/http/http_server_properties_impl.h b/net/http/http_server_properties_impl.h
index 598b4474..36e13e6 100644
--- a/net/http/http_server_properties_impl.h
+++ b/net/http/http_server_properties_impl.h
@@ -17,11 +17,13 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/non_thread_safe.h"
+#include "base/time/default_tick_clock.h"
 #include "base/values.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/ip_address.h"
 #include "net/base/linked_hash_map.h"
 #include "net/base/net_export.h"
+#include "net/http/broken_alternative_services.h"
 #include "net/http/http_server_properties.h"
 
 namespace base {
@@ -30,18 +32,15 @@
 
 namespace net {
 
-struct AlternativeServiceHash {
-  size_t operator()(const net::AlternativeService& entry) const {
-    return entry.protocol ^ std::hash<std::string>()(entry.host) ^ entry.port;
-  }
-};
-
 // The implementation for setting/retrieving the HTTP server properties.
 class NET_EXPORT HttpServerPropertiesImpl
     : public HttpServerProperties,
+      public BrokenAlternativeServices::Delegate,
       NON_EXPORTED_BASE(public base::NonThreadSafe) {
  public:
   HttpServerPropertiesImpl();
+  explicit HttpServerPropertiesImpl(
+      base::TickClock* broken_alternative_services_clock);
   ~HttpServerPropertiesImpl() override;
 
   // Sets |spdy_servers_map_| with the servers (host/port) from
@@ -122,7 +121,13 @@
       size_t max_server_configs_stored_in_properties) override;
   bool IsInitialized() const override;
 
+  // BrokenAlternativeServices::Delegate method.
+  void OnExpireBrokenAlternativeService(
+      const AlternativeService& expired_alternative_service) override;
+
  private:
+  // TODO (wangyix): modify HttpServerPropertiesImpl unit tests so this
+  // friendness is no longer required.
   friend class HttpServerPropertiesImplPeer;
 
   // |spdy_servers_map_| has flattened representation of servers
@@ -132,14 +137,6 @@
   typedef std::vector<std::string> CanonicalSufficList;
   typedef std::set<HostPortPair> Http11ServerHostPortSet;
 
-  // Linked hash map from AlternativeService to expiration time.  This container
-  // is a queue with O(1) enqueue and dequeue, and a hash_map with O(1) lookup
-  // at the same time.
-  typedef linked_hash_map<AlternativeService,
-                          base::TimeTicks,
-                          AlternativeServiceHash>
-      BrokenAlternativeServices;
-
   // Return the iterator for |server|, or for its canonical host, or end.
   AlternativeServiceMap::const_iterator GetAlternateProtocolIterator(
       const url::SchemeHostPort& server);
@@ -150,17 +147,14 @@
 
   // Remove the cononical host for |server|.
   void RemoveCanonicalHost(const url::SchemeHostPort& server);
-  void ExpireBrokenAlternateProtocolMappings();
-  void ScheduleBrokenAlternateProtocolMappingsExpiration();
+
+  base::DefaultTickClock broken_alternative_services_clock_;
+  BrokenAlternativeServices broken_alternative_services_;
 
   SpdyServersMap spdy_servers_map_;
   Http11ServerHostPortSet http11_servers_;
 
   AlternativeServiceMap alternative_service_map_;
-  BrokenAlternativeServices broken_alternative_services_;
-  // Class invariant:  Every alternative service in broken_alternative_services_
-  // must also be in recently_broken_alternative_services_.
-  RecentlyBrokenAlternativeServices recently_broken_alternative_services_;
 
   IPAddress last_quic_address_;
   ServerNetworkStatsMap server_network_stats_map_;
@@ -175,8 +169,6 @@
   QuicServerInfoMap quic_server_info_map_;
   size_t max_server_configs_stored_in_properties_;
 
-  base::WeakPtrFactory<HttpServerPropertiesImpl> weak_ptr_factory_;
-
   DISALLOW_COPY_AND_ASSIGN(HttpServerPropertiesImpl);
 };
 
diff --git a/net/http/http_server_properties_impl_unittest.cc b/net/http/http_server_properties_impl_unittest.cc
index 18edad7..46cd0df 100644
--- a/net/http/http_server_properties_impl_unittest.cc
+++ b/net/http/http_server_properties_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/test/test_mock_time_task_runner.h"
 #include "base/values.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/ip_address.h"
@@ -21,18 +22,30 @@
 
 namespace net {
 
+const base::TimeDelta BROKEN_ALT_SVC_EXPIRE_DELAYS[10] = {
+    base::TimeDelta::FromSeconds(300),   base::TimeDelta::FromSeconds(600),
+    base::TimeDelta::FromSeconds(1200),  base::TimeDelta::FromSeconds(2400),
+    base::TimeDelta::FromSeconds(4800),  base::TimeDelta::FromSeconds(9600),
+    base::TimeDelta::FromSeconds(19200), base::TimeDelta::FromSeconds(38400),
+    base::TimeDelta::FromSeconds(76800), base::TimeDelta::FromSeconds(153600),
+};
+
 class HttpServerPropertiesImplPeer {
  public:
   static void AddBrokenAlternativeServiceWithExpirationTime(
       HttpServerPropertiesImpl* impl,
-      AlternativeService alternative_service,
+      const AlternativeService& alternative_service,
       base::TimeTicks when) {
-    impl->broken_alternative_services_.insert(
-        std::make_pair(alternative_service, when));
+    BrokenAlternativeServices::BrokenAlternativeServiceList::iterator unused_it;
+    impl->broken_alternative_services_.AddToBrokenAlternativeServiceListAndMap(
+        alternative_service, when, &unused_it);
     auto it =
-        impl->recently_broken_alternative_services_.Get(alternative_service);
-    if (it == impl->recently_broken_alternative_services_.end()) {
-      impl->recently_broken_alternative_services_.Put(alternative_service, 1);
+        impl->broken_alternative_services_.recently_broken_alternative_services_
+            .Get(alternative_service);
+    if (it == impl->broken_alternative_services_
+                  .recently_broken_alternative_services_.end()) {
+      impl->broken_alternative_services_.recently_broken_alternative_services_
+          .Put(alternative_service, 1);
     } else {
       it->second++;
     }
@@ -40,7 +53,7 @@
 
   static void ExpireBrokenAlternateProtocolMappings(
       HttpServerPropertiesImpl* impl) {
-    impl->ExpireBrokenAlternateProtocolMappings();
+    impl->broken_alternative_services_.ExpireBrokenAlternateProtocolMappings();
   }
 };
 
@@ -50,6 +63,11 @@
 
 class HttpServerPropertiesImplTest : public testing::Test {
  protected:
+  HttpServerPropertiesImplTest()
+      : test_task_runner_(new base::TestMockTimeTaskRunner()),
+        broken_services_clock_(test_task_runner_->GetMockTickClock()),
+        impl_(broken_services_clock_.get()) {}
+
   bool HasAlternativeService(const url::SchemeHostPort& origin) {
     const AlternativeServiceInfoVector alternative_service_info_vector =
         impl_.GetAlternativeServiceInfos(origin);
@@ -63,6 +81,13 @@
     return impl_.SetAlternativeService(origin, alternative_service, expiration);
   }
 
+  void MarkBrokenAndLetExpireAlternativeServiceNTimes(
+      const AlternativeService& alternative_service,
+      int num_times) {}
+
+  scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
+
+  std::unique_ptr<base::TickClock> broken_services_clock_;
   HttpServerPropertiesImpl impl_;
 };
 
@@ -948,7 +973,7 @@
   EXPECT_FALSE(impl_.WasAlternativeServiceRecentlyBroken(alternative_service));
 
   base::TimeTicks past =
-      base::TimeTicks::Now() - base::TimeDelta::FromSeconds(42);
+      broken_services_clock_->NowTicks() - base::TimeDelta::FromSeconds(42);
   HttpServerPropertiesImplPeer::AddBrokenAlternativeServiceWithExpirationTime(
       &impl_, alternative_service, past);
   EXPECT_TRUE(impl_.IsAlternativeServiceBroken(alternative_service));
@@ -978,7 +1003,7 @@
 
   // Mark "bar:443" as broken.
   base::TimeTicks past =
-      base::TimeTicks::Now() - base::TimeDelta::FromSeconds(42);
+      broken_services_clock_->NowTicks() - base::TimeDelta::FromSeconds(42);
   HttpServerPropertiesImplPeer::AddBrokenAlternativeServiceWithExpirationTime(
       &impl_, bar_alternative_service, past);
 
@@ -998,6 +1023,70 @@
       impl_.WasAlternativeServiceRecentlyBroken(baz_alternative_service));
 }
 
+// Regression test for https://crbug.com/724302
+TEST_F(AlternateProtocolServerPropertiesTest, RemoveExpiredBrokenAltSvc2) {
+  // This test will mark an alternative service A that has already been marked
+  // broken many times, then immediately mark another alternative service B as
+  // broken for the first time. Because A's been marked broken many times
+  // already, its brokenness will be scheduled to expire much further in the
+  // future than B, even though it was marked broken before B. This test makes
+  // sure that even though A was marked broken before B, B's brokenness should
+  // expire before A.
+
+  url::SchemeHostPort server1("https", "foo", 443);
+  AlternativeService alternative_service1(kProtoQUIC, "foo", 443);
+  SetAlternativeService(server1, alternative_service1);
+
+  url::SchemeHostPort server2("https", "bar", 443);
+  AlternativeService alternative_service2(kProtoQUIC, "bar", 443);
+  SetAlternativeService(server2, alternative_service2);
+
+  // Repeatedly mark alt svc 1 broken and wait for its brokenness to expire.
+  // This will increase its time until expiration.
+  for (int i = 0; i < 3; ++i) {
+    {
+      base::TestMockTimeTaskRunner::ScopedContext scoped_context(
+          test_task_runner_);
+      impl_.MarkAlternativeServiceBroken(alternative_service1);
+    }
+    // |impl_| should have posted task to expire the brokenness of
+    // |alternative_service1|
+    EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+    EXPECT_TRUE(impl_.IsAlternativeServiceBroken(alternative_service1));
+
+    // Advance time by just enough so that |alternative_service1|'s brokenness
+    // expires.
+    test_task_runner_->FastForwardBy(BROKEN_ALT_SVC_EXPIRE_DELAYS[i]);
+
+    // Ensure brokenness of |alternative_service1| has expired.
+    EXPECT_FALSE(test_task_runner_->HasPendingTask());
+    EXPECT_FALSE(impl_.IsAlternativeServiceBroken(alternative_service1));
+  }
+
+  {
+    base::TestMockTimeTaskRunner::ScopedContext scoped_context(
+        test_task_runner_);
+    impl_.MarkAlternativeServiceBroken(alternative_service1);
+    impl_.MarkAlternativeServiceBroken(alternative_service2);
+  }
+
+  EXPECT_TRUE(impl_.IsAlternativeServiceBroken(alternative_service2));
+
+  // Advance time by just enough so that |alternative_service2|'s brokennness
+  // expires.
+  test_task_runner_->FastForwardBy(BROKEN_ALT_SVC_EXPIRE_DELAYS[0]);
+
+  EXPECT_TRUE(impl_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(impl_.IsAlternativeServiceBroken(alternative_service2));
+
+  // Advance time by enough so that |alternative_service1|'s brokenness expires.
+  test_task_runner_->FastForwardBy(BROKEN_ALT_SVC_EXPIRE_DELAYS[3] -
+                                   BROKEN_ALT_SVC_EXPIRE_DELAYS[0]);
+
+  EXPECT_FALSE(impl_.IsAlternativeServiceBroken(alternative_service1));
+  EXPECT_FALSE(impl_.IsAlternativeServiceBroken(alternative_service2));
+}
+
 typedef HttpServerPropertiesImplTest SupportsQuicServerPropertiesTest;
 
 TEST_F(SupportsQuicServerPropertiesTest, Set) {
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl.cc b/net/quic/chromium/bidirectional_stream_quic_impl.cc
index 5d2ae044..df82cec1 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl.cc
+++ b/net/quic/chromium/bidirectional_stream_quic_impl.cc
@@ -81,6 +81,10 @@
 }
 
 void BidirectionalStreamQuicImpl::SendRequestHeaders() {
+  WriteHeaders();
+}
+
+bool BidirectionalStreamQuicImpl::WriteHeaders() {
   DCHECK(!has_sent_headers_);
   if (!stream_) {
     LOG(ERROR)
@@ -88,7 +92,7 @@
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::Bind(&BidirectionalStreamQuicImpl::NotifyError,
                               weak_factory_.GetWeakPtr(), ERR_UNEXPECTED));
-    return;
+    return false;
   }
 
   SpdyHeaderBlock headers;
@@ -99,14 +103,17 @@
 
   CreateSpdyHeadersFromHttpRequest(
       http_request_info, http_request_info.extra_headers, true, &headers);
-  // Sending the request might result in |this| being deleted.
-  auto guard = weak_factory_.GetWeakPtr();
+  // Sending the request might result in the stream being closed via OnClose
+  // which will post a task to notify the delegate asynchronously.
+  // TODO(rch): Clean up this interface when OnClose and OnError are removed.
   size_t headers_bytes_sent = stream_->WriteHeaders(
       std::move(headers), request_info_->end_stream_on_headers, nullptr);
-  if (!guard.get())
-    return;
+  if (!stream_)
+    return false;
+
   headers_bytes_sent_ += headers_bytes_sent;
   has_sent_headers_ = true;
+  return true;
 }
 
 int BidirectionalStreamQuicImpl::ReadData(IOBuffer* buffer, int buffer_len) {
@@ -157,7 +164,9 @@
     // single data buffer.
     bundler =
         session_->CreatePacketBundler(QuicConnection::SEND_ACK_IF_PENDING);
-    SendRequestHeaders();
+    // Sending the request might result in the stream being closed.
+    if (!WriteHeaders())
+      return;
   }
 
   QuicStringPiece string_data(data->data(), length);
@@ -191,7 +200,9 @@
       session_->CreatePacketBundler(QuicConnection::SEND_ACK_IF_PENDING));
   if (!has_sent_headers_) {
     DCHECK(!send_request_headers_automatically_);
-    SendRequestHeaders();
+    // Sending the request might result in the stream being closed.
+    if (!WriteHeaders())
+      return;
   }
 
   int rv = stream_->WritevStreamData(
@@ -242,16 +253,15 @@
 
   if (stream_->connection_error() != QUIC_NO_ERROR ||
       stream_->stream_error() != QUIC_STREAM_NO_ERROR) {
-    NotifyError(session_->IsCryptoHandshakeConfirmed()
-                    ? ERR_QUIC_PROTOCOL_ERROR
-                    : ERR_QUIC_HANDSHAKE_FAILED);
+    OnError(session_->IsCryptoHandshakeConfirmed() ? ERR_QUIC_PROTOCOL_ERROR
+                                                   : ERR_QUIC_HANDSHAKE_FAILED);
     return;
   }
 
   if (!stream_->fin_sent() || !stream_->fin_received()) {
     // The connection must have been closed by the peer with QUIC_NO_ERROR,
     // which is improper.
-    NotifyError(ERR_UNEXPECTED);
+    OnError(ERR_UNEXPECTED);
     return;
   }
 
@@ -261,7 +271,8 @@
 }
 
 void BidirectionalStreamQuicImpl::OnError(int error) {
-  NotifyError(error);
+  // Avoid reentrancy by notifying the delegate asynchronously.
+  NotifyErrorImpl(error, /*notify_delegate_later*/ true);
 }
 
 void BidirectionalStreamQuicImpl::OnStreamReady(int rv) {
@@ -358,6 +369,11 @@
 }
 
 void BidirectionalStreamQuicImpl::NotifyError(int error) {
+  NotifyErrorImpl(error, /*notify_delegate_later*/ false);
+}
+
+void BidirectionalStreamQuicImpl::NotifyErrorImpl(int error,
+                                                  bool notify_delegate_later) {
   DCHECK_NE(OK, error);
   DCHECK_NE(ERR_IO_PENDING, error);
 
@@ -368,19 +384,29 @@
     delegate_ = nullptr;
     // Cancel any pending callback.
     weak_factory_.InvalidateWeakPtrs();
-    delegate->OnFailed(error);
-    // |this| might be destroyed at this point.
+    if (notify_delegate_later) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::Bind(&BidirectionalStreamQuicImpl::NotifyFailure,
+                                weak_factory_.GetWeakPtr(), delegate, error));
+    } else {
+      NotifyFailure(delegate, error);
+      // |this| might be destroyed at this point.
+    }
   }
 }
 
+void BidirectionalStreamQuicImpl::NotifyFailure(
+    BidirectionalStreamImpl::Delegate* delegate,
+    int error) {
+  delegate->OnFailed(error);
+  // |this| might be destroyed at this point.
+}
+
 void BidirectionalStreamQuicImpl::NotifyStreamReady() {
-  if (send_request_headers_automatically_) {
-    // Sending the request might result in |this| being deleted.
-    auto guard = weak_factory_.GetWeakPtr();
-    SendRequestHeaders();
-    if (!guard.get())
-      return;
-  }
+  // Sending the request might result in the stream being closed.
+  if (send_request_headers_automatically_ && !WriteHeaders())
+    return;
+
   if (delegate_)
     delegate_->OnStreamReady(has_sent_headers_);
 }
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl.h b/net/quic/chromium/bidirectional_stream_quic_impl.h
index 36129e1..af5bcf4 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl.h
+++ b/net/quic/chromium/bidirectional_stream_quic_impl.h
@@ -61,6 +61,10 @@
   void OnClose() override;
   void OnError(int error) override;
 
+  // Write headers to the stream and returns true on success. Posts a task to
+  // notify the delegate asynchronously and returns false on failure
+  bool WriteHeaders();
+
   void OnStreamReady(int rv);
   void OnSendDataComplete(int rv);
   void ReadInitialHeaders();
@@ -69,12 +73,20 @@
   void OnReadTrailingHeadersComplete(int rv);
   void OnReadDataComplete(int rv);
 
-  // Notifies the delegate of an error.
+  // Notifies the delegate of an error, clears |stream_| and |delegate_|,
+  // and cancels any pending callbacks.
   void NotifyError(int error);
+  // Notifies the delegate of an error, clears |stream_| and |delegate_|,
+  // and cancels any pending callbacks. If |notify_delegate_later| is true
+  // then the delegate will be notified asynchronously via a posted task,
+  // otherwise the notification will be synchronous.
+  void NotifyErrorImpl(int error, bool notify_delegate_later);
   // Notifies the delegate that the stream is ready.
   void NotifyStreamReady();
   // Resets the stream and ensures that |delegate_| won't be called back.
   void ResetStream();
+  // Invokes OnFailure(error) on |delegate|.
+  void NotifyFailure(BidirectionalStreamImpl::Delegate* delegate, int error);
 
   const std::unique_ptr<QuicChromiumClientSession::Handle> session_;
   std::unique_ptr<QuicChromiumClientStream::Handle> stream_;
diff --git a/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc b/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
index 94d6009..2efcb02 100644
--- a/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/chromium/bidirectional_stream_quic_impl_unittest.cc
@@ -1248,6 +1248,75 @@
       delegate->GetTotalReceivedBytes());
 }
 
+// Tests that when request headers are delayed and SendData triggers the
+// headers to be sent, if that write fails the stream does not crash.
+TEST_P(BidirectionalStreamQuicImplTest,
+       SendDataWriteErrorCoalesceDataBufferAndHeaderFrame) {
+  QuicStreamOffset header_stream_offset = 0;
+  AddWrite(ConstructInitialSettingsPacket(1, &header_stream_offset));
+  AddWriteError(SYNCHRONOUS, ERR_CONNECTION_REFUSED);
+
+  Initialize();
+
+  BidirectionalStreamRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.end_stream_on_headers = false;
+  request.priority = DEFAULT_PRIORITY;
+  request.extra_headers.SetHeader("cookie", std::string(2048, 'A'));
+
+  scoped_refptr<IOBuffer> read_buffer(new IOBuffer(kReadBufferSize));
+  std::unique_ptr<DeleteStreamDelegate> delegate(new DeleteStreamDelegate(
+      read_buffer.get(), kReadBufferSize, DeleteStreamDelegate::ON_FAILED));
+  delegate->DoNotSendRequestHeadersAutomatically();
+  delegate->Start(&request, net_log().bound(), session()->CreateHandle());
+  ConfirmHandshake();
+  delegate->WaitUntilNextCallback(kOnStreamReady);
+
+  // Attempt to send the headers and data.
+  const char kBody1[] = "here are some data";
+  scoped_refptr<StringIOBuffer> buf1(new StringIOBuffer(kBody1));
+  delegate->SendData(buf1, buf1->size(), !kFin);
+
+  delegate->WaitUntilNextCallback(kOnFailed);
+}
+
+// Tests that when request headers are delayed and SendvData triggers the
+// headers to be sent, if that write fails the stream does not crash.
+TEST_P(BidirectionalStreamQuicImplTest,
+       SendvDataWriteErrorCoalesceDataBufferAndHeaderFrame) {
+  QuicStreamOffset header_stream_offset = 0;
+  AddWrite(ConstructInitialSettingsPacket(1, &header_stream_offset));
+  AddWriteError(SYNCHRONOUS, ERR_CONNECTION_REFUSED);
+
+  Initialize();
+
+  BidirectionalStreamRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.end_stream_on_headers = false;
+  request.priority = DEFAULT_PRIORITY;
+  request.extra_headers.SetHeader("cookie", std::string(2048, 'A'));
+
+  scoped_refptr<IOBuffer> read_buffer(new IOBuffer(kReadBufferSize));
+  std::unique_ptr<DeleteStreamDelegate> delegate(new DeleteStreamDelegate(
+      read_buffer.get(), kReadBufferSize, DeleteStreamDelegate::ON_FAILED));
+  delegate->DoNotSendRequestHeadersAutomatically();
+  delegate->Start(&request, net_log().bound(), session()->CreateHandle());
+  ConfirmHandshake();
+  delegate->WaitUntilNextCallback(kOnStreamReady);
+
+  // Attempt to send the headers and data.
+  const char kBody1[] = "here are some data";
+  const char kBody2[] = "data keep coming";
+  scoped_refptr<StringIOBuffer> buf1(new StringIOBuffer(kBody1));
+  scoped_refptr<StringIOBuffer> buf2(new StringIOBuffer(kBody2));
+  std::vector<int> lengths = {buf1->size(), buf2->size()};
+  delegate->SendvData({buf1, buf2}, lengths, !kFin);
+
+  delegate->WaitUntilNextCallback(kOnFailed);
+}
+
 TEST_P(BidirectionalStreamQuicImplTest, PostRequest) {
   SetRequest("POST", "/", DEFAULT_PRIORITY);
   size_t spdy_request_headers_frame_length;
@@ -1520,7 +1589,7 @@
   // Server sends a Rst.
   ProcessPacket(ConstructServerRstStreamPacket(1));
 
-  EXPECT_TRUE(delegate->on_failed_called());
+  delegate->WaitUntilNextCallback(kOnFailed);
 
   TestCompletionCallback cb;
   EXPECT_THAT(delegate->ReadData(cb.callback()),
@@ -1584,7 +1653,7 @@
   // Server sends a Rst.
   ProcessPacket(ConstructServerRstStreamPacket(3));
 
-  EXPECT_TRUE(delegate->on_failed_called());
+  delegate->WaitUntilNextCallback(kOnFailed);
 
   EXPECT_THAT(delegate->ReadData(cb.callback()),
               IsError(ERR_QUIC_PROTOCOL_ERROR));
@@ -1638,7 +1707,7 @@
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
   session()->connection()->CloseConnection(
       QUIC_NO_ERROR, "test", ConnectionCloseBehavior::SILENT_CLOSE);
-  EXPECT_TRUE(delegate->on_failed_called());
+  delegate->WaitUntilNextCallback(kOnFailed);
 
   // Try to send data after OnFailed(), should not get called back.
   scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(kUploadData));
diff --git a/remoting/ios/app/remoting_view_controller.mm b/remoting/ios/app/remoting_view_controller.mm
index 1f28e5e..443311a 100644
--- a/remoting/ios/app/remoting_view_controller.mm
+++ b/remoting/ios/app/remoting_view_controller.mm
@@ -203,6 +203,13 @@
 
 - (void)didSelectCell:(HostCollectionViewCell*)cell
            completion:(void (^)())completionBlock {
+  if (![cell.hostInfo isOnline]) {
+    MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
+    message.text = @"Host is offline.";
+    [MDCSnackbarManager showMessage:message];
+    return;
+  }
+
   _client = [[RemotingClient alloc] init];
 
   [_remotingService.authentication
diff --git a/services/preferences/persistent_pref_store_factory.cc b/services/preferences/persistent_pref_store_factory.cc
index 587ac96..c9145fdc 100644
--- a/services/preferences/persistent_pref_store_factory.cc
+++ b/services/preferences/persistent_pref_store_factory.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "components/prefs/in_memory_pref_store.h"
 #include "components/prefs/json_pref_store.h"
 #include "components/prefs/pref_filter.h"
 #include "services/preferences/persistent_pref_store_impl.h"
@@ -44,6 +45,10 @@
             std::move(configuration->get_tracked_configuration()), worker_pool),
         std::move(on_initialized));
   }
+  if (configuration->is_incognito_configuration()) {
+    return base::MakeUnique<PersistentPrefStoreImpl>(
+        base::MakeRefCounted<InMemoryPrefStore>(), std::move(on_initialized));
+  }
   NOTREACHED();
   return nullptr;
 }
diff --git a/services/preferences/pref_store_manager_impl.cc b/services/preferences/pref_store_manager_impl.cc
index 288e649..9803ec5b 100644
--- a/services/preferences/pref_store_manager_impl.cc
+++ b/services/preferences/pref_store_manager_impl.cc
@@ -36,9 +36,13 @@
       base::ContainsValue(expected_pref_stores_, PrefValueStore::DEFAULT_STORE))
       << "expected_pref_stores must always include PrefValueStore::USER_STORE "
          "and PrefValueStore::DEFAULT_STORE.";
-  // The user store is not actually connected to in the implementation, but
-  // accessed directly.
+  // The user store is not actually registered or connected to in the
+  // implementation, but accessed directly.
   expected_pref_stores_.erase(PrefValueStore::USER_STORE);
+  DVLOG(1) << "Expecting " << expected_pref_stores_.size()
+           << " pref store(s) to register";
+  // This store is done in-process so it's already "registered":
+  DVLOG(1) << "Registering pref store: " << PrefValueStore::DEFAULT_STORE;
   registry_.AddInterface<prefs::mojom::PrefStoreConnector>(
       base::Bind(&PrefStoreManagerImpl::BindPrefStoreConnectorRequest,
                  base::Unretained(this)));
@@ -50,7 +54,10 @@
                  base::Unretained(this)));
 }
 
-PrefStoreManagerImpl::~PrefStoreManagerImpl() = default;
+PrefStoreManagerImpl::~PrefStoreManagerImpl() {
+  // For logging consistency:
+  DVLOG(1) << "Deregistering pref store: " << PrefValueStore::DEFAULT_STORE;
+}
 
 void PrefStoreManagerImpl::Register(PrefValueStore::PrefStoreType type,
                                     mojom::PrefStorePtr pref_store_ptr) {
@@ -79,6 +86,9 @@
     mojom::PrefRegistryPtr pref_registry,
     const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
     ConnectCallback callback) {
+  DVLOG(1) << "Will connect to "
+           << expected_pref_stores_.size() - already_connected_types.size()
+           << " pref store(s)";
   std::set<PrefValueStore::PrefStoreType> required_remote_types;
   for (auto type : expected_pref_stores_) {
     if (!base::ContainsValue(already_connected_types, type)) {
@@ -98,6 +108,19 @@
   for (auto type : remaining_remote_types) {
     pending_connections_[type].push_back(connection);
   }
+  if (!Initialized()) {
+    pending_incognito_connections_.push_back(connection);
+  } else if (incognito_connector_) {
+    connection->ProvideIncognitoConnector(incognito_connector_);
+  }
+}
+
+void PrefStoreManagerImpl::ConnectToUserPrefStore(
+    const std::vector<std::string>& observed_prefs,
+    mojom::PrefStoreConnector::ConnectToUserPrefStoreCallback callback) {
+  std::move(callback).Run(persistent_pref_store_->CreateConnection(
+      PersistentPrefStoreImpl::ObservedPrefs(observed_prefs.begin(),
+                                             observed_prefs.end())));
 }
 
 void PrefStoreManagerImpl::BindPrefStoreConnectorRequest(
@@ -129,6 +152,14 @@
     mojom::PersistentPrefStoreConfigurationPtr configuration) {
   DCHECK(!persistent_pref_store_);
 
+  if (configuration->is_incognito_configuration()) {
+    incognito_connector_ =
+        std::move(configuration->get_incognito_configuration()->connector);
+    for (auto connection : pending_incognito_connections_) {
+      connection->ProvideIncognitoConnector(incognito_connector_);
+    }
+  }
+  pending_incognito_connections_.clear();
   persistent_pref_store_ = CreatePersistentPrefStore(
       std::move(configuration), worker_pool_.get(),
       base::Bind(&PrefStoreManagerImpl::OnPersistentPrefStoreReady,
@@ -162,4 +193,8 @@
   pending_persistent_connections_.clear();
 }
 
+bool PrefStoreManagerImpl::Initialized() const {
+  return bool(persistent_pref_store_);
+}
+
 }  // namespace prefs
diff --git a/services/preferences/pref_store_manager_impl.h b/services/preferences/pref_store_manager_impl.h
index c1b0ae83..5e7c4f4 100644
--- a/services/preferences/pref_store_manager_impl.h
+++ b/services/preferences/pref_store_manager_impl.h
@@ -54,13 +54,18 @@
   void Register(PrefValueStore::PrefStoreType type,
                 mojom::PrefStorePtr pref_store_ptr) override;
 
-  // mojom::PrefStoreConnector: |already_connected_types| must not include
-  // PrefValueStore::DEFAULT_STORE and PrefValueStore::USER_STORE as these must
-  // always be accessed through the service.
+  // mojom::PrefStoreConnector:
+  // |already_connected_types| must not include PrefValueStore::DEFAULT_STORE
+  // and PrefValueStore::USER_STORE as these must always be accessed through the
+  // service.
   void Connect(
       mojom::PrefRegistryPtr pref_registry,
       const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
       ConnectCallback callback) override;
+  void ConnectToUserPrefStore(
+      const std::vector<std::string>& observed_prefs,
+      mojom::PrefStoreConnector::ConnectToUserPrefStoreCallback callback)
+      override;
 
   void BindPrefStoreConnectorRequest(
       const service_manager::BindSourceInfo& source_info,
@@ -86,6 +91,9 @@
 
   void OnPersistentPrefStoreReady();
 
+  // Has |Init| been called?
+  bool Initialized() const;
+
   // PrefStores that need to register before replying to any Connect calls. This
   // does not include the PersistentPrefStore, which is handled separately.
   std::set<PrefValueStore::PrefStoreType> expected_pref_stores_;
@@ -99,6 +107,8 @@
   std::unique_ptr<PersistentPrefStoreImpl> persistent_pref_store_;
   mojo::Binding<mojom::PrefServiceControl> init_binding_;
 
+  mojom::PrefStoreConnectorPtr incognito_connector_;
+
   const scoped_refptr<DefaultPrefStore> defaults_;
   const std::unique_ptr<PrefStoreImpl> defaults_wrapper_;
 
@@ -110,6 +120,8 @@
       pending_connections_;
   std::vector<scoped_refptr<ScopedPrefConnectionBuilder>>
       pending_persistent_connections_;
+  std::vector<scoped_refptr<ScopedPrefConnectionBuilder>>
+      pending_incognito_connections_;
 
   const scoped_refptr<base::SequencedWorkerPool> worker_pool_;
 
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.cc b/services/preferences/public/cpp/persistent_pref_store_client.cc
index baf9aa1a..ef9a633 100644
--- a/services/preferences/public/cpp/persistent_pref_store_client.cc
+++ b/services/preferences/public/cpp/persistent_pref_store_client.cc
@@ -73,7 +73,7 @@
 PersistentPrefStoreClient::PersistentPrefStoreClient(
     mojom::PersistentPrefStoreConnectionPtr connection)
     : weak_factory_(this) {
-  OnConnect(std::move(connection),
+  OnConnect(std::move(connection), mojom::PersistentPrefStoreConnection::New(),
             std::unordered_map<PrefValueStore::PrefStoreType,
                                prefs::mojom::PrefStoreConnectionPtr>());
 }
@@ -139,16 +139,18 @@
 
 PersistentPrefStore::PrefReadError PersistentPrefStoreClient::ReadPrefs() {
   mojom::PersistentPrefStoreConnectionPtr connection;
+  mojom::PersistentPrefStoreConnectionPtr incognito_connection;
   std::unordered_map<PrefValueStore::PrefStoreType,
                      prefs::mojom::PrefStoreConnectionPtr>
       other_pref_stores;
   mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_calls;
   bool success = connector_->Connect(SerializePrefRegistry(*pref_registry_),
                                      already_connected_types_, &connection,
-                                     &other_pref_stores);
+                                     &incognito_connection, &other_pref_stores);
   DCHECK(success);
   pref_registry_ = nullptr;
-  OnConnect(std::move(connection), std::move(other_pref_stores));
+  OnConnect(std::move(connection), std::move(incognito_connection),
+            std::move(other_pref_stores));
   return read_error_;
 }
 
@@ -188,6 +190,7 @@
 
 void PersistentPrefStoreClient::OnConnect(
     mojom::PersistentPrefStoreConnectionPtr connection,
+    mojom::PersistentPrefStoreConnectionPtr incognito_connection,
     std::unordered_map<PrefValueStore::PrefStoreType,
                        prefs::mojom::PrefStoreConnectionPtr>
         other_pref_stores) {
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.h b/services/preferences/public/cpp/persistent_pref_store_client.h
index 67849f42..c6f9de86 100644
--- a/services/preferences/public/cpp/persistent_pref_store_client.h
+++ b/services/preferences/public/cpp/persistent_pref_store_client.h
@@ -70,6 +70,7 @@
 
  private:
   void OnConnect(mojom::PersistentPrefStoreConnectionPtr connection,
+                 mojom::PersistentPrefStoreConnectionPtr incognito_connection,
                  std::unordered_map<PrefValueStore::PrefStoreType,
                                     prefs::mojom::PrefStoreConnectionPtr>
                      other_pref_stores);
diff --git a/services/preferences/public/cpp/pref_service_factory.cc b/services/preferences/public/cpp/pref_service_factory.cc
index 4c6b4c7..eda3a20d 100644
--- a/services/preferences/public/cpp/pref_service_factory.cc
+++ b/services/preferences/public/cpp/pref_service_factory.cc
@@ -5,6 +5,7 @@
 #include "services/preferences/public/cpp/pref_service_factory.h"
 
 #include "base/callback_helpers.h"
+#include "components/prefs/overlay_user_pref_store.h"
 #include "components/prefs/persistent_pref_store.h"
 #include "components/prefs/pref_notifier_impl.h"
 #include "components/prefs/pref_registry.h"
@@ -64,6 +65,7 @@
         local_layered_pref_stores,
     ConnectCallback callback,
     mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+    mojom::PersistentPrefStoreConnectionPtr incognito_connection,
     std::unordered_map<PrefValueStore::PrefStoreType,
                        mojom::PrefStoreConnectionPtr> connections) {
   scoped_refptr<PrefStore> managed_prefs = CreatePrefStoreClient(
@@ -86,13 +88,22 @@
   scoped_refptr<PersistentPrefStore> persistent_pref_store(
       new PersistentPrefStoreClient(
           std::move(persistent_pref_store_connection)));
+  // If in incognito mode, |persistent_pref_store| above will be a connection to
+  // an in-memory pref store and |incognito_connection| will refer to the
+  // underlying profile's user pref store.
+  scoped_refptr<PersistentPrefStore> user_pref_store =
+      incognito_connection
+          ? new OverlayUserPrefStore(
+                persistent_pref_store.get(),
+                new PersistentPrefStoreClient(std::move(incognito_connection)))
+          : persistent_pref_store;
   PrefNotifierImpl* pref_notifier = new PrefNotifierImpl();
   auto* pref_value_store = new PrefValueStore(
       managed_prefs.get(), supervised_user_prefs.get(), extension_prefs.get(),
-      command_line_prefs.get(), persistent_pref_store.get(),
-      recommended_prefs.get(), pref_registry->defaults().get(), pref_notifier);
+      command_line_prefs.get(), user_pref_store.get(), recommended_prefs.get(),
+      pref_registry->defaults().get(), pref_notifier);
   callback.Run(base::MakeUnique<::PrefService>(
-      pref_notifier, pref_value_store, persistent_pref_store.get(),
+      pref_notifier, pref_value_store, user_pref_store.get(),
       pref_registry.get(), base::Bind(&DoNothingHandleReadError), true));
   connector_ptr->reset();
 }
diff --git a/services/preferences/public/cpp/tracked/configuration.h b/services/preferences/public/cpp/tracked/configuration.h
index bbb0ebff..2b06c56 100644
--- a/services/preferences/public/cpp/tracked/configuration.h
+++ b/services/preferences/public/cpp/tracked/configuration.h
@@ -5,7 +5,7 @@
 #ifndef SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_
 #define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_
 
-#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 
 namespace prefs {
 
diff --git a/services/preferences/public/cpp/tracked/mock_validation_delegate.h b/services/preferences/public/cpp/tracked/mock_validation_delegate.h
index 5538c693..44f9fb8 100644
--- a/services/preferences/public/cpp/tracked/mock_validation_delegate.h
+++ b/services/preferences/public/cpp/tracked/mock_validation_delegate.h
@@ -12,7 +12,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
-#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 #include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
 
 class MockValidationDelegate;
diff --git a/services/preferences/public/interfaces/BUILD.gn b/services/preferences/public/interfaces/BUILD.gn
index 8bc3749..58d8191f 100644
--- a/services/preferences/public/interfaces/BUILD.gn
+++ b/services/preferences/public/interfaces/BUILD.gn
@@ -7,7 +7,6 @@
 mojom("interfaces") {
   sources = [
     "preferences.mojom",
-    "preferences_configuration.mojom",
     "tracked_preference_validation_delegate.mojom",
   ]
   public_deps = [
diff --git a/services/preferences/public/interfaces/preferences.mojom b/services/preferences/public/interfaces/preferences.mojom
index eb44945..d195e240 100644
--- a/services/preferences/public/interfaces/preferences.mojom
+++ b/services/preferences/public/interfaces/preferences.mojom
@@ -4,8 +4,10 @@
 
 module prefs.mojom;
 
+import "mojo/common/file_path.mojom";
+import "mojo/common/string16.mojom";
 import "mojo/common/values.mojom";
-import "services/preferences/public/interfaces/preferences_configuration.mojom";
+import "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom";
 
 const string kServiceName = "preferences";
 const string kForwarderServiceName = "preferences_forwarder";
@@ -92,11 +94,24 @@
   // be received. The client asserts that it is already connected to the
   // |already_connected_types| pref stores through some other means, so the
   // Connect call will not connect to those.
+  //
+  // The returned |connection| is the connection to the main writable user pref
+  // store. If a |underlay| is returned any writes or reads not serviced by
+  // |connection| should be serviced by |underlay| instead (e.g. by using an
+  // |OverlayUserPrefStore|).
+  //
+  // Calls to |Connect| before |Init| are allowed and will cause the calls to
+  // queue and connect once |Init| has been called.
   [Sync]
   Connect(PrefRegistry pref_registry,
           array<PrefStoreType> already_connected_types) =>
       (PersistentPrefStoreConnection connection,
+       PersistentPrefStoreConnection? underlay,
        map<PrefStoreType, PrefStoreConnection> connections);
+
+  // Connect to the user pref store. Used for incognito.
+  ConnectToUserPrefStore(array<string> prefs_to_observe) =>
+      (PersistentPrefStoreConnection connection);
 };
 
 // An update to a subcomponent of a pref.
@@ -155,3 +170,65 @@
   // be used.
   Init(PersistentPrefStoreConfiguration configuration);
 };
+
+// ---------------------------------------------------------------------
+// Service Configuration
+
+union PersistentPrefStoreConfiguration {
+  SimplePersistentPrefStoreConfiguration simple_configuration;
+  TrackedPersistentPrefStoreConfiguration tracked_configuration;
+  IncognitoPersistentPrefStoreConfiguration incognito_configuration;
+};
+
+struct SimplePersistentPrefStoreConfiguration {
+  mojo.common.mojom.FilePath pref_filename;
+};
+
+// These parameters are passed to prefs::CreateTrackedPersistentPrefStore() in
+// services/preferences/persistent_pref_store_factory.cc.
+struct TrackedPersistentPrefStoreConfiguration {
+  mojo.common.mojom.FilePath unprotected_pref_filename;
+  mojo.common.mojom.FilePath protected_pref_filename;
+  array<TrackedPreferenceMetadata> tracking_configuration;
+  uint64 reporting_ids_count;
+  string seed;
+  string legacy_device_id;
+  string registry_seed;
+  mojo.common.mojom.String16 registry_path;
+  TrackedPreferenceValidationDelegate? validation_delegate;
+  ResetOnLoadObserver? reset_on_load_observer;
+};
+
+struct TrackedPreferenceMetadata {
+  enum EnforcementLevel { NO_ENFORCEMENT, ENFORCE_ON_LOAD };
+
+  enum PrefTrackingStrategy {
+    // Atomic preferences are tracked as a whole.
+    ATOMIC,
+    // Split preferences are dictionaries for which each top-level entry is
+    // tracked independently. Note: preferences using this strategy must be kept
+    // in sync with TrackedSplitPreferences in histograms.xml.
+    SPLIT,
+  };
+
+  enum ValueType {
+    IMPERSONAL,
+    // The preference value may contain personal information.
+    PERSONAL,
+  };
+
+  uint64 reporting_id;
+  string name;
+  EnforcementLevel enforcement_level;
+  PrefTrackingStrategy strategy;
+  ValueType value_type;
+};
+
+interface ResetOnLoadObserver {
+  OnResetOnLoad();
+};
+
+struct IncognitoPersistentPrefStoreConfiguration {
+  // A connector for the underlying profile's prefs.
+  PrefStoreConnector connector;
+};
diff --git a/services/preferences/public/interfaces/preferences_configuration.mojom b/services/preferences/public/interfaces/preferences_configuration.mojom
deleted file mode 100644
index 76f7785..0000000
--- a/services/preferences/public/interfaces/preferences_configuration.mojom
+++ /dev/null
@@ -1,62 +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.
-
-module prefs.mojom;
-
-import "mojo/common/file_path.mojom";
-import "mojo/common/string16.mojom";
-import "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom";
-
-union PersistentPrefStoreConfiguration {
-  SimplePersistentPrefStoreConfiguration simple_configuration;
-  TrackedPersistentPrefStoreConfiguration tracked_configuration;
-};
-
-struct SimplePersistentPrefStoreConfiguration {
-  mojo.common.mojom.FilePath pref_filename;
-};
-
-// These parameters are passed to prefs::CreateTrackedPersistentPrefStore() in
-// services/preferences/persistent_pref_store_factory.cc.
-struct TrackedPersistentPrefStoreConfiguration {
-  mojo.common.mojom.FilePath unprotected_pref_filename;
-  mojo.common.mojom.FilePath protected_pref_filename;
-  array<TrackedPreferenceMetadata> tracking_configuration;
-  uint64 reporting_ids_count;
-  string seed;
-  string legacy_device_id;
-  string registry_seed;
-  mojo.common.mojom.String16 registry_path;
-  TrackedPreferenceValidationDelegate? validation_delegate;
-  ResetOnLoadObserver? reset_on_load_observer;
-};
-
-struct TrackedPreferenceMetadata {
-  enum EnforcementLevel { NO_ENFORCEMENT, ENFORCE_ON_LOAD };
-
-  enum PrefTrackingStrategy {
-    // Atomic preferences are tracked as a whole.
-    ATOMIC,
-    // Split preferences are dictionaries for which each top-level entry is
-    // tracked independently. Note: preferences using this strategy must be kept
-    // in sync with TrackedSplitPreferences in histograms.xml.
-    SPLIT,
-  };
-
-  enum ValueType {
-    IMPERSONAL,
-    // The preference value may contain personal information.
-    PERSONAL,
-  };
-
-  uint64 reporting_id;
-  string name;
-  EnforcementLevel enforcement_level;
-  PrefTrackingStrategy strategy;
-  ValueType value_type;
-};
-
-interface ResetOnLoadObserver {
-  OnResetOnLoad();
-};
diff --git a/services/preferences/scoped_pref_connection_builder.cc b/services/preferences/scoped_pref_connection_builder.cc
index 8e6af17..c279b7c0 100644
--- a/services/preferences/scoped_pref_connection_builder.cc
+++ b/services/preferences/scoped_pref_connection_builder.cc
@@ -46,8 +46,16 @@
                                              observed_prefs_.end()));
 }
 
+void ScopedPrefConnectionBuilder::ProvideIncognitoConnector(
+    const mojom::PrefStoreConnectorPtr& incognito_connector) {
+  incognito_connector->ConnectToUserPrefStore(
+      observed_prefs_,
+      base::Bind(&ScopedPrefConnectionBuilder::OnIncognitoConnect, this));
+}
+
 ScopedPrefConnectionBuilder::~ScopedPrefConnectionBuilder() {
   std::move(callback_).Run(std::move(persistent_pref_store_connection_),
+                           std::move(incognito_connection_),
                            std::move(connections_));
 }
 
@@ -58,4 +66,10 @@
   DCHECK(inserted);
 }
 
+void ScopedPrefConnectionBuilder::OnIncognitoConnect(
+    mojom::PersistentPrefStoreConnectionPtr connection_ptr) {
+  DCHECK(!incognito_connection_);
+  incognito_connection_ = std::move(connection_ptr);
+}
+
 }  // namespace prefs
diff --git a/services/preferences/scoped_pref_connection_builder.h b/services/preferences/scoped_pref_connection_builder.h
index c73e97e..71d38f41 100644
--- a/services/preferences/scoped_pref_connection_builder.h
+++ b/services/preferences/scoped_pref_connection_builder.h
@@ -40,6 +40,9 @@
   void ProvidePersistentPrefStore(
       PersistentPrefStoreImpl* persistent_pref_store);
 
+  void ProvideIncognitoConnector(
+      const mojom::PrefStoreConnectorPtr& incognito_connector);
+
  private:
   friend class base::RefCounted<ScopedPrefConnectionBuilder>;
   ~ScopedPrefConnectionBuilder();
@@ -47,6 +50,9 @@
   void OnConnect(PrefValueStore::PrefStoreType type,
                  mojom::PrefStoreConnectionPtr connection_ptr);
 
+  void OnIncognitoConnect(
+      mojom::PersistentPrefStoreConnectionPtr connection_ptr);
+
   mojom::PrefStoreConnector::ConnectCallback callback_;
   std::vector<std::string> observed_prefs_;
 
@@ -57,6 +63,7 @@
       connections_;
 
   mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection_;
+  mojom::PersistentPrefStoreConnectionPtr incognito_connection_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedPrefConnectionBuilder);
 };
diff --git a/services/preferences/tracked/pref_hash_filter.h b/services/preferences/tracked/pref_hash_filter.h
index 4c3cdaa4..6467eb10 100644
--- a/services/preferences/tracked/pref_hash_filter.h
+++ b/services/preferences/tracked/pref_hash_filter.h
@@ -18,7 +18,7 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 #include "services/preferences/tracked/hash_store_contents.h"
 #include "services/preferences/tracked/interceptable_pref_filter.h"
 #include "services/preferences/tracked/tracked_preference.h"
diff --git a/services/preferences/tracked/tracked_persistent_pref_store_factory.h b/services/preferences/tracked/tracked_persistent_pref_store_factory.h
index fb426bab..e50d1278 100644
--- a/services/preferences/tracked/tracked_persistent_pref_store_factory.h
+++ b/services/preferences/tracked/tracked_persistent_pref_store_factory.h
@@ -6,7 +6,7 @@
 #define SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_
 
 #include <utility>
-#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
 
 namespace base {
 class DictionaryValue;
diff --git a/services/ui/ws/BUILD.gn b/services/ui/ws/BUILD.gn
index 0f93d30..46e503c 100644
--- a/services/ui/ws/BUILD.gn
+++ b/services/ui/ws/BUILD.gn
@@ -48,6 +48,7 @@
     "event_matcher.h",
     "event_targeter.cc",
     "event_targeter.h",
+    "event_targeter_delegate.h",
     "focus_controller.cc",
     "focus_controller.h",
     "focus_controller_delegate.h",
diff --git a/services/ui/ws/event_dispatcher.cc b/services/ui/ws/event_dispatcher.cc
index c6e6897..5634221 100644
--- a/services/ui/ws/event_dispatcher.cc
+++ b/services/ui/ws/event_dispatcher.cc
@@ -47,8 +47,7 @@
       capture_window_client_id_(kInvalidClientId),
       modal_window_controller_(this),
       event_targeter_(
-          base::MakeUnique<EventTargeter>(delegate_,
-                                          &modal_window_controller_)),
+          base::MakeUnique<EventTargeter>(this, &modal_window_controller_)),
       mouse_button_down_(false),
       mouse_cursor_source_window_(nullptr),
       mouse_cursor_in_non_client_area_(false) {}
@@ -317,6 +316,12 @@
   return;
 }
 
+ServerWindow* EventDispatcher::GetRootWindowContaining(
+    gfx::Point* location_in_display,
+    int64_t* display_id) {
+  return delegate_->GetRootWindowContaining(location_in_display, display_id);
+}
+
 void EventDispatcher::SetMouseCursorSourceWindow(ServerWindow* window) {
   if (mouse_cursor_source_window_ == window)
     return;
diff --git a/services/ui/ws/event_dispatcher.h b/services/ui/ws/event_dispatcher.h
index 193786b..1da07968 100644
--- a/services/ui/ws/event_dispatcher.h
+++ b/services/ui/ws/event_dispatcher.h
@@ -17,6 +17,7 @@
 #include "services/ui/public/interfaces/window_manager.mojom.h"
 #include "services/ui/ws/drag_cursor_updater.h"
 #include "services/ui/ws/event_targeter.h"
+#include "services/ui/ws/event_targeter_delegate.h"
 #include "services/ui/ws/modal_window_controller.h"
 #include "services/ui/ws/server_window_observer.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -41,7 +42,9 @@
 }
 
 // Handles dispatching events to the right location as well as updating focus.
-class EventDispatcher : public ServerWindowObserver, public DragCursorUpdater {
+class EventDispatcher : public ServerWindowObserver,
+                        public DragCursorUpdater,
+                        public EventTargeterDelegate {
  public:
   enum class AcceleratorMatchPhase {
     // Both pre and post should be considered.
@@ -148,6 +151,10 @@
                     const int64_t display_id,
                     AcceleratorMatchPhase match_phase);
 
+  // EventTargeterDelegate:
+  ServerWindow* GetRootWindowContaining(gfx::Point* location_in_display,
+                                        int64_t* display_id) override;
+
  private:
   friend class test::EventDispatcherTestApi;
 
diff --git a/services/ui/ws/event_targeter.cc b/services/ui/ws/event_targeter.cc
index 9329b4b..7e1f3a4 100644
--- a/services/ui/ws/event_targeter.cc
+++ b/services/ui/ws/event_targeter.cc
@@ -4,7 +4,7 @@
 
 #include "services/ui/ws/event_targeter.h"
 
-#include "services/ui/ws/event_dispatcher_delegate.h"
+#include "services/ui/ws/event_targeter_delegate.h"
 #include "services/ui/ws/modal_window_controller.h"
 #include "services/ui/ws/window_finder.h"
 #include "ui/events/event.h"
@@ -13,9 +13,9 @@
 namespace ui {
 namespace ws {
 
-EventTargeter::EventTargeter(EventDispatcherDelegate* event_dispatcher_delegate,
+EventTargeter::EventTargeter(EventTargeterDelegate* event_targeter_delegate,
                              ModalWindowController* modal_window_controller)
-    : event_dispatcher_delegate_(event_dispatcher_delegate),
+    : event_targeter_delegate_(event_targeter_delegate),
       modal_window_controller_(modal_window_controller) {}
 
 EventTargeter::~EventTargeter() {}
@@ -41,7 +41,7 @@
     gfx::Point* location,
     int64_t* display_id) {
   ServerWindow* root =
-      event_dispatcher_delegate_->GetRootWindowContaining(location, display_id);
+      event_targeter_delegate_->GetRootWindowContaining(location, display_id);
   return root ? ui::ws::FindDeepestVisibleWindowForEvents(root, *location)
               : DeepestWindow();
 }
diff --git a/services/ui/ws/event_targeter.h b/services/ui/ws/event_targeter.h
index a59aba2..91dfaf3 100644
--- a/services/ui/ws/event_targeter.h
+++ b/services/ui/ws/event_targeter.h
@@ -18,7 +18,7 @@
 
 namespace ws {
 struct DeepestWindow;
-class EventDispatcherDelegate;
+class EventTargeterDelegate;
 class ModalWindowController;
 class ServerWindow;
 
@@ -46,7 +46,7 @@
 // Finds the PointerTarget for an event or the DeepestWindow for a location.
 class EventTargeter {
  public:
-  EventTargeter(EventDispatcherDelegate* event_dispatcher_delegate,
+  EventTargeter(EventTargeterDelegate* event_targeter_delegate,
                 ModalWindowController* modal_window_controller);
   ~EventTargeter();
 
@@ -62,7 +62,7 @@
                                                   int64_t* display_id);
 
  private:
-  EventDispatcherDelegate* event_dispatcher_delegate_;
+  EventTargeterDelegate* event_targeter_delegate_;
   ModalWindowController* modal_window_controller_;
 
   DISALLOW_COPY_AND_ASSIGN(EventTargeter);
diff --git a/services/ui/ws/event_targeter_delegate.h b/services/ui/ws/event_targeter_delegate.h
new file mode 100644
index 0000000..2e46860
--- /dev/null
+++ b/services/ui/ws/event_targeter_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_UI_WS_EVENT_TARGETER_DELEGATE_H_
+#define SERVICES_UI_WS_EVENT_TARGETER_DELEGATE_H_
+
+#include <stdint.h>
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+namespace ws {
+class ServerWindow;
+
+// Used by EventTargeter to talk to WindowManagerState.
+class EventTargeterDelegate {
+ public:
+  // Calls EventDispatcherDelegate::GetRootWindowContaining, see
+  // event_dispatcher_delegate.h for details.
+  virtual ServerWindow* GetRootWindowContaining(gfx::Point* location_in_display,
+                                                int64_t* display_id) = 0;
+
+ protected:
+  virtual ~EventTargeterDelegate() {}
+};
+
+}  // namespace ws
+}  // namespace ui
+
+#endif  // SERVICES_UI_WS_EVENT_TARGETER_DELEGATE_H_
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index e73c1d63..6cf6bfc 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -1503,6 +1503,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "webkit_unit_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "wm_unittests"
       }
     ]
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
index 226c642d..7fc09aec 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
@@ -33,10 +33,10 @@
         function dump()
         {
             TestRunner.addResult("Query:" + JSON.stringify(filteredSelectionDialog._value()));
-            var list = filteredSelectionDialog._list;
+            var items = filteredSelectionDialog._items;
             var output = [];
-            for (var i = 0; i < list.length(); ++i)
-                output.push(provider.itemKeyAt(list.itemAtIndex(i)));
+            for (var i = 0; i < items.length(); ++i)
+                output.push(provider.itemKeyAt(items.itemAtIndex(i)));
             TestRunner.addResult("Output:" + JSON.stringify(output));
         }
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-list-widget-providers.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-list-widget-providers.js
index 44774ddb..13b4d98 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-list-widget-providers.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-list-widget-providers.js
@@ -42,18 +42,18 @@
   }
 
   function dump() {
-    var list = filteredListWidget._list;
     if (filteredListWidget._bottomElementsContainer.classList.contains('hidden')) {
       TestRunner.addResult('Output: <hidden>');
       return;
     }
-    if (list.element.classList.contains('hidden')) {
+    if (filteredListWidget._list.element.classList.contains('hidden')) {
       TestRunner.addResult('Output: ' + filteredListWidget._notFoundElement.textContent);
       return;
     }
+    var items = filteredListWidget._items;
     var output = [];
-    for (var i = 0; i < list.length(); ++i)
-      output.push(filteredListWidget._provider.itemKeyAt(list.itemAtIndex(i)));
+    for (var i = 0; i < items.length(); ++i)
+      output.push(filteredListWidget._provider.itemKeyAt(items.itemAtIndex(i)));
     TestRunner.addResult('Output:' + JSON.stringify(output));
   }
 }
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height-expected.txt
index 5fd0dcd..ec113cad 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height-expected.txt
@@ -642,4 +642,34 @@
 *[0] 29
 *[15] bottom
 
+Replacing model with [5-7]
+Creating element for 5
+Creating element for 6
+Creating element for 7
+----list[length=1][height=84]----
+ [0] top
+*[0] 5
+*[15] 6
+*[30] 7
+*[45] bottom
+
+Pushing 8
+Creating element for 8
+----list[length=1][height=84]----
+ [0] top
+*[0] 5
+*[15] 6
+*[30] 7
+*[45] 8
+*[60] bottom
+
+Pushing 9 to old model
+----list[length=2][height=84]----
+ [0] top
+*[0] 5
+*[15] 6
+*[30] 7
+*[45] 8
+*[60] bottom
+
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height.js
index 189faf0..67b85b7 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-equal-height.js
@@ -31,14 +31,15 @@
 }
 
 var delegate = new Delegate();
-var list = new UI.ListControl(delegate, UI.ListMode.EqualHeightItems);
+var model = new UI.ListModel();
+var list = new UI.ListControl(model, delegate, UI.ListMode.EqualHeightItems);
 list.element.style.height = '73px';
 UI.inspectorView.element.appendChild(list.element);
 
 function dumpList()
 {
   var height = list.element.offsetHeight;
-  TestRunner.addResult(`----list[length=${list.length()}][height=${height}]----`);
+  TestRunner.addResult(`----list[length=${model.length()}][height=${height}]----`);
   for (var child of list.element.children) {
     var offsetTop = child.getBoundingClientRect().top - list.element.getBoundingClientRect().top;
     var offsetBottom = child.getBoundingClientRect().bottom - list.element.getBoundingClientRect().top;
@@ -52,7 +53,7 @@
 }
 
 TestRunner.addResult('Adding 0, 1, 2');
-list.replaceAllItems([0, 1, 2]);
+model.replaceAllItems([0, 1, 2]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 0');
@@ -84,7 +85,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 0 with 5, 6, 7');
-list.replaceItemsInRange(0, 1, [5, 6, 7]);
+model.replaceItemsInRange(0, 1, [5, 6, 7]);
 dumpList();
 
 TestRunner.addResult('ArrowUp');
@@ -92,7 +93,7 @@
 dumpList();
 
 TestRunner.addResult('Pushing 10');
-list.pushItem(10);
+model.pushItem(10);
 dumpList();
 
 TestRunner.addResult('Selecting 10');
@@ -100,19 +101,19 @@
 dumpList();
 
 TestRunner.addResult('Popping 10');
-list.popItem();
+model.popItem();
 dumpList();
 
 TestRunner.addResult('Removing 2');
-list.removeItemAtIndex(4);
+model.removeItemAtIndex(4);
 dumpList();
 
 TestRunner.addResult('Inserting 8');
-list.insertItemAtIndex(1, 8);
+model.insertItemAtIndex(1, 8);
 dumpList();
 
 TestRunner.addResult('Replacing with 0...20');
-list.replaceAllItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+model.replaceAllItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
 dumpList();
 
 TestRunner.addResult('Resizing');
@@ -146,11 +147,11 @@
 dumpList();
 
 TestRunner.addResult('Replacing 7 with 27');
-list.replaceItemsInRange(7, 8, [27]);
+model.replaceItemsInRange(7, 8, [27]);
 dumpList();
 
 TestRunner.addResult('Replacing 18, 19 with 28, 29');
-list.replaceItemsInRange(18, 20, [28, 29]);
+model.replaceItemsInRange(18, 20, [28, 29]);
 dumpList();
 
 TestRunner.addResult('PageDown');
@@ -158,7 +159,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 1, 2, 3 with [31-43]');
-list.replaceItemsInRange(1, 4, [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]);
+model.replaceItemsInRange(1, 4, [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 13 (center)');
@@ -194,11 +195,24 @@
 dumpList();
 
 TestRunner.addResult('Replacing all but 29 with []');
-list.replaceItemsInRange(0, 29, []);
+model.replaceItemsInRange(0, 29, []);
 dumpList();
 
 TestRunner.addResult('ArrowDown');
 list._onKeyDown(TestRunner.createKeyEvent('ArrowDown'));
 dumpList();
 
+var newModel = new UI.ListModel([5, 6, 7]);
+TestRunner.addResult('Replacing model with [5-7]');
+list.setModel(newModel);
+dumpList();
+
+TestRunner.addResult('Pushing 8');
+newModel.pushItem(8);
+dumpList();
+
+TestRunner.addResult('Pushing 9 to old model');
+model.pushItem(9);
+dumpList();
+
 TestRunner.completeTest();
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-non-viewport.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-non-viewport.js
index b525b1c..fe6e78e 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-non-viewport.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-non-viewport.js
@@ -31,13 +31,14 @@
 }
 
 var delegate = new Delegate();
-var list = new UI.ListControl(delegate, UI.ListMode.NonViewport);
+var model = new UI.ListModel();
+var list = new UI.ListControl(model, delegate, UI.ListMode.NonViewport);
 UI.inspectorView.element.appendChild(list.element);
 
 function dumpList()
 {
   var height = list.element.offsetHeight;
-  TestRunner.addResult(`----list[length=${list.length()}][height=${height}]----`);
+  TestRunner.addResult(`----list[length=${model.length()}][height=${height}]----`);
   for (var child of list.element.children) {
     var offsetTop = child.getBoundingClientRect().top - list.element.getBoundingClientRect().top;
     var offsetBottom = child.getBoundingClientRect().bottom - list.element.getBoundingClientRect().top;
@@ -51,7 +52,7 @@
 }
 
 TestRunner.addResult('Adding 0, 1, 2');
-list.replaceAllItems([0, 1, 2]);
+model.replaceAllItems([0, 1, 2]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 0');
@@ -63,7 +64,7 @@
 dumpList();
 
 TestRunner.addResult('Adding 3-20');
-list.replaceItemsInRange(3, 3, [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+model.replaceItemsInRange(3, 3, [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 19');
@@ -75,7 +76,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 0, 1 with 25-36');
-list.replaceItemsInRange(0, 2, [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]);
+model.replaceItemsInRange(0, 2, [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 18');
@@ -83,11 +84,11 @@
 dumpList();
 
 TestRunner.addResult('Replacing 25-36 with 0-1');
-list.replaceItemsInRange(0, 12, [0, 1]);
+model.replaceItemsInRange(0, 12, [0, 1]);
 dumpList();
 
 TestRunner.addResult('Replacing 16-18 with 45');
-list.replaceItemsInRange(16, 19, [45]);
+model.replaceItemsInRange(16, 19, [45]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 4');
@@ -95,7 +96,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 45 with 16-18');
-list.replaceItemsInRange(16, 17, [16, 17, 18]);
+model.replaceItemsInRange(16, 17, [16, 17, 18]);
 dumpList();
 
 TestRunner.completeTest();
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-various-height.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-various-height.js
index 85d24f25..763d01ef 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-various-height.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-various-height.js
@@ -30,14 +30,15 @@
 }
 
 var delegate = new Delegate();
-var list = new UI.ListControl(delegate, UI.ListMode.VariousHeightItems);
+var model = new UI.ListModel();
+var list = new UI.ListControl(model, delegate, UI.ListMode.VariousHeightItems);
 list.element.style.height = '73px';
 UI.inspectorView.element.appendChild(list.element);
 
 function dumpList()
 {
   var height = list.element.offsetHeight;
-  TestRunner.addResult(`----list[length=${list.length()}][height=${height}]----`);
+  TestRunner.addResult(`----list[length=${model.length()}][height=${height}]----`);
   for (var child of list.element.children) {
     var offsetTop = child.getBoundingClientRect().top - list.element.getBoundingClientRect().top;
     var offsetBottom = child.getBoundingClientRect().bottom - list.element.getBoundingClientRect().top;
@@ -52,7 +53,7 @@
 }
 
 TestRunner.addResult('Adding 0, 1, 2');
-list.replaceAllItems([0, 1, 2]);
+model.replaceAllItems([0, 1, 2]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 0');
@@ -64,7 +65,7 @@
 dumpList();
 
 TestRunner.addResult('Adding 3-20');
-list.replaceItemsInRange(3, 3, [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+model.replaceItemsInRange(3, 3, [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 11');
@@ -84,7 +85,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 0, 1 with 25-36');
-list.replaceItemsInRange(0, 2, [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]);
+model.replaceItemsInRange(0, 2, [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 18');
@@ -92,11 +93,11 @@
 dumpList();
 
 TestRunner.addResult('Replacing 25-36 with 0-1');
-list.replaceItemsInRange(0, 12, [0, 1]);
+model.replaceItemsInRange(0, 12, [0, 1]);
 dumpList();
 
 TestRunner.addResult('Replacing 16-18 with 45');
-list.replaceItemsInRange(16, 19, [45]);
+model.replaceItemsInRange(16, 19, [45]);
 dumpList();
 
 TestRunner.addResult('Scrolling to 4');
@@ -104,7 +105,7 @@
 dumpList();
 
 TestRunner.addResult('Replacing 45 with 16-18');
-list.replaceItemsInRange(16, 17, [16, 17, 18]);
+model.replaceItemsInRange(16, 17, [16, 17, 18]);
 dumpList();
 
 TestRunner.addResult('Resizing');
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model-expected.txt
new file mode 100644
index 0000000..a078ea0c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model-expected.txt
@@ -0,0 +1,46 @@
+Test ListModel API.
+Adding 0, 1, 2
+Replaced [] at index 0 with [0, 1, 2]
+Resulting list: [0, 1, 2]
+
+Replacing 0 with 5, 6, 7
+Replaced [0] at index 0 with [5, 6, 7]
+Resulting list: [5, 6, 7, 1, 2]
+
+Pushing 10
+Replaced [] at index 5 with [10]
+Resulting list: [5, 6, 7, 1, 2, 10]
+
+Popping 10
+Replaced [10] at index 5 with []
+Resulting list: [5, 6, 7, 1, 2]
+
+Removing 2
+Replaced [2] at index 4 with []
+Resulting list: [5, 6, 7, 1]
+
+Inserting 8
+Replaced [] at index 1 with [8]
+Resulting list: [5, 8, 6, 7, 1]
+
+Replacing with 0...20
+Replaced [5, 8, 6, 7, 1] at index 0 with [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
+Resulting list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
+
+Replacing 7 with 27
+Replaced [7] at index 7 with [27]
+Resulting list: [0, 1, 2, 3, 4, 5, 6, 27, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
+
+Replacing 18, 19 with 28, 29
+Replaced [18, 19] at index 18 with [28, 29]
+Resulting list: [0, 1, 2, 3, 4, 5, 6, 27, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29]
+
+Replacing 1, 2, 3 with [31-43]
+Replaced [1, 2, 3] at index 1 with [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]
+Resulting list: [0, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 4, 5, 6, 27, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29]
+
+Replacing all but 29 with []
+Replaced [0, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 4, 5, 6, 27, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 28] at index 0 with []
+Resulting list: [29]
+
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model.js
new file mode 100644
index 0000000..9fbf6c8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-model.js
@@ -0,0 +1,50 @@
+TestRunner.addResult('Test ListModel API.');
+
+var list = new UI.ListModel();
+list.addEventListener(UI.ListModel.Events.ItemsReplaced, event => {
+  var data = event.data;
+  var inserted = [];
+  for (var index = data.index; index < data.index + data.inserted; index++)
+    inserted.push(list.itemAtIndex(index));
+  TestRunner.addResult(`Replaced [${data.removed.join(', ')}] at index ${data.index} with [${inserted.join(', ')}]`);
+  var all = [];
+  for (var index = 0; index < list.length(); index++)
+    all.push(list.itemAtIndex(index));
+  TestRunner.addResult(`Resulting list: [${all.join(', ')}]`);
+  TestRunner.addResult('');
+});
+
+TestRunner.addResult('Adding 0, 1, 2');
+list.replaceAllItems([0, 1, 2]);
+
+TestRunner.addResult('Replacing 0 with 5, 6, 7');
+list.replaceItemsInRange(0, 1, [5, 6, 7]);
+
+TestRunner.addResult('Pushing 10');
+list.pushItem(10);
+
+TestRunner.addResult('Popping 10');
+list.popItem();
+
+TestRunner.addResult('Removing 2');
+list.removeItemAtIndex(4);
+
+TestRunner.addResult('Inserting 8');
+list.insertItemAtIndex(1, 8);
+
+TestRunner.addResult('Replacing with 0...20');
+list.replaceAllItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+
+TestRunner.addResult('Replacing 7 with 27');
+list.replaceItemsInRange(7, 8, [27]);
+
+TestRunner.addResult('Replacing 18, 19 with 28, 29');
+list.replaceItemsInRange(18, 20, [28, 29]);
+
+TestRunner.addResult('Replacing 1, 2, 3 with [31-43]');
+list.replaceItemsInRange(1, 4, [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]);
+
+TestRunner.addResult('Replacing all but 29 with []');
+list.replaceItemsInRange(0, 29, []);
+
+TestRunner.completeTest();
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js
index d23a82e..9210a99a 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js
@@ -395,7 +395,7 @@
 InspectorTest.changeExecutionContext = function(namePrefix)
 {
     var selector = Console.ConsoleView.instance()._consoleContextSelector;
-    for (var executionContext of selector._list._items) {
+    for (var executionContext of selector._items._items) {
         if (selector._titleFor(executionContext).startsWith(namePrefix)) {
             UI.context.setFlavor(SDK.ExecutionContext, executionContext);
             return;
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html
index 864a7e4..f94132f 100644
--- a/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html
+++ b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html
@@ -98,7 +98,7 @@
     var consoleView = Console.ConsoleView.instance();
     var selector = consoleView._consoleContextSelector;
     InspectorTest.addResult('Console context selector:');
-    for (var executionContext of selector._list._items) {
+    for (var executionContext of selector._items._items) {
       var selected = UI.context.flavor(SDK.ExecutionContext) === executionContext;
       var text = '____'.repeat(selector._depthFor(executionContext)) + selector._titleFor(executionContext);
       var disabled = !selector.isItemSelectable(executionContext);
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-change-variable.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-change-variable.html
index 3910f18..4cb7fb0 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-change-variable.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-change-variable.html
@@ -45,7 +45,7 @@
     function step2(callFrames)
     {
         var pane = self.runtime.sharedInstance(Sources.CallStackSidebarPane);
-        pane._list.selectItem(pane._list.itemAtIndex(1));
+        pane._list.selectItem(pane._list._model.itemAtIndex(1));
         InspectorTest.deprecatedRunAfterPendingDispatches(step3);
     }
 
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-on-call-frame-inside-iframe.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-on-call-frame-inside-iframe.html
index ba6754f..e4ced82 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-on-call-frame-inside-iframe.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-on-call-frame-inside-iframe.html
@@ -77,7 +77,7 @@
     function step3()
     {
         var pane = self.runtime.sharedInstance(Sources.CallStackSidebarPane);
-        pane._list.selectItem(pane._list.itemAtIndex(1));
+        pane._list.selectItem(pane._list._model.itemAtIndex(1));
         InspectorTest.deprecatedRunAfterPendingDispatches(step4);
     }
 
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-while-paused.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-while-paused.html
index 7be8554..a4702a5 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-while-paused.html
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger-pause/debugger-eval-while-paused.html
@@ -37,7 +37,7 @@
     {
         InspectorTest.addResult("Evaluated script on the top frame: " + result);
         var pane = self.runtime.sharedInstance(Sources.CallStackSidebarPane);
-        pane._list.selectItem(pane._list.itemAtIndex(1));
+        pane._list.selectItem(pane._list._model.itemAtIndex(1));
         InspectorTest.deprecatedRunAfterPendingDispatches(step4);
     }
 
diff --git a/third_party/WebKit/Source/core/editing/BUILD.gn b/third_party/WebKit/Source/core/editing/BUILD.gn
index f6cb0331..c69f5a0 100644
--- a/third_party/WebKit/Source/core/editing/BUILD.gn
+++ b/third_party/WebKit/Source/core/editing/BUILD.gn
@@ -216,6 +216,7 @@
     "markers/SpellingMarker.h",
     "markers/SpellingMarkerListImpl.cpp",
     "markers/SpellingMarkerListImpl.h",
+    "markers/TextMatchMarker.cpp",
     "markers/TextMatchMarker.h",
     "markers/TextMatchMarkerListImpl.cpp",
     "markers/TextMatchMarkerListImpl.h",
diff --git a/third_party/WebKit/Source/core/editing/markers/CompositionMarker.cpp b/third_party/WebKit/Source/core/editing/markers/CompositionMarker.cpp
index f445f50..f24217d 100644
--- a/third_party/WebKit/Source/core/editing/markers/CompositionMarker.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/CompositionMarker.cpp
@@ -11,11 +11,15 @@
                                      Color underline_color,
                                      bool thick,
                                      Color background_color)
-    : DocumentMarker(DocumentMarker::kComposition, start_offset, end_offset),
+    : DocumentMarker(start_offset, end_offset),
       underline_color_(underline_color),
       background_color_(background_color),
       thick_(thick) {}
 
+DocumentMarker::MarkerType CompositionMarker::GetType() const {
+  return DocumentMarker::kComposition;
+}
+
 Color CompositionMarker::UnderlineColor() const {
   return underline_color_;
 }
diff --git a/third_party/WebKit/Source/core/editing/markers/CompositionMarker.h b/third_party/WebKit/Source/core/editing/markers/CompositionMarker.h
index 0b02e62..fa476cb 100644
--- a/third_party/WebKit/Source/core/editing/markers/CompositionMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/CompositionMarker.h
@@ -22,6 +22,10 @@
                     bool thick,
                     Color background_color);
 
+  // DocumentMarker implementations
+  MarkerType GetType() const final;
+
+  // CompositionMarker-specific
   Color UnderlineColor() const;
   bool Thick() const;
   Color BackgroundColor() const;
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.cpp
index 7ecc322..3a43d7aa 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.cpp
@@ -37,10 +37,8 @@
 
 DocumentMarker::~DocumentMarker() = default;
 
-DocumentMarker::DocumentMarker(MarkerType type,
-                               unsigned start_offset,
-                               unsigned end_offset)
-    : type_(type), start_offset_(start_offset), end_offset_(end_offset) {
+DocumentMarker::DocumentMarker(unsigned start_offset, unsigned end_offset)
+    : start_offset_(start_offset), end_offset_(end_offset) {
   DCHECK_LT(start_offset_, end_offset_);
 }
 
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
index 0ab8fa70..7440dae 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarker.h
@@ -129,7 +129,7 @@
 
   virtual ~DocumentMarker();
 
-  MarkerType GetType() const { return type_; }
+  virtual MarkerType GetType() const = 0;
   unsigned StartOffset() const { return start_offset_; }
   unsigned EndOffset() const { return end_offset_; }
 
@@ -151,10 +151,9 @@
   DEFINE_INLINE_VIRTUAL_TRACE() {}
 
  protected:
-  DocumentMarker(MarkerType, unsigned start_offset, unsigned end_offset);
+  DocumentMarker(unsigned start_offset, unsigned end_offset);
 
  private:
-  const MarkerType type_;
   unsigned start_offset_;
   unsigned end_offset_;
 
diff --git a/third_party/WebKit/Source/core/editing/markers/GrammarMarker.cpp b/third_party/WebKit/Source/core/editing/markers/GrammarMarker.cpp
index 32cfbb6..eadeee2f 100644
--- a/third_party/WebKit/Source/core/editing/markers/GrammarMarker.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/GrammarMarker.cpp
@@ -9,11 +9,12 @@
 GrammarMarker::GrammarMarker(unsigned start_offset,
                              unsigned end_offset,
                              const String& description)
-    : SpellCheckMarker(DocumentMarker::kGrammar,
-                       start_offset,
-                       end_offset,
-                       description) {
+    : SpellCheckMarker(start_offset, end_offset, description) {
   DCHECK_LT(start_offset, end_offset);
 }
 
+DocumentMarker::MarkerType GrammarMarker::GetType() const {
+  return DocumentMarker::kGrammar;
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/editing/markers/GrammarMarker.h b/third_party/WebKit/Source/core/editing/markers/GrammarMarker.h
index 6bf39c5..17c526a 100644
--- a/third_party/WebKit/Source/core/editing/markers/GrammarMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/GrammarMarker.h
@@ -20,6 +20,9 @@
                 const String& description);
 
  private:
+  // DocumentMarker implementations
+  MarkerType GetType() const final;
+
   DISALLOW_COPY_AND_ASSIGN(GrammarMarker);
 };
 
diff --git a/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.cpp b/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.cpp
index 04952394..3a1cb2d 100644
--- a/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.cpp
@@ -6,12 +6,10 @@
 
 namespace blink {
 
-SpellCheckMarker::SpellCheckMarker(DocumentMarker::MarkerType type,
-                                   unsigned start_offset,
+SpellCheckMarker::SpellCheckMarker(unsigned start_offset,
                                    unsigned end_offset,
                                    const String& description)
-    : DocumentMarker(type, start_offset, end_offset),
-      description_(description) {
+    : DocumentMarker(start_offset, end_offset), description_(description) {
   DCHECK_LT(start_offset, end_offset);
 }
 
diff --git a/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.h b/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.h
index 053f7c5..07ca3e6 100644
--- a/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/SpellCheckMarker.h
@@ -15,8 +15,7 @@
 // or grammar error.
 class CORE_EXPORT SpellCheckMarker : public DocumentMarker {
  public:
-  SpellCheckMarker(DocumentMarker::MarkerType,
-                   unsigned start_offset,
+  SpellCheckMarker(unsigned start_offset,
                    unsigned end_offset,
                    const String& description);
 
diff --git a/third_party/WebKit/Source/core/editing/markers/SpellingMarker.cpp b/third_party/WebKit/Source/core/editing/markers/SpellingMarker.cpp
index fa0b1b1b..0d7d1100 100644
--- a/third_party/WebKit/Source/core/editing/markers/SpellingMarker.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/SpellingMarker.cpp
@@ -9,11 +9,12 @@
 SpellingMarker::SpellingMarker(unsigned start_offset,
                                unsigned end_offset,
                                const String& description)
-    : SpellCheckMarker(DocumentMarker::kSpelling,
-                       start_offset,
-                       end_offset,
-                       description) {
+    : SpellCheckMarker(start_offset, end_offset, description) {
   DCHECK_LT(start_offset, end_offset);
 }
 
+DocumentMarker::MarkerType SpellingMarker::GetType() const {
+  return DocumentMarker::kSpelling;
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/editing/markers/SpellingMarker.h b/third_party/WebKit/Source/core/editing/markers/SpellingMarker.h
index d4dac785..f657926 100644
--- a/third_party/WebKit/Source/core/editing/markers/SpellingMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/SpellingMarker.h
@@ -20,6 +20,9 @@
                  const String& description);
 
  private:
+  // DocumentMarker implementations
+  MarkerType GetType() const final;
+
   DISALLOW_COPY_AND_ASSIGN(SpellingMarker);
 };
 
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.cpp b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.cpp
new file mode 100644
index 0000000..096b8a5e
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.cpp
@@ -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.
+
+#include "core/editing/markers/TextMatchMarker.h"
+
+namespace blink {
+
+DocumentMarker::MarkerType TextMatchMarker::GetType() const {
+  return DocumentMarker::kTextMatch;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
index 0b712782..e4c9fc2 100644
--- a/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
+++ b/third_party/WebKit/Source/core/editing/markers/TextMatchMarker.h
@@ -36,7 +36,7 @@
 // markers. We store whether or not the match is active, a LayoutRect used for
 // rendering the marker, and whether or not the LayoutRect is currently
 // up-to-date.
-class TextMatchMarker final : public DocumentMarker {
+class CORE_EXPORT TextMatchMarker final : public DocumentMarker {
  private:
   enum class State { kInvalid, kValidNull, kValidNotNull };
 
@@ -46,11 +46,14 @@
   TextMatchMarker(unsigned start_offset,
                   unsigned end_offset,
                   MatchStatus status)
-      : DocumentMarker(DocumentMarker::kTextMatch, start_offset, end_offset),
-        match_status_(status) {
+      : DocumentMarker(start_offset, end_offset), match_status_(status) {
     layout_state_ = State::kInvalid;
   }
 
+  // DocumentMarker implementations
+  MarkerType GetType() const final;
+
+  // TextMatchMarker-specific
   bool IsActiveMatch() const { return match_status_ == MatchStatus::kActive; }
   void SetIsActiveMatch(bool active) {
     match_status_ = active ? MatchStatus::kActive : MatchStatus::kInactive;
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 335b3f4..47296f2 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -680,6 +680,7 @@
   "front_end/ui/inspectorViewTabbedPane.css",
   "front_end/ui/KeyboardShortcut.js",
   "front_end/ui/ListControl.js",
+  "front_end/ui/ListModel.js",
   "front_end/ui/listWidget.css",
   "front_end/ui/ListWidget.js",
   "front_end/ui/module.json",
diff --git a/third_party/WebKit/Source/devtools/front_end/Images/whatsnew.png b/third_party/WebKit/Source/devtools/front_end/Images/whatsnew.png
index 9dbe93d..9bb608b8 100644
--- a/third_party/WebKit/Source/devtools/front_end/Images/whatsnew.png
+++ b/third_party/WebKit/Source/devtools/front_end/Images/whatsnew.png
Binary files differ
diff --git a/third_party/WebKit/Source/devtools/front_end/Tests.js b/third_party/WebKit/Source/devtools/front_end/Tests.js
index 5ce03ea..c09309dc 100644
--- a/third_party/WebKit/Source/devtools/front_end/Tests.js
+++ b/third_party/WebKit/Source/devtools/front_end/Tests.js
@@ -847,10 +847,10 @@
 
     function onExecutionContexts() {
       var consoleView = Console.ConsoleView.instance();
-      var items = consoleView._consoleContextSelector._list._items;
+      var items = consoleView._consoleContextSelector._items;
       var values = [];
-      for (var i = 0; i < items.length; ++i)
-        values.push(consoleView._consoleContextSelector._titleFor(items[i]));
+      for (var i = 0; i < items.length(); ++i)
+        values.push(consoleView._consoleContextSelector._titleFor(items.itemAtIndex(i)));
       test.assertEquals('top', values[0]);
       test.assertEquals('Simple content script', values[1]);
       test.releaseControl();
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 f6e003f..665bdda 100644
--- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
+++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
@@ -22,7 +22,10 @@
     this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom);
     this._glassPane.setOutsideClickCallback(this._hide.bind(this));
     this._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane);
-    this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems);
+    /** @type {!UI.ListModel<!SDK.ExecutionContext>} */
+    this._items = new UI.ListModel();
+    /** @type {!UI.ListControl<!SDK.ExecutionContext>} */
+    this._list = new UI.ListControl(this._items, this, UI.ListMode.EqualHeightItems);
     this._list.element.classList.add('context-list');
     this._rowHeight = 36;
     UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'console/consoleContextSelector.css')
@@ -96,7 +99,7 @@
   }
 
   _updateGlasspaneSize() {
-    var maxHeight = this._rowHeight * (Math.min(this._list.length(), 9));
+    var maxHeight = this._rowHeight * (Math.min(this._items.length(), 9));
     this._glassPane.setMaxContentSize(new UI.Size(315, maxHeight));
     this._list.viewportResized();
   }
@@ -127,7 +130,7 @@
         var currentExecutionContext = this._list.selectedItem();
         if (!currentExecutionContext)
           break;
-        var nextExecutionContext = this._list.itemAtIndex(this._list.selectedIndex() + 1);
+        var nextExecutionContext = this._items.itemAtIndex(this._list.selectedIndex() + 1);
         if (nextExecutionContext && this._depthFor(currentExecutionContext) < this._depthFor(nextExecutionContext))
           handled = this._list.selectNextItem(false, false);
         break;
@@ -137,9 +140,9 @@
           break;
         var depth = this._depthFor(currentExecutionContext);
         for (var i = this._list.selectedIndex() - 1; i >= 0; i--) {
-          if (this._depthFor(this._list.itemAtIndex(i)) < depth) {
+          if (this._depthFor(this._items.itemAtIndex(i)) < depth) {
             handled = true;
-            this._list.selectItem(this._list.itemAtIndex(i), false);
+            this._list.selectItem(this._items.itemAtIndex(i), false);
             break;
           }
         }
@@ -151,18 +154,18 @@
         handled = this._list.selectItemNextPage(false);
         break;
       case 'Home':
-        for (var i = 0; i < this._list.length(); i++) {
-          if (this.isItemSelectable(this._list.itemAtIndex(i))) {
-            this._list.selectItem(this._list.itemAtIndex(i));
+        for (var i = 0; i < this._items.length(); i++) {
+          if (this.isItemSelectable(this._items.itemAtIndex(i))) {
+            this._list.selectItem(this._items.itemAtIndex(i));
             handled = true;
             break;
           }
         }
         break;
       case 'End':
-        for (var i = this._list.length() - 1; i >= 0; i--) {
-          if (this.isItemSelectable(this._list.itemAtIndex(i))) {
-            this._list.selectItem(this._list.itemAtIndex(i));
+        for (var i = this._items.length() - 1; i >= 0; i--) {
+          if (this.isItemSelectable(this._items.itemAtIndex(i))) {
+            this._list.selectItem(this._items.itemAtIndex(i));
             handled = true;
             break;
           }
@@ -192,8 +195,8 @@
         if (event.key.length === 1) {
           var selectedIndex = this._list.selectedIndex();
           var letter = event.key.toUpperCase();
-          for (var i = 0; i < this._list.length(); i++) {
-            var context = this._list.itemAtIndex((selectedIndex + i + 1) % this._list.length());
+          for (var i = 0; i < this._items.length(); i++) {
+            var context = this._items.itemAtIndex((selectedIndex + i + 1) % this._items.length());
             if (this._titleFor(context).toUpperCase().startsWith(letter)) {
               this._list.selectItem(context);
               break;
@@ -296,7 +299,7 @@
     if (!executionContext.target().hasJSCapability())
       return;
 
-    this._list.insertItemWithComparator(executionContext, executionContext.runtimeModel.executionContextComparator());
+    this._items.insertItemWithComparator(executionContext, executionContext.runtimeModel.executionContextComparator());
 
     if (executionContext === UI.context.flavor(SDK.ExecutionContext))
       this._updateSelectionTitle();
@@ -317,7 +320,7 @@
    */
   _onExecutionContextChanged(event) {
     var executionContext = /** @type {!SDK.ExecutionContext} */ (event.data);
-    if (this._list.indexOfItem(executionContext) === -1)
+    if (this._items.indexOfItem(executionContext) === -1)
       return;
     this._executionContextDestroyed(executionContext);
     this._executionContextCreated(executionContext);
@@ -328,10 +331,10 @@
    * @param {!SDK.ExecutionContext} executionContext
    */
   _executionContextDestroyed(executionContext) {
-    if (this._list.indexOfItem(executionContext) === -1)
+    if (this._items.indexOfItem(executionContext) === -1)
       return;
     this._disposeExecutionContextBadge(executionContext);
-    this._list.removeItem(executionContext);
+    this._items.removeItem(executionContext);
     this._updateGlasspaneSize();
   }
 
@@ -349,7 +352,7 @@
    */
   _executionContextChangedExternally(event) {
     var executionContext = /** @type {?SDK.ExecutionContext} */ (event.data);
-    if (!executionContext || this._list.indexOfItem(executionContext) === -1)
+    if (!executionContext || this._items.indexOfItem(executionContext) === -1)
       return;
     this._list.selectItem(executionContext);
     this._updateSelectedContext();
@@ -383,8 +386,8 @@
    * @return {boolean}
    */
   _hasTopContext() {
-    for (var i = 0; i < this._list.length(); i++) {
-      if (this._isTopContext(this._list.itemAtIndex(i)))
+    for (var i = 0; i < this._items.length(); i++) {
+      if (this._isTopContext(this._items.itemAtIndex(i)))
         return true;
     }
     return false;
@@ -403,9 +406,9 @@
    * @param {!SDK.RuntimeModel} runtimeModel
    */
   modelRemoved(runtimeModel) {
-    for (var i = 0; i < this._list.length(); i++) {
-      if (this._list.itemAtIndex(i).runtimeModel === runtimeModel)
-        this._executionContextDestroyed(this._list.itemAtIndex(i));
+    for (var i = 0; i < this._items.length(); i++) {
+      if (this._items.itemAtIndex(i).runtimeModel === runtimeModel)
+        this._executionContextDestroyed(this._items.itemAtIndex(i));
     }
   }
 
@@ -520,10 +523,11 @@
    */
   _callFrameSelectedInModel(event) {
     var debuggerModel = /** @type {!SDK.DebuggerModel} */ (event.data);
-    for (var i = 0; i < this._list.length(); i++) {
-      if (this._list.itemAtIndex(i).debuggerModel === debuggerModel) {
-        this._disposeExecutionContextBadge(this._list.itemAtIndex(i));
-        this._list.refreshItemsInRange(i, i + 1);
+    for (var i = 0; i < this._items.length(); i++) {
+      var executionContext = this._items.itemAtIndex(i);
+      if (executionContext.debuggerModel === debuggerModel) {
+        this._disposeExecutionContextBadge(executionContext);
+        this._list.refreshItem(executionContext);
       }
     }
   }
@@ -533,10 +537,11 @@
    */
   _frameNavigated(event) {
     var frameId = event.data.id;
-    for (var i = 0; i < this._list.length(); i++) {
-      if (frameId === this._list.itemAtIndex(i).frameId) {
-        this._disposeExecutionContextBadge(this._list.itemAtIndex(i));
-        this._list.refreshItemsInRange(i, i + 1);
+    for (var i = 0; i < this._items.length(); i++) {
+      var executionContext = this._items.itemAtIndex(i);
+      if (frameId === executionContext.frameId) {
+        this._disposeExecutionContextBadge(executionContext);
+        this._list.refreshItem(executionContext);
       }
     }
   }
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsoleView.js b/third_party/WebKit/Source/devtools/front_end/console/ConsoleView.js
index e038414..9b68900 100644
--- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleView.js
+++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleView.js
@@ -575,9 +575,9 @@
     contextMenu.appendItem(Common.UIString('Save as...'), this._saveConsole.bind(this));
 
     var request = consoleMessage ? consoleMessage.request : null;
-    if (request && request.resourceType() === Common.resourceTypes.XHR) {
+    if (request && SDK.NetworkManager.canReplayRequest(request)) {
       contextMenu.appendSeparator();
-      contextMenu.appendItem(Common.UIString('Replay XHR'), request.replayXHR.bind(request));
+      contextMenu.appendItem(Common.UIString('Replay XHR'), SDK.NetworkManager.replayRequest.bind(null, request));
     }
 
     contextMenu.show();
diff --git a/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css b/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
index 1fec4e6..8cad9b6f 100644
--- a/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
+++ b/third_party/WebKit/Source/devtools/front_end/devices/devicesView.css
@@ -137,15 +137,18 @@
     flex: auto 1 1;
 }
 
-.port-forwarding-value {
+.list-item .port-forwarding-value {
     white-space: nowrap;
     text-overflow: ellipsis;
     -webkit-user-select: none;
     color: #222;
-    flex: 3 1 0;
     overflow: hidden;
 }
 
+.port-forwarding-value {
+    flex: 3 1 0;
+}
+
 .port-forwarding-value.port-forwarding-port {
     flex: 1 1 0;
 }
@@ -224,15 +227,18 @@
     flex: auto 1 1;
 }
 
-.network-discovery-value {
+.list-item .network-discovery-value {
     white-space: nowrap;
     text-overflow: ellipsis;
     -webkit-user-select: none;
     color: #222;
-    flex: 3 1 0;
     overflow: hidden;
 }
 
+.network-discovery-value {
+    flex: 3 1 0;
+}
+
 .network-discovery-edit-row {
     flex: none;
     display: flex;
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/SensorsView.js b/third_party/WebKit/Source/devtools/front_end/emulation/SensorsView.js
index 79317b6..294c0ba 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/SensorsView.js
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/SensorsView.js
@@ -77,6 +77,7 @@
     var longitudeGroup = this._fieldsetElement.createChild('div', 'latlong-group');
 
     this._latitudeInput = latitudeGroup.createChild('input');
+    this._latitudeInput.setAttribute('step', 'any');
     this._latitudeInput.setAttribute('type', 'number');
     this._latitudeInput.value = 0;
     this._latitudeSetter = UI.bindInput(
@@ -85,6 +86,7 @@
     this._latitudeSetter(String(geolocation.latitude));
 
     this._longitudeInput = longitudeGroup.createChild('input');
+    this._longitudeInput.setAttribute('step', 'any');
     this._longitudeInput.setAttribute('type', 'number');
     this._longitudeInput.value = 0;
     this._longitudeSetter = UI.bindInput(
@@ -311,14 +313,17 @@
     var cellElement = fieldsetElement.createChild('td', 'orientation-inputs-cell');
 
     this._alphaElement = createElement('input');
+    this._alphaElement.setAttribute('step', 'any');
     this._alphaSetter = this._createAxisInput(cellElement, this._alphaElement, Common.UIString('\u03B1 (alpha)'));
     this._alphaSetter(String(deviceOrientation.alpha));
 
     this._betaElement = createElement('input');
+    this._betaElement.setAttribute('step', 'any');
     this._betaSetter = this._createAxisInput(cellElement, this._betaElement, Common.UIString('\u03B2 (beta)'));
     this._betaSetter(String(deviceOrientation.beta));
 
     this._gammaElement = createElement('input');
+    this._gammaElement.setAttribute('step', 'any');
     this._gammaSetter = this._createAxisInput(cellElement, this._gammaElement, Common.UIString('\u03B3 (gamma)'));
     this._gammaSetter(String(deviceOrientation.gamma));
 
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/devicesSettingsTab.css b/third_party/WebKit/Source/devtools/front_end/emulation/devicesSettingsTab.css
index a731ee9f..0f4b71d 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/devicesSettingsTab.css
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/devicesSettingsTab.css
@@ -74,7 +74,6 @@
     height: 22px;
     border: 1px solid rgb(213, 213, 213);
     border-radius: 2px;
-    color: #444444;
     padding: 3px;
 }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/emulation/sensors.css b/third_party/WebKit/Source/devtools/front_end/emulation/sensors.css
index 8fec49e..6288dfd 100644
--- a/third_party/WebKit/Source/devtools/front_end/emulation/sensors.css
+++ b/third_party/WebKit/Source/devtools/front_end/emulation/sensors.css
@@ -28,11 +28,6 @@
 .sensors-view select {
     border: 1px solid #bfbfbf;
     border-radius: 2px;
-    box-sizing: border-box;
-    color: #444;
-    font: inherit;
-    border-width: 1px;
-    text-align: left;
 }
 
 .sensors-view input {
diff --git a/third_party/WebKit/Source/devtools/front_end/help/ReleaseNoteText.js b/third_party/WebKit/Source/devtools/front_end/help/ReleaseNoteText.js
index cac4efb..2cb717fe 100644
--- a/third_party/WebKit/Source/devtools/front_end/help/ReleaseNoteText.js
+++ b/third_party/WebKit/Source/devtools/front_end/help/ReleaseNoteText.js
@@ -6,11 +6,52 @@
 // be shown in Canary (e.g. make sure the release notes are accurate).
 // https://github.com/ChromeDevTools/devtools-frontend/wiki/Release-Notes
 
+var continueToHereShortcut = Host.isMac() ? 'Command' : 'Control';
 var commandMenuShortcut = Host.isMac() ? 'Command + Shift + P' : 'Control + Shift + P';
 
 /** @type {!Array<!Help.ReleaseNote>} */
 Help.releaseNoteText = [
   {
+    version: 3,
+    header: 'Highlights from the Chrome 60 update',
+    highlights: [
+      {
+        title: 'New Audits panel, powered by Lighthouse',
+        subtitle:
+            'Find out whether your site qualifies as a Progressive Web App, measure the accessibility and performance of a page, and discover best practices.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#lighthouse',
+      },
+      {
+        title: 'Third-party badges',
+        subtitle:
+            'See what third-party entities are logging to the Console, making network requests, and causing work during performance recordings.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#badges',
+      },
+      {
+        title: 'New "Continue to Here" gesture',
+        subtitle: 'While paused on a line of code, hold ' + continueToHereShortcut +
+            ' and then click to continue to another line of code.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#continue',
+      },
+      {
+        title: 'Step into async',
+        subtitle: 'Predictably step into a promise resolution or other asynchronous code with a single gesture.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#step-into-async',
+      },
+      {
+        title: 'More informative object previews',
+        subtitle: 'Get a better idea of the contents of objects when logging them to the Console.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#object-previews',
+      },
+      {
+        title: 'Real-time Coverage tab updates',
+        subtitle: 'See what code is being used in real-time.',
+        link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes#coverage',
+      }
+    ],
+    link: 'https://developers.google.com/web/updates/2017/05/devtools-release-notes',
+  },
+  {
     version: 2,
     header: 'Highlights from Chrome 59 update',
     highlights: [
diff --git a/third_party/WebKit/Source/devtools/front_end/network/BlockedURLsPane.js b/third_party/WebKit/Source/devtools/front_end/network/BlockedURLsPane.js
index 35c0f73..9608234 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/BlockedURLsPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/network/BlockedURLsPane.js
@@ -153,7 +153,7 @@
     var editor = new UI.ListWidget.Editor();
     var content = editor.contentElement();
     var titles = content.createChild('div', 'blocked-url-edit-row');
-    titles.createChild('div', 'blocked-url-edit-value').textContent =
+    titles.createChild('div').textContent =
         Common.UIString('Text pattern to block matching requests; use * for wildcard');
     var fields = content.createChild('div', 'blocked-url-edit-row');
     var urlInput = editor.createInput(
diff --git a/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js b/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js
index 953a544..16f0bfbb 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js
+++ b/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js
@@ -1161,6 +1161,12 @@
             Common.UIString.capitalize('Unblock ' + croppedDomain), removeBlockedURL.bind(null, domain));
       }
 
+      if (SDK.NetworkManager.canReplayRequest(request)) {
+        contextMenu.appendSeparator();
+        contextMenu.appendItem(Common.UIString('Replay XHR'), SDK.NetworkManager.replayRequest.bind(null, request));
+        contextMenu.appendSeparator();
+      }
+
       /**
        * @param {string} url
        */
@@ -1180,12 +1186,6 @@
         UI.viewManager.showView('network.blocked-urls');
       }
     }
-
-    if (request && request.resourceType() === Common.resourceTypes.XHR) {
-      contextMenu.appendSeparator();
-      contextMenu.appendItem(Common.UIString('Replay XHR'), request.replayXHR.bind(request));
-      contextMenu.appendSeparator();
-    }
   }
 
   _harRequests() {
diff --git a/third_party/WebKit/Source/devtools/front_end/network/blockedURLsPane.css b/third_party/WebKit/Source/devtools/front_end/network/blockedURLsPane.css
index 1ba8d66..e40dafa3 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/blockedURLsPane.css
+++ b/third_party/WebKit/Source/devtools/front_end/network/blockedURLsPane.css
@@ -67,12 +67,8 @@
 }
 
 .blocked-url-edit-value {
-    white-space: nowrap;
-    text-overflow: ellipsis;
     -webkit-user-select: none;
-    color: #222;
     flex: 1 1 0px;
-    overflow: hidden;
 }
 
 .blocked-url-edit-row input {
diff --git a/third_party/WebKit/Source/devtools/front_end/network/networkConfigView.css b/third_party/WebKit/Source/devtools/front_end/network/networkConfigView.css
index 5580678..72b179d 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/networkConfigView.css
+++ b/third_party/WebKit/Source/devtools/front_end/network/networkConfigView.css
@@ -67,10 +67,6 @@
     max-width: 250px;
     border: 1px solid #bfbfbf;
     border-radius: 2px;
-    box-sizing: border-box;
-    color: #444;
-    font: inherit;
-    border-width: 1px;
     min-height: 2em;
     padding: 3px;
 }
diff --git a/third_party/WebKit/Source/devtools/front_end/persistence/editFileSystemView.css b/third_party/WebKit/Source/devtools/front_end/persistence/editFileSystemView.css
index 7bf411c..a6de783 100644
--- a/third_party/WebKit/Source/devtools/front_end/persistence/editFileSystemView.css
+++ b/third_party/WebKit/Source/devtools/front_end/persistence/editFileSystemView.css
@@ -42,15 +42,17 @@
     flex: auto 1 1;
 }
 
-.file-system-value {
+.list-item .file-system-value {
     white-space: nowrap;
     text-overflow: ellipsis;
     -webkit-user-select: none;
-    color: #222;
-    flex: 1 1 0px;
     overflow: hidden;
 }
 
+.file-system-value {
+    flex: 1 1 0px;
+}
+
 .file-system-separator {
     flex: 0 0 1px;
     background-color: rgb(231, 231, 231);
diff --git a/third_party/WebKit/Source/devtools/front_end/persistence/workspaceSettingsTab.css b/third_party/WebKit/Source/devtools/front_end/persistence/workspaceSettingsTab.css
index 736c105..a3a121a 100644
--- a/third_party/WebKit/Source/devtools/front_end/persistence/workspaceSettingsTab.css
+++ b/third_party/WebKit/Source/devtools/front_end/persistence/workspaceSettingsTab.css
@@ -55,14 +55,6 @@
     padding: 0;
 }
 
-.settings-tab input:not([type]),
-.settings-tab input[type="text"] {
-    border: 1px solid rgb(213, 213, 213);
-    border-radius: 2px;
-    color: #444444;
-    padding: 3px;
-}
-
 .settings-tab p {
     margin: 12px 0;
 }
diff --git a/third_party/WebKit/Source/devtools/front_end/quick_open/FilteredListWidget.js b/third_party/WebKit/Source/devtools/front_end/quick_open/FilteredListWidget.js
index 86152f7..e7deb52 100644
--- a/third_party/WebKit/Source/devtools/front_end/quick_open/FilteredListWidget.js
+++ b/third_party/WebKit/Source/devtools/front_end/quick_open/FilteredListWidget.js
@@ -34,8 +34,10 @@
     this._progressElement = this._bottomElementsContainer.createChild('div', 'filtered-list-widget-progress');
     this._progressBarElement = this._progressElement.createChild('div', 'filtered-list-widget-progress-bar');
 
+    /** @type {!UI.ListModel<number>} */
+    this._items = new UI.ListModel();
     /** @type {!UI.ListControl<number>} */
-    this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems);
+    this._list = new UI.ListControl(this._items, this, UI.ListMode.EqualHeightItems);
     this._itemElementsContainer = this._list.element;
     this._itemElementsContainer.classList.add('container');
     this._bottomElementsContainer.appendChild(this._itemElementsContainer);
@@ -150,7 +152,7 @@
   }
 
   _attachProvider() {
-    this._list.replaceAllItems([]);
+    this._items.replaceAllItems([]);
     this._list.invalidateItemHeight();
     if (this._provider) {
       this._provider.setRefreshCallback(this._itemsLoaded.bind(this, this._provider));
@@ -433,7 +435,7 @@
     filteredItems = [].concat(bestItems, overflowItems, filteredItems);
     this._updateNotFoundMessage(!!filteredItems.length);
     var oldHeight = this._list.element.offsetHeight;
-    this._list.replaceAllItems(filteredItems);
+    this._items.replaceAllItems(filteredItems);
     if (filteredItems.length)
       this._list.selectItem(filteredItems[0]);
     if (this._list.element.offsetHeight !== oldHeight)
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 1d0b90c..1e4f6d7 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkManager.js
@@ -58,6 +58,22 @@
   }
 
   /**
+   * @param {!SDK.NetworkRequest} request
+   * @return {boolean}
+   */
+  static canReplayRequest(request) {
+    return request.resourceType() === Common.resourceTypes.XHR;
+  }
+
+  /**
+   * @param {!SDK.NetworkRequest} request
+   */
+  static replayRequest(request) {
+    // TODO(allada) networkAgent() will be removed from NetworkRequest, but in the mean time we extract it from request.
+    request.networkManager()._networkAgent.replayXHR(request.requestId());
+  }
+
+  /**
    * @param {!SDK.NetworkManager.Conditions} conditions
    * @return {!Protocol.Network.ConnectionType}
    * TODO(allada): this belongs to NetworkConditionsSelector, which should hardcode/guess it.
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 311f417..12c4ad84 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/NetworkRequest.js
@@ -1100,10 +1100,6 @@
     this.dispatchEventToListeners(SDK.NetworkRequest.Events.EventSourceMessageAdded, message);
   }
 
-  replayXHR() {
-    this._networkManager.target().networkAgent().replayXHR(this._requestId);
-  }
-
   /**
    * @return {!SDK.NetworkManager}
    */
diff --git a/third_party/WebKit/Source/devtools/front_end/settings/frameworkBlackboxSettingsTab.css b/third_party/WebKit/Source/devtools/front_end/settings/frameworkBlackboxSettingsTab.css
index e7bb786b..00ca7ba 100644
--- a/third_party/WebKit/Source/devtools/front_end/settings/frameworkBlackboxSettingsTab.css
+++ b/third_party/WebKit/Source/devtools/front_end/settings/frameworkBlackboxSettingsTab.css
@@ -51,15 +51,18 @@
     flex: auto 1 1;
 }
 
-.blackbox-pattern {
+ .blackbox-list-item .blackbox-pattern {
     white-space: nowrap;
     text-overflow: ellipsis;
     -webkit-user-select: none;
     color: #222;
-    flex: auto;
     overflow: hidden;
 }
 
+.blackbox-pattern {
+    flex: auto;
+}
+
 .blackbox-list-item.blackbox-disabled .blackbox-pattern {
     text-decoration: line-through;
 }
diff --git a/third_party/WebKit/Source/devtools/front_end/settings/settingsScreen.css b/third_party/WebKit/Source/devtools/front_end/settings/settingsScreen.css
index 6387a4c..707b6383 100644
--- a/third_party/WebKit/Source/devtools/front_end/settings/settingsScreen.css
+++ b/third_party/WebKit/Source/devtools/front_end/settings/settingsScreen.css
@@ -164,7 +164,6 @@
 .settings-tab input[type="text"] {
     border: 1px solid rgb(213, 213, 213);
     border-radius: 2px;
-    color: #444444;
     padding: 3px;
 }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js
index 73503b4..4c30a89 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js
@@ -39,8 +39,10 @@
     this._notPausedMessageElement = this.contentElement.createChild('div', 'gray-info-message');
     this._notPausedMessageElement.textContent = Common.UIString('Not Paused');
 
+    /** @type {!UI.ListModel<!Sources.CallStackSidebarPane.Item>} */
+    this._items = new UI.ListModel();
     /** @type {!UI.ListControl<!Sources.CallStackSidebarPane.Item>} */
-    this._list = new UI.ListControl(this, UI.ListMode.NonViewport);
+    this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport);
     this.contentElement.appendChild(this._list.element);
     this._list.element.addEventListener('contextmenu', this._onContextMenu.bind(this), false);
     this._list.element.addEventListener('click', this._onClick.bind(this), false);
@@ -68,7 +70,7 @@
     if (!details) {
       this._notPausedMessageElement.classList.remove('hidden');
       this._blackboxedMessageElement.classList.add('hidden');
-      this._list.replaceAllItems([]);
+      this._items.replaceAllItems([]);
       this._debuggerModel = null;
       UI.context.setFlavor(SDK.DebuggerModel.CallFrame, null);
       return;
@@ -139,7 +141,7 @@
       this._blackboxedMessageElement.classList.remove('hidden');
     }
 
-    this._list.replaceAllItems(items);
+    this._items.replaceAllItems(items);
     this._list.selectNextItem(true /* canWrap */, false /* center */);
   }
 
@@ -357,8 +359,8 @@
 
   _copyStackTrace() {
     var text = [];
-    for (var i = 0; i < this._list.length(); i++) {
-      var item = this._list.itemAtIndex(i);
+    for (var i = 0; i < this._items.length(); i++) {
+      var item = this._items.itemAtIndex(i);
       if (item.promiseCreationFrame)
         continue;
       var itemText = this._itemTitle(item);
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/SourcesPanel.js b/third_party/WebKit/Source/devtools/front_end/sources/SourcesPanel.js
index c54e0e7..6278274b5 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/SourcesPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/SourcesPanel.js
@@ -1056,7 +1056,7 @@
       this._sidebarPaneStack.showView(jsBreakpoints);
       this._extensionSidebarPanesContainer = this._sidebarPaneStack;
       this.sidebarPaneView = vbox;
-      this._splitWidget.uninstallResizer(this._debugToolbar.element);
+      this._splitWidget.uninstallResizer(this._debugToolbar.gripElementForResize());
     } else {
       var splitWidget = new UI.SplitWidget(true, true, 'sourcesPanelDebuggerSidebarSplitViewState', 0.5);
       splitWidget.setMainWidget(vbox);
@@ -1068,7 +1068,7 @@
       splitWidget.setSidebarWidget(tabbedLocation.tabbedPane());
       this._tabbedLocationHeader = tabbedLocation.tabbedPane().headerElement();
       this._splitWidget.installResizer(this._tabbedLocationHeader);
-      this._splitWidget.installResizer(this._debugToolbar.element);
+      this._splitWidget.installResizer(this._debugToolbar.gripElementForResize());
       tabbedLocation.appendView(scopeChainView);
       tabbedLocation.appendView(this._watchSidebarPane);
       this._extensionSidebarPanesContainer = tabbedLocation;
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/ThreadsSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/sources/ThreadsSidebarPane.js
index 8009585..f193a3f 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/ThreadsSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/ThreadsSidebarPane.js
@@ -10,8 +10,10 @@
     super(true);
     this.registerRequiredCSS('sources/threadsSidebarPane.css');
 
+    /** @type {!UI.ListModel<!SDK.DebuggerModel>} */
+    this._items = new UI.ListModel();
     /** @type {!UI.ListControl<!SDK.DebuggerModel>} */
-    this._list = new UI.ListControl(this, UI.ListMode.NonViewport);
+    this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport);
     this.contentElement.appendChild(this._list.element);
 
     UI.context.addFlavorChangeListener(SDK.Target, this._targetFlavorChanged, this);
@@ -106,7 +108,7 @@
    * @param {!SDK.DebuggerModel} debuggerModel
    */
   modelAdded(debuggerModel) {
-    this._list.pushItem(debuggerModel);
+    this._items.pushItem(debuggerModel);
     var currentTarget = UI.context.flavor(SDK.Target);
     if (currentTarget === debuggerModel.target())
       this._list.selectItem(debuggerModel);
@@ -117,7 +119,7 @@
    * @param {!SDK.DebuggerModel} debuggerModel
    */
   modelRemoved(debuggerModel) {
-    this._list.removeItem(debuggerModel);
+    this._items.removeItem(debuggerModel);
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js
index 68ffb79..8e2522b5 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelineHistoryManager.js
@@ -253,9 +253,10 @@
         UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'timeline/timelineHistoryManager.css');
     var contentElement = shadowRoot.createChild('div', 'drop-down');
 
-    this._listControl = new UI.ListControl(this, UI.ListMode.NonViewport);
+    var listModel = new UI.ListModel();
+    this._listControl = new UI.ListControl(listModel, this, UI.ListMode.NonViewport);
     this._listControl.element.addEventListener('mousemove', this._onMouseMove.bind(this), false);
-    this._listControl.replaceAllItems(models);
+    listModel.replaceAllItems(models);
 
     contentElement.appendChild(this._listControl.element);
     contentElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
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 a2b92c00..39be6fd 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
@@ -50,10 +50,11 @@
  */
 UI.ListControl = class {
   /**
+   * @param {!UI.ListModel<T>} model
    * @param {!UI.ListDelegate<T>} delegate
    * @param {!UI.ListMode=} mode
    */
-  constructor(delegate, mode) {
+  constructor(model, delegate, mode) {
     this.element = createElement('div');
     this.element.style.overflowY = 'auto';
     this._topElement = this.element.createChild('div');
@@ -64,11 +65,13 @@
     this._topHeight = 0;
     this._bottomHeight = 0;
 
-    /** @type {!Array<T>} */
-    this._items = [];
+    this._model = model;
+    this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this);
     /** @type {!Map<T, !Element>} */
     this._itemToElement = new Map();
     this._selectedIndex = -1;
+    /** @type {?T} */
+    this._selectedItem = null;
 
     this.element.tabIndex = -1;
     this.element.addEventListener('click', this._onClick.bind(this), false);
@@ -88,106 +91,36 @@
   }
 
   /**
-   * @return {number}
+   * @param {!UI.ListModel<T>} model
    */
-  length() {
-    return this._items.length;
+  setModel(model) {
+    this._itemToElement.clear();
+    var length = this._model.length();
+    this._model.removeEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this);
+    this._model = model;
+    this._model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._replacedItemsInRange, this);
+    this.invalidateRange(0, length);
   }
 
   /**
-   * @param {number} index
-   * @return {T}
+   * @param {!Common.Event} event
    */
-  itemAtIndex(index) {
-    return this._items[index];
-  }
+  _replacedItemsInRange(event) {
+    var data = /** @type {{index: number, removed: !Array<T>, inserted: number}} */ (event.data);
+    var from = data.index;
+    var to = from + data.removed.length;
 
-  /**
-   * @param {T} item
-   */
-  pushItem(item) {
-    this.replaceItemsInRange(this._items.length, this._items.length, [item]);
-  }
-
-  /**
-   * @return {T}
-   */
-  popItem() {
-    return this.removeItemAtIndex(this._items.length - 1);
-  }
-
-  /**
-   * @param {number} index
-   * @param {T} item
-   */
-  insertItemAtIndex(index, item) {
-    this.replaceItemsInRange(index, index, [item]);
-  }
-
-  /**
-   * @param {T} item
-   * @param {function(T, T):number} comparator
-   */
-  insertItemWithComparator(item, comparator) {
-    var index = this._items.lowerBound(item, comparator);
-    this.insertItemAtIndex(index, item);
-  }
-
-  /**
-   * @param {T} item
-   * @return {number}
-   */
-  indexOfItem(item) {
-    return this._items.indexOf(item);
-  }
-
-  /**
-   * @param {number} index
-   * @return {T}
-   */
-  removeItemAtIndex(index) {
-    var result = this._items[index];
-    this.replaceItemsInRange(index, index + 1, []);
-    return result;
-  }
-
-  /**
-   * @param {T} item
-   */
-  removeItem(item) {
-    var index = this._items.indexOf(item);
-    if (index === -1) {
-      console.error('Attempt to remove non-existing item');
-      return;
-    }
-    this.removeItemAtIndex(index);
-  }
-
-  /**
-   * @param {number} from
-   * @param {number} to
-   * @param {!Array<T>} items
-   */
-  replaceItemsInRange(from, to, items) {
-    var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+    var oldSelectedItem = this._selectedItem;
     var oldSelectedElement = oldSelectedItem ? (this._itemToElement.get(oldSelectedItem) || null) : null;
-
-    for (var i = from; i < to; i++)
-      this._itemToElement.delete(this._items[i]);
-    if (items.length < 10000) {
-      this._items.splice.bind(this._items, from, to - from).apply(null, items);
-    } else {
-      // Splice may fail with too many arguments.
-      var before = this._items.slice(0, from);
-      var after = this._items.slice(to);
-      this._items = [].concat(before, items, after);
-    }
-    this._invalidate(from, to, items.length);
+    for (var i = 0; i < data.removed.length; i++)
+      this._itemToElement.delete(data.removed[i]);
+    this._invalidate(from, to, data.inserted);
 
     if (this._selectedIndex >= to) {
-      this._selectedIndex += items.length - (to - from);
+      this._selectedIndex += data.inserted - (to - from);
+      this._selectedItem = this._model.itemAtIndex(this._selectedIndex);
     } else if (this._selectedIndex >= from) {
-      var index = this._findFirstSelectable(from + items.length, +1, false);
+      var index = this._findFirstSelectable(from + data.inserted, +1, false);
       if (index === -1)
         index = this._findFirstSelectable(from - 1, -1, false);
       this._select(index, oldSelectedItem, oldSelectedElement);
@@ -195,24 +128,16 @@
   }
 
   /**
-   * @param {!Array<T>} items
+   * @param {T} item
    */
-  replaceAllItems(items) {
-    this.replaceItemsInRange(0, this._items.length, items);
-  }
-
-  refreshAllItems() {
-    this.refreshItemsInRange(0, this._items.length);
-  }
-
-  /**
-   * @param {number} from
-   * @param {number} to
-   */
-  refreshItemsInRange(from, to) {
-    for (var i = from; i < to; i++)
-      this._itemToElement.delete(this._items[i]);
-    this.invalidateRange(from, to);
+  refreshItem(item) {
+    var index = this._model.indexOfItem(item);
+    if (index === -1) {
+      console.error('Item to refresh is not present');
+      return;
+    }
+    this._itemToElement.delete(item);
+    this.invalidateRange(index, index + 1);
     if (this._selectedIndex !== -1)
       this._select(this._selectedIndex, null, null);
   }
@@ -241,9 +166,9 @@
       return;
     }
     this._fixedHeight = 0;
-    if (this._items.length) {
+    if (this._model.length()) {
       this._itemToElement.clear();
-      this._invalidate(0, this._items.length, this._items.length);
+      this._invalidate(0, this._model.length(), this._model.length());
     }
   }
 
@@ -257,8 +182,8 @@
     if (!node)
       return null;
     var element = /** @type {!Element} */ (node);
-    var index = this._items.findIndex(item => this._itemToElement.get(item) === element);
-    return index !== -1 ? this._items[index] : null;
+    var index = this._model.findIndex(item => this._itemToElement.get(item) === element);
+    return index !== -1 ? this._model.itemAtIndex(index) : null;
   }
 
   /**
@@ -266,7 +191,7 @@
    * @param {boolean=} center
    */
   scrollItemIntoView(item, center) {
-    var index = this._items.indexOf(item);
+    var index = this._model.indexOfItem(item);
     if (index === -1) {
       console.error('Attempt to scroll onto missing item');
       return;
@@ -278,7 +203,7 @@
    * @return {?T}
    */
   selectedItem() {
-    return this._selectedIndex === -1 ? null : this._items[this._selectedIndex];
+    return this._selectedItem;
   }
 
   /**
@@ -296,7 +221,7 @@
   selectItem(item, center, dontScroll) {
     var index = -1;
     if (item !== null) {
-      index = this._items.indexOf(item);
+      index = this._model.indexOfItem(item);
       if (index === -1) {
         console.error('Attempt to select missing item');
         return;
@@ -320,7 +245,7 @@
   selectPreviousItem(canWrap, center) {
     if (this._selectedIndex === -1 && !canWrap)
       return false;
-    var index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex - 1;
+    var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._selectedIndex - 1;
     index = this._findFirstSelectable(index, -1, !!canWrap);
     if (index !== -1) {
       this._scrollIntoView(index, center);
@@ -355,7 +280,7 @@
   selectItemPreviousPage(center) {
     if (this._mode === UI.ListMode.NonViewport)
       return false;
-    var index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex;
+    var index = this._selectedIndex === -1 ? this._model.length() - 1 : this._selectedIndex;
     index = this._findPageSelectable(index, -1);
     if (index !== -1) {
       this._scrollIntoView(index, center);
@@ -444,7 +369,7 @@
    * @return {number}
    */
   _totalHeight() {
-    return this._offsetAtIndex(this._items.length);
+    return this._offsetAtIndex(this._model.length());
   }
 
   /**
@@ -454,15 +379,15 @@
   _indexAtOffset(offset) {
     if (this._mode === UI.ListMode.NonViewport)
       throw 'There should be no offset conversions in non-viewport mode';
-    if (!this._items.length || offset < 0)
+    if (!this._model.length() || offset < 0)
       return 0;
     if (this._mode === UI.ListMode.VariousHeightItems) {
       return Math.min(
-          this._items.length - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._items.length));
+          this._model.length() - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._model.length()));
     }
     if (!this._fixedHeight)
       this._measureHeight();
-    return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeight));
+    return Math.min(this._model.length() - 1, Math.floor(offset / this._fixedHeight));
   }
 
   /**
@@ -470,7 +395,7 @@
    * @return {!Element}
    */
   _elementAtIndex(index) {
-    var item = this._items[index];
+    var item = this._model.itemAtIndex(index);
     var element = this._itemToElement.get(item);
     if (!element) {
       element = this._delegate.createElementForItem(item);
@@ -486,7 +411,7 @@
   _offsetAtIndex(index) {
     if (this._mode === UI.ListMode.NonViewport)
       throw 'There should be no offset conversions in non-viewport mode';
-    if (!this._items.length)
+    if (!this._model.length())
       return 0;
     if (this._mode === UI.ListMode.VariousHeightItems)
       return this._variableOffsets[index];
@@ -496,7 +421,7 @@
   }
 
   _measureHeight() {
-    this._fixedHeight = this._delegate.heightForItem(this._items[0]);
+    this._fixedHeight = this._delegate.heightForItem(this._model.itemAtIndex(0));
     if (!this._fixedHeight)
       this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.element).height;
   }
@@ -508,11 +433,12 @@
    */
   _select(index, oldItem, oldElement) {
     if (oldItem === undefined)
-      oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+      oldItem = this._selectedItem;
     if (oldElement === undefined)
       oldElement = this._itemToElement.get(oldItem) || null;
     this._selectedIndex = index;
-    var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+    this._selectedItem = index === -1 ? null : this._model.itemAtIndex(index);
+    var newItem = this._selectedItem;
     var newElement = this._selectedIndex !== -1 ? this._elementAtIndex(index) : null;
     this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */ (oldElement), newElement);
   }
@@ -524,7 +450,7 @@
    * @return {number}
    */
   _findFirstSelectable(index, direction, canWrap) {
-    var length = this._items.length;
+    var length = this._model.length();
     if (!length)
       return -1;
     for (var step = 0; step <= length; step++) {
@@ -533,7 +459,7 @@
           return -1;
         index = (index + length) % length;
       }
-      if (this._delegate.isItemSelectable(this._items[index]))
+      if (this._delegate.isItemSelectable(this._model.itemAtIndex(index)))
         return index;
       index += direction;
     }
@@ -550,8 +476,8 @@
     var startOffset = this._offsetAtIndex(index);
     // Compensate for zoom rounding errors with -1.
     var viewportHeight = this.element.offsetHeight - 1;
-    while (index >= 0 && index < this._items.length) {
-      if (this._delegate.isItemSelectable(this._items[index])) {
+    while (index >= 0 && index < this._model.length()) {
+      if (this._delegate.isItemSelectable(this._model.itemAtIndex(index))) {
         if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight)
           return index;
         lastSelectable = index;
@@ -589,9 +515,11 @@
     }
 
     if (this._mode === UI.ListMode.VariousHeightItems) {
-      this._reallocateVariableOffsets(this._items.length + 1, from + 1);
-      for (var i = from + 1; i <= this._items.length; i++)
-        this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate.heightForItem(this._items[i - 1]);
+      this._reallocateVariableOffsets(this._model.length() + 1, from + 1);
+      for (var i = from + 1; i <= this._model.length(); i++) {
+        this._variableOffsets[i] =
+            this._variableOffsets[i - 1] + this._delegate.heightForItem(this._model.itemAtIndex(i - 1));
+      }
     }
 
     var viewportHeight = this.element.offsetHeight;
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListModel.js b/third_party/WebKit/Source/devtools/front_end/ui/ListModel.js
new file mode 100644
index 0000000..5ebd819
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListModel.js
@@ -0,0 +1,140 @@
+// 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.
+
+/**
+ * @template T
+ */
+UI.ListModel = class extends Common.Object {
+  /**
+   * @param {!Array<T>=} items
+   */
+  constructor(items) {
+    super();
+    this._items = items || [];
+  }
+
+  /**
+   * @return {number}
+   */
+  length() {
+    return this._items.length;
+  }
+
+  /**
+   * @param {number} index
+   * @return {T}
+   */
+  itemAtIndex(index) {
+    return this._items[index];
+  }
+
+  /**
+   * @param {T} item
+   */
+  pushItem(item) {
+    this.replaceItemsInRange(this._items.length, this._items.length, [item]);
+  }
+
+  /**
+   * @return {T}
+   */
+  popItem() {
+    return this.removeItemAtIndex(this._items.length - 1);
+  }
+
+  /**
+   * @param {number} index
+   * @param {T} item
+   */
+  insertItemAtIndex(index, item) {
+    this.replaceItemsInRange(index, index, [item]);
+  }
+
+  /**
+   * @param {T} item
+   * @param {function(T, T):number} comparator
+   */
+  insertItemWithComparator(item, comparator) {
+    var index = this._items.lowerBound(item, comparator);
+    this.insertItemAtIndex(index, item);
+  }
+
+  /**
+   * @param {T} item
+   * @return {number}
+   */
+  indexOfItem(item) {
+    return this._items.indexOf(item);
+  }
+
+  /**
+   * @param {number} index
+   * @return {T}
+   */
+  removeItemAtIndex(index) {
+    var result = this._items[index];
+    this.replaceItemsInRange(index, index + 1, []);
+    return result;
+  }
+
+  /**
+   * @param {T} item
+   */
+  removeItem(item) {
+    var index = this._items.indexOf(item);
+    if (index === -1) {
+      console.error('Attempt to remove non-existing item');
+      return;
+    }
+    this.removeItemAtIndex(index);
+  }
+
+  /**
+   * @param {!Array<T>} items
+   */
+  replaceAllItems(items) {
+    this.replaceItemsInRange(0, this._items.length, items);
+  }
+
+  /**
+   * @param {number} index
+   * @param {T} item
+   */
+  replaceItemAtIndex(index, item) {
+    this.replaceItemsInRange(index, index + 1, [item]);
+  }
+
+  /**
+   * @param {function(T):boolean} callback
+   * @return {number}
+   */
+  findIndex(callback) {
+    return this._items.findIndex(callback);
+  }
+
+  /**
+   * @param {number} from
+   * @param {number} to
+   * @param {!Array<T>} items
+   */
+  replaceItemsInRange(from, to, items) {
+    var removed;
+    if (items.length < 10000) {
+      removed = this._items.splice(from, to - from, ...items);
+    } else {
+      removed = this._items.slice(from, to);
+      // Splice may fail with too many arguments.
+      var before = this._items.slice(0, from);
+      var after = this._items.slice(to);
+      this._items = [].concat(before, items, after);
+    }
+    this.dispatchEventToListeners(
+        UI.ListModel.Events.ItemsReplaced, {index: from, removed: removed, inserted: items.length});
+  }
+};
+
+/** @enum {symbol} */
+UI.ListModel.Events = {
+  ItemsReplaced: Symbol('ItemsReplaced'),
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
index 03e0c12..7c3badc 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
@@ -66,8 +66,10 @@
     /** @type {?string} */
     this._onlyCompletion = null;
 
+    /** @type {!UI.ListModel<!UI.SuggestBox.Suggestion>} */
+    this._items = new UI.ListModel();
     /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */
-    this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems);
+    this._list = new UI.ListControl(this._items, this, UI.ListMode.EqualHeightItems);
     this._element = this._list.element;
     this._element.classList.add('suggest-box');
     this._element.addEventListener('mousedown', event => event.preventDefault(), true);
@@ -316,7 +318,7 @@
       this._updateMaxSize(completions);
       this._glassPane.setContentAnchorBox(anchorBox);
       this._list.invalidateItemHeight();
-      this._list.replaceAllItems(completions);
+      this._items.replaceAllItems(completions);
 
       if (selectHighestPriority) {
         var highestPriorityItem = completions[0];
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 909d19a..37cc0e2f 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/Toolbar.js
@@ -194,6 +194,13 @@
   }
 
   /**
+   * @return {!Element}
+   */
+  gripElementForResize() {
+    return this._contentElement;
+  }
+
+  /**
    * @param {boolean=} reverse
    * @param {boolean=} growVertically
    */
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/module.json b/third_party/WebKit/Source/devtools/front_end/ui/module.json
index 52967e3f..c1144a11 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/ui/module.json
@@ -28,6 +28,7 @@
         "TextEditor.js",
         "KeyboardShortcut.js",
         "ListControl.js",
+        "ListModel.js",
         "ListWidget.js",
         "Panel.js",
         "Popover.js",
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines.py
new file mode 100644
index 0000000..2cb949a
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines.py
@@ -0,0 +1,124 @@
+# 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 logging
+
+from webkitpy.common.memoized import memoized
+from webkitpy.layout_tests.models.test_expectations import SKIP
+from webkitpy.layout_tests.models.test_expectations import TestExpectations
+from webkitpy.tool.commands.rebaseline import AbstractRebaseliningCommand
+
+_log = logging.getLogger(__name__)
+
+
+class CopyExistingBaselines(AbstractRebaseliningCommand):
+    name = 'copy-existing-baselines-internal'
+    help_text = ('Copy existing baselines down one level in the baseline '
+                 'order to ensure new baselines don\'t break existing passing '
+                 'platforms.')
+
+    def __init__(self):
+        super(CopyExistingBaselines, self).__init__(options=[
+            self.test_option,
+            self.suffixes_option,
+            self.port_name_option,
+            self.results_directory_option,
+        ])
+
+    def execute(self, options, args, tool):
+        self._tool = tool
+        port_name = options.port_name
+        for suffix in options.suffixes.split(','):
+            self._copy_existing_baseline(port_name, options.test, suffix)
+
+    def _copy_existing_baseline(self, port_name, test_name, suffix):
+        """Copies the baseline for the given builder to all "predecessor" directories."""
+        baseline_directory = self._tool.port_factory.get(port_name).baseline_version_dir()
+        ports = [self._port_for_primary_baseline(baseline)
+                 for baseline in self._immediate_predecessors_in_fallback(baseline_directory)]
+
+        old_baselines = []
+        new_baselines = []
+
+        # Need to gather all the baseline paths before modifying the filesystem since
+        # the modifications can affect the results of port.expected_filename.
+        for port in ports:
+            old_baseline = port.expected_filename(test_name, '.' + suffix)
+            if not self._tool.filesystem.exists(old_baseline):
+                _log.debug('No existing baseline for %s.', test_name)
+                continue
+
+            new_baseline = self._tool.filesystem.join(
+                port.baseline_version_dir(),
+                self._file_name_for_expected_result(test_name, suffix))
+            if self._tool.filesystem.exists(new_baseline):
+                _log.debug('Existing baseline at %s, not copying over it.', new_baseline)
+                continue
+
+            generic_expectations = TestExpectations(port, tests=[test_name], include_overrides=False)
+            full_expectations = TestExpectations(port, tests=[test_name], include_overrides=True)
+            # TODO(qyearsley): Change Port.skips_test so that this can be simplified.
+            if SKIP in full_expectations.get_expectations(test_name):
+                _log.debug('%s is skipped (perhaps temporarily) on %s.', test_name, port.name())
+                continue
+            if port.skips_test(test_name, generic_expectations, full_expectations):
+                _log.debug('%s is skipped on %s.', test_name, port.name())
+                continue
+
+            old_baselines.append(old_baseline)
+            new_baselines.append(new_baseline)
+
+        for i in range(len(old_baselines)):
+            old_baseline = old_baselines[i]
+            new_baseline = new_baselines[i]
+
+            _log.debug('Copying baseline from %s to %s.', old_baseline, new_baseline)
+            self._tool.filesystem.maybe_make_directory(self._tool.filesystem.dirname(new_baseline))
+            self._tool.filesystem.copyfile(old_baseline, new_baseline)
+
+    def _port_for_primary_baseline(self, baseline):
+        """Returns a Port object for the given baseline directory base name."""
+        for port in [self._tool.port_factory.get(port_name) for port_name in self._tool.port_factory.all_port_names()]:
+            if self._tool.filesystem.basename(port.baseline_version_dir()) == baseline:
+                return port
+        raise Exception('Failed to find port for primary baseline %s.' % baseline)
+
+    @memoized
+    def _immediate_predecessors_in_fallback(self, path_to_rebaseline):
+        """Returns the predecessor directories in the baseline fall-back graph.
+
+        The platform-specific fall-back baseline directories form a tree, where
+        when we search for baselines we normally fall back to parents nodes in
+        the tree. The "immediate predecessors" are the children nodes of the
+        given node.
+
+        For example, if the baseline fall-back graph includes:
+            "mac10.9" -> "mac10.10/"
+            "mac10.10/" -> "mac/"
+            "retina/" -> "mac/"
+        Then, the "immediate predecessors" are:
+            "mac/": ["mac10.10/", "retina/"]
+            "mac10.10/": ["mac10.9/"]
+            "mac10.9/", "retina/": []
+
+        Args:
+            path_to_rebaseline: The absolute path to a baseline directory.
+
+        Returns:
+            A list of directory names (not full paths) of directories that are
+            "immediate predecessors" of the given baseline path.
+        """
+        port_names = self._tool.port_factory.all_port_names()
+        immediate_predecessors = []
+        for port_name in port_names:
+            port = self._tool.port_factory.get(port_name)
+            baseline_search_path = port.baseline_search_path()
+            try:
+                index = baseline_search_path.index(path_to_rebaseline)
+                if index:
+                    immediate_predecessors.append(self._tool.filesystem.basename(baseline_search_path[index - 1]))
+            except ValueError:
+                # baseline_search_path.index() throws a ValueError if the item isn't in the list.
+                pass
+        return immediate_predecessors
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines_unittest.py
new file mode 100644
index 0000000..b01f871
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/copy_existing_baselines_unittest.py
@@ -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.
+
+import optparse
+
+from webkitpy.tool.commands.rebaseline_unittest import BaseTestCase
+from webkitpy.tool.commands.copy_existing_baselines import CopyExistingBaselines
+
+
+class TestCopyExistingBaselines(BaseTestCase):
+    command_constructor = CopyExistingBaselines
+
+    def options(self, **kwargs):
+        options_dict = {
+            'results_directory': None,
+            'suffixes': 'txt',
+            'verbose': False,
+            'port_name': None,
+        }
+        options_dict.update(kwargs)
+        return optparse.Values(options_dict)
+
+    def baseline_path(self, path_from_layout_test_dir):
+        port = self.tool.port_factory.get()
+        return self.tool.filesystem.join(port.layout_tests_dir(), path_from_layout_test_dir)
+
+    # The tests in this class all depend on the fall-back path graph
+    # that is set up in |TestPort.FALLBACK_PATHS|.
+
+    def test_copy_baseline_mac_newer_to_older_version(self):
+        # The test-mac-mac10.11 baseline is copied over to the test-mac-mac10.10
+        # baseline directory because test-mac-mac10.10 is the "immediate
+        # predecessor" in the fall-back graph.
+        self._write(
+            self.baseline_path('platform/test-mac-mac10.11/failures/expected/image-expected.txt'),
+            'original test-mac-mac10.11 result')
+        self.assertFalse(self.tool.filesystem.exists(
+            self.baseline_path('platform/test-mac-mac10.10/failures/expected/image-expected.txt')))
+
+        self.command.execute(self.options(port_name='test-mac-mac10.11', test='failures/expected/image.html'), [], self.tool)
+
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-mac-mac10.11/failures/expected/image-expected.txt')),
+            'original test-mac-mac10.11 result')
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-mac-mac10.10/failures/expected/image-expected.txt')),
+            'original test-mac-mac10.11 result')
+
+    def test_copy_baseline_to_multiple_immediate_predecessors(self):
+        # The test-win-win10 baseline is copied over to the test-linux-trusty
+        # and test-win-win7 baseline paths, since both of these are "immediate
+        # predecessors".
+        self._write(
+            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
+            'original test-win-win10 result')
+        self.assertFalse(self.tool.filesystem.exists(
+            self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')))
+
+        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
+
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt')),
+            'original test-win-win10 result')
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
+            'original test-win-win10 result')
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
+            'original test-win-win10 result')
+
+    def test_no_copy_existing_baseline(self):
+        # If a baseline exists already for an "immediate predecessor" baseline
+        # directory, (e.g. test-linux-trusty), then no "immediate successor"
+        # baselines (e.g. test-win-win10) are copied over.
+        self._write(
+            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
+            'original test-win-win10 result')
+        self._write(
+            self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt'),
+            'original test-linux-trusty result')
+
+        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
+
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt')),
+            'original test-win-win10 result')
+        self.assertEqual(
+            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
+            'original test-linux-trusty result')
+
+    def test_no_copy_skipped_test(self):
+        # If a test is skipped on some platform, no baselines are copied over
+        # to that directory. In this example, the test is skipped on linux,
+        # so the test-win-win10 baseline is not copied over.
+        port = self.tool.port_factory.get('test-win-win10')
+        self._write(
+            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
+            'original test-win-win10 result')
+        self._write(
+            port.path_to_generic_test_expectations_file(),
+            ('[ Win ] failures/expected/image.html [ Failure ]\n'
+             '[ Linux ] failures/expected/image.html [ Skip ]\n'))
+
+        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
+
+        self.assertFalse(
+            self.tool.filesystem.exists(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')))
+
+    def test_port_for_primary_baseline(self):
+        # Testing a protected method - pylint: disable=protected-access
+        self.assertEqual(self.command._port_for_primary_baseline('test-linux-trusty').name(), 'test-linux-trusty')
+        self.assertEqual(self.command._port_for_primary_baseline('test-mac-mac10.11').name(), 'test-mac-mac10.11')
+
+    def test_port_for_primary_baseline_not_found(self):
+        # Testing a protected method - pylint: disable=protected-access
+        with self.assertRaises(Exception):
+            self.command._port_for_primary_baseline('test-foo-foo4.7')
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
index 381697a..9745818 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
@@ -26,24 +26,19 @@
 # (INCLUDING NEGLIGENCE OR/ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from __future__ import print_function
 import collections
 import json
 import logging
 import optparse
 import re
-import sys
-import traceback
 
-from webkitpy.common.memoized import memoized
 from webkitpy.common.net.buildbot import Build
-from webkitpy.common.system.executive import ScriptError
+from webkitpy.layout_tests.models.test_expectations import BASELINE_SUFFIX_LIST
+from webkitpy.layout_tests.models.test_expectations import TestExpectations
 from webkitpy.layout_tests.models.testharness_results import is_all_pass_testharness_result
-from webkitpy.layout_tests.models.test_expectations import TestExpectations, BASELINE_SUFFIX_LIST, SKIP
 from webkitpy.layout_tests.port import factory
 from webkitpy.tool.commands.command import Command
 
-
 _log = logging.getLogger(__name__)
 
 
@@ -82,10 +77,7 @@
         self.expectation_line_changes = ChangeSet()
         self._tool = None
 
-    def _print_expectation_line_changes(self):
-        print(json.dumps(self.expectation_line_changes.to_dict()))
-
-    def _baseline_directory(self, builder_name):
+    def baseline_directory(self, builder_name):
         port = self._tool.port_factory.get_from_builder_name(builder_name)
         return port.baseline_version_dir()
 
@@ -199,187 +191,6 @@
         return self._builder_names
 
 
-class CopyExistingBaselinesInternal(AbstractRebaseliningCommand):
-    name = 'copy-existing-baselines-internal'
-    help_text = ('Copy existing baselines down one level in the baseline order to ensure '
-                 "new baselines don't break existing passing platforms.")
-
-    def __init__(self):
-        super(CopyExistingBaselinesInternal, self).__init__(options=[
-            self.test_option,
-            self.suffixes_option,
-            self.port_name_option,
-            self.results_directory_option,
-        ])
-
-    @memoized
-    def _immediate_predecessors_in_fallback(self, path_to_rebaseline):
-        """Returns the predecessor directories in the baseline fall-back graph.
-
-        The platform-specific fall-back baseline directories form a tree, where
-        when we search for baselines we normally fall back to parents nodes in
-        the tree. The "immediate predecessors" are the children nodes of the
-        given node.
-
-        For example, if the baseline fall-back graph includes:
-            "mac10.9" -> "mac10.10/"
-            "mac10.10/" -> "mac/"
-            "retina/" -> "mac/"
-        Then, the "immediate predecessors" are:
-            "mac/": ["mac10.10/", "retina/"]
-            "mac10.10/": ["mac10.9/"]
-            "mac10.9/", "retina/": []
-
-        Args:
-            path_to_rebaseline: The absolute path to a baseline directory.
-
-        Returns:
-            A list of directory names (not full paths) of directories that are
-            "immediate predecessors" of the given baseline path.
-        """
-        port_names = self._tool.port_factory.all_port_names()
-        immediate_predecessors = []
-        for port_name in port_names:
-            port = self._tool.port_factory.get(port_name)
-            baseline_search_path = port.baseline_search_path()
-            try:
-                index = baseline_search_path.index(path_to_rebaseline)
-                if index:
-                    immediate_predecessors.append(self._tool.filesystem.basename(baseline_search_path[index - 1]))
-            except ValueError:
-                # baseline_search_path.index() throws a ValueError if the item isn't in the list.
-                pass
-        return immediate_predecessors
-
-    def _port_for_primary_baseline(self, baseline):
-        """Returns a Port object for the given baseline directory base name."""
-        for port in [self._tool.port_factory.get(port_name) for port_name in self._tool.port_factory.all_port_names()]:
-            if self._tool.filesystem.basename(port.baseline_version_dir()) == baseline:
-                return port
-        raise Exception('Failed to find port for primary baseline %s.' % baseline)
-
-    def _copy_existing_baseline(self, port_name, test_name, suffix):
-        """Copies the baseline for the given builder to all "predecessor" directories."""
-        baseline_directory = self._tool.port_factory.get(port_name).baseline_version_dir()
-        ports = [self._port_for_primary_baseline(baseline)
-                 for baseline in self._immediate_predecessors_in_fallback(baseline_directory)]
-
-        old_baselines = []
-        new_baselines = []
-
-        # Need to gather all the baseline paths before modifying the filesystem since
-        # the modifications can affect the results of port.expected_filename.
-        for port in ports:
-            old_baseline = port.expected_filename(test_name, '.' + suffix)
-            if not self._tool.filesystem.exists(old_baseline):
-                _log.debug('No existing baseline for %s.', test_name)
-                continue
-
-            new_baseline = self._tool.filesystem.join(
-                port.baseline_version_dir(),
-                self._file_name_for_expected_result(test_name, suffix))
-            if self._tool.filesystem.exists(new_baseline):
-                _log.debug('Existing baseline at %s, not copying over it.', new_baseline)
-                continue
-
-            generic_expectations = TestExpectations(port, tests=[test_name], include_overrides=False)
-            full_expectations = TestExpectations(port, tests=[test_name], include_overrides=True)
-            # TODO(qyearsley): Change Port.skips_test so that this can be simplified.
-            if SKIP in full_expectations.get_expectations(test_name):
-                _log.debug('%s is skipped (perhaps temporarily) on %s.', test_name, port.name())
-                continue
-            if port.skips_test(test_name, generic_expectations, full_expectations):
-                _log.debug('%s is skipped on %s.', test_name, port.name())
-                continue
-
-            old_baselines.append(old_baseline)
-            new_baselines.append(new_baseline)
-
-        for i in range(len(old_baselines)):
-            old_baseline = old_baselines[i]
-            new_baseline = new_baselines[i]
-
-            _log.debug('Copying baseline from %s to %s.', old_baseline, new_baseline)
-            self._tool.filesystem.maybe_make_directory(self._tool.filesystem.dirname(new_baseline))
-            self._tool.filesystem.copyfile(old_baseline, new_baseline)
-
-    def execute(self, options, args, tool):
-        self._tool = tool
-        port_name = options.port_name
-        for suffix in options.suffixes.split(','):
-            self._copy_existing_baseline(port_name, options.test, suffix)
-
-
-class RebaselineTest(AbstractRebaseliningCommand):
-    name = 'rebaseline-test-internal'
-    help_text = 'Rebaseline a single test from a buildbot. Only intended for use by other webkit-patch commands.'
-
-    def __init__(self):
-        super(RebaselineTest, self).__init__(options=[
-            self.test_option,
-            self.suffixes_option,
-            self.port_name_option,
-            self.builder_option,
-            self.build_number_option,
-            self.results_directory_option,
-        ])
-
-    def _save_baseline(self, data, target_baseline):
-        if not data:
-            _log.debug('No baseline data to save.')
-            return
-
-        filesystem = self._tool.filesystem
-        filesystem.maybe_make_directory(filesystem.dirname(target_baseline))
-        filesystem.write_binary_file(target_baseline, data)
-
-    def _rebaseline_test(self, port_name, test_name, suffix, results_url):
-        """Downloads a baseline file and saves it to the filesystem.
-
-        Args:
-            port_name: The port that the baseline is for. This determines
-                the directory that the baseline is saved to.
-            test_name: The name of the test being rebaselined.
-            suffix: The baseline file extension (e.g. png); together with the
-                test name and results_url this determines what file to download.
-            results_url: Base URL to download the actual result from.
-        """
-        baseline_directory = self._tool.port_factory.get(port_name).baseline_version_dir()
-
-        source_baseline = '%s/%s' % (results_url, self._file_name_for_actual_result(test_name, suffix))
-        target_baseline = self._tool.filesystem.join(baseline_directory, self._file_name_for_expected_result(test_name, suffix))
-
-        _log.debug('Retrieving source %s for target %s.', source_baseline, target_baseline)
-        self._save_baseline(self._tool.web.get_binary(source_baseline, return_none_on_404=True),
-                            target_baseline)
-
-    def _rebaseline_test_and_update_expectations(self, options):
-        self._baseline_suffix_list = options.suffixes.split(',')
-
-        port_name = options.port_name or self._tool.builders.port_name_for_builder_name(options.builder)
-        port = self._tool.port_factory.get(port_name)
-
-        if port.reference_files(options.test):
-            if 'png' in self._baseline_suffix_list:
-                _log.warning('Cannot rebaseline image result for reftest: %s', options.test)
-                return
-            assert self._baseline_suffix_list == ['txt']
-
-        if options.results_directory:
-            results_url = 'file://' + options.results_directory
-        else:
-            results_url = self._tool.buildbot.results_url(options.builder, build_number=options.build_number)
-
-        for suffix in self._baseline_suffix_list:
-            self._rebaseline_test(port_name, options.test, suffix, results_url)
-        self.expectation_line_changes.remove_line(test=options.test, port_name=port_name)
-
-    def execute(self, options, args, tool):
-        self._tool = tool
-        self._rebaseline_test_and_update_expectations(options)
-        self._print_expectation_line_changes()
-
-
 class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
     """Base class for rebaseline commands that do some tasks in parallel."""
     # Not overriding execute() - pylint: disable=abstract-method
@@ -644,7 +455,7 @@
         baseline_paths = set()
         for test, build, _ in test_baseline_set:
             filenames = [self._file_name_for_expected_result(test, suffix) for suffix in BASELINE_SUFFIX_LIST]
-            port_baseline_dir = self._baseline_directory(build.builder_name)
+            port_baseline_dir = self.baseline_directory(build.builder_name)
             baseline_paths.update({filesystem.join(port_baseline_dir, filename) for filename in filenames})
             baseline_paths.update({filesystem.join(self._layout_tests_dir(), filename) for filename in filenames})
         return sorted(baseline_paths)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test.py
new file mode 100644
index 0000000..66d0305d
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test.py
@@ -0,0 +1,85 @@
+# 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 json
+import logging
+
+from webkitpy.tool.commands.rebaseline import AbstractRebaseliningCommand
+
+_log = logging.getLogger(__name__)
+
+
+class RebaselineTest(AbstractRebaseliningCommand):
+    name = 'rebaseline-test-internal'
+    help_text = 'Rebaseline a single test from a single builder.'
+
+    def __init__(self):
+        super(RebaselineTest, self).__init__(options=[
+            self.test_option,
+            self.suffixes_option,
+            self.port_name_option,
+            self.builder_option,
+            self.build_number_option,
+            self.results_directory_option,
+        ])
+
+    def execute(self, options, args, tool):
+        self._tool = tool
+        self._rebaseline_test_and_update_expectations(options)
+        self._print_expectation_line_changes()
+
+    def _rebaseline_test_and_update_expectations(self, options):
+        self._baseline_suffix_list = options.suffixes.split(',')
+
+        port_name = options.port_name or self._tool.builders.port_name_for_builder_name(options.builder)
+        port = self._tool.port_factory.get(port_name)
+
+        if port.reference_files(options.test):
+            if 'png' in self._baseline_suffix_list:
+                _log.warning('Cannot rebaseline image result for reftest: %s', options.test)
+                return
+            assert self._baseline_suffix_list == ['txt']
+
+        if options.results_directory:
+            results_url = 'file://' + options.results_directory
+        else:
+            results_url = self._tool.buildbot.results_url(options.builder, build_number=options.build_number)
+
+        for suffix in self._baseline_suffix_list:
+            self._rebaseline_test(port_name, options.test, suffix, results_url)
+        self.expectation_line_changes.remove_line(
+            test=options.test,
+            port_name=port_name)
+
+    def _save_baseline(self, data, target_baseline):
+        if not data:
+            _log.debug('No baseline data to save.')
+            return
+        filesystem = self._tool.filesystem
+        self._tool.filesystem.maybe_make_directory(filesystem.dirname(target_baseline))
+        self._tool.filesystem.write_binary_file(target_baseline, data)
+
+    def _rebaseline_test(self, port_name, test_name, suffix, results_url):
+        """Downloads a baseline file and saves it to the filesystem.
+
+        Args:
+            port_name: The port that the baseline is for. This determines
+                the directory that the baseline is saved to.
+            test_name: The name of the test being rebaselined.
+            suffix: The baseline file extension (e.g. png); together with the
+                test name and results_url this determines what file to download.
+            results_url: Base URL to download the actual result from.
+        """
+        baseline_directory = self._tool.port_factory.get(port_name).baseline_version_dir()
+
+        source_baseline = '%s/%s' % (results_url, self._file_name_for_actual_result(test_name, suffix))
+        target_baseline = self._tool.filesystem.join(baseline_directory, self._file_name_for_expected_result(test_name, suffix))
+
+        _log.debug('Retrieving source %s for target %s.', source_baseline, target_baseline)
+        self._save_baseline(
+            self._tool.web.get_binary(source_baseline, return_none_on_404=True),
+            target_baseline)
+
+    def _print_expectation_line_changes(self):
+        print json.dumps(self.expectation_line_changes.to_dict())
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test_unittest.py
new file mode 100644
index 0000000..cb38860
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_test_unittest.py
@@ -0,0 +1,124 @@
+# 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 optparse
+
+from webkitpy.common.system.executive_mock import MockExecutive
+from webkitpy.common.system.output_capture import OutputCapture
+from webkitpy.tool.commands.rebaseline_test import RebaselineTest
+from webkitpy.tool.commands.rebaseline_unittest import BaseTestCase
+
+
+class TestRebaselineTest(BaseTestCase):
+    command_constructor = RebaselineTest
+
+    @staticmethod
+    def options(**kwargs):
+        return optparse.Values(dict({
+            'builder': 'MOCK Mac10.11',
+            'port_name': None,
+            'test': 'userscripts/another-test.html',
+            'suffixes': 'txt',
+            'results_directory': None,
+            'build_number': None
+        }, **kwargs))
+
+    def test_rebaseline_test_internal_with_port_that_lacks_buildbot(self):
+        self.tool.executive = MockExecutive()
+
+        port = self.tool.port_factory.get('test-win-win7')
+        self._write(
+            port.host.filesystem.join(
+                port.layout_tests_dir(),
+                'platform/test-win-win10/failures/expected/image-expected.txt'),
+            'original win10 result')
+
+        oc = OutputCapture()
+        try:
+            options = optparse.Values({
+                'optimize': True,
+                'builder': 'MOCK Win10',
+                'port_name': None,
+                'suffixes': 'txt',
+                'verbose': True,
+                'test': 'failures/expected/image.html',
+                'results_directory': None,
+                'build_number': None
+            })
+            oc.capture_output()
+            self.command.execute(options, [], self.tool)
+        finally:
+            out, _, _ = oc.restore_output()
+
+        self.assertMultiLineEqual(
+            self._read(self.tool.filesystem.join(
+                port.layout_tests_dir(),
+                'platform/test-win-win10/failures/expected/image-expected.txt')),
+            'MOCK Web result, convert 404 to None=True')
+        self.assertFalse(self.tool.filesystem.exists(self.tool.filesystem.join(
+            port.layout_tests_dir(), 'platform/test-win-win7/failures/expected/image-expected.txt')))
+        self.assertMultiLineEqual(
+            out, '{"remove-lines": [{"test": "failures/expected/image.html", "port_name": "test-win-win10"}]}\n')
+
+    def test_baseline_directory(self):
+        self.assertMultiLineEqual(
+            self.command.baseline_directory('MOCK Mac10.11'),
+            '/test.checkout/LayoutTests/platform/test-mac-mac10.11')
+        self.assertMultiLineEqual(
+            self.command.baseline_directory('MOCK Mac10.10'),
+            '/test.checkout/LayoutTests/platform/test-mac-mac10.10')
+        self.assertMultiLineEqual(
+            self.command.baseline_directory('MOCK Trusty'),
+            '/test.checkout/LayoutTests/platform/test-linux-trusty')
+        self.assertMultiLineEqual(
+            self.command.baseline_directory('MOCK Precise'),
+            '/test.checkout/LayoutTests/platform/test-linux-precise')
+
+    def test_rebaseline_updates_expectations_file_noop(self):
+        # pylint: disable=protected-access
+        self._zero_out_test_expectations()
+        self._write(
+            self.mac_expectations_path,
+            ('Bug(B) [ Mac Linux Win7 Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]\n'
+             'Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]\n'))
+        self._write('fast/dom/Window/window-postmessage-clone-really-deep-array.html', 'Dummy test contents')
+        self._write('fast/css/large-list-of-rules-crash.html', 'Dummy test contents')
+        self._write('userscripts/another-test.html', 'Dummy test contents')
+
+        self.command._rebaseline_test_and_update_expectations(self.options(suffixes='png,wav,txt'))
+
+        self.assertItemsEqual(self.tool.web.urls_fetched,
+                              [self.WEB_PREFIX + '/userscripts/another-test-actual.png',
+                               self.WEB_PREFIX + '/userscripts/another-test-actual.wav',
+                               self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
+        new_expectations = self._read(self.mac_expectations_path)
+        self.assertMultiLineEqual(
+            new_expectations,
+            ('Bug(B) [ Mac Linux Win7 Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]\n'
+             'Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]\n'))
+
+    def test_rebaseline_test(self):
+        # pylint: disable=protected-access
+        self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX)
+        self.assertItemsEqual(
+            self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
+
+    def test_rebaseline_test_with_results_directory(self):
+        # pylint: disable=protected-access
+        self._write('userscripts/another-test.html', 'test data')
+        self._write(
+            self.mac_expectations_path,
+            ('Bug(x) [ Mac ] userscripts/another-test.html [ Failure ]\n'
+             'bug(z) [ Linux ] userscripts/another-test.html [ Failure ]\n'))
+        self.command._rebaseline_test_and_update_expectations(self.options(results_directory='/tmp'))
+        self.assertItemsEqual(self.tool.web.urls_fetched, ['file:///tmp/userscripts/another-test-actual.txt'])
+
+    def test_rebaseline_reftest(self):
+        # pylint: disable=protected-access
+        self._write('userscripts/another-test.html', 'test data')
+        self._write('userscripts/another-test-expected.html', 'generic result')
+        OutputCapture().assert_outputs(
+            self, self.command._rebaseline_test_and_update_expectations, args=[self.options(suffixes='png')],
+            expected_logs='Cannot rebaseline image result for reftest: userscripts/another-test.html\n')
+        self.assertDictEqual(self.command.expectation_line_changes.to_dict(), {'remove-lines': []})
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
index 9819a88..b5b7e1347 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
@@ -9,12 +9,11 @@
 from webkitpy.common.net.buildbot import Build
 from webkitpy.common.net.layout_test_results import LayoutTestResults
 from webkitpy.common.system.executive_mock import MockExecutive
-from webkitpy.common.system.output_capture import OutputCapture
 from webkitpy.layout_tests.builder_list import BuilderList
 from webkitpy.layout_tests.port.factory_mock import MockPortFactory
 from webkitpy.tool.commands.rebaseline import (
-    AbstractParallelRebaselineCommand, CopyExistingBaselinesInternal,
-    Rebaseline, RebaselineExpectations, RebaselineTest, TestBaselineSet
+    AbstractParallelRebaselineCommand, Rebaseline, RebaselineExpectations,
+    TestBaselineSet
 )
 from webkitpy.tool.mock_tool import MockWebKitPatch
 
@@ -105,225 +104,6 @@
             }))
 
 
-class TestCopyExistingBaselinesInternal(BaseTestCase):
-    command_constructor = CopyExistingBaselinesInternal
-
-    def setUp(self):
-        super(TestCopyExistingBaselinesInternal, self).setUp()
-
-    def options(self, **kwargs):
-        options_dict = {
-            'results_directory': None,
-            'suffixes': 'txt',
-            'verbose': False,
-            'port_name': None,
-        }
-        options_dict.update(kwargs)
-        return optparse.Values(options_dict)
-
-    def baseline_path(self, path_from_layout_test_dir):
-        port = self.tool.port_factory.get()
-        return self.tool.filesystem.join(port.layout_tests_dir(), path_from_layout_test_dir)
-
-    # The tests in this class all depend on the fall-back path graph
-    # that is set up in |TestPort.FALLBACK_PATHS|.
-
-    def test_copy_baseline_mac_newer_to_older_version(self):
-        # The test-mac-mac10.11 baseline is copied over to the test-mac-mac10.10
-        # baseline directory because test-mac-mac10.10 is the "immediate
-        # predecessor" in the fall-back graph.
-        self._write(
-            self.baseline_path('platform/test-mac-mac10.11/failures/expected/image-expected.txt'),
-            'original test-mac-mac10.11 result')
-        self.assertFalse(self.tool.filesystem.exists(
-            self.baseline_path('platform/test-mac-mac10.10/failures/expected/image-expected.txt')))
-
-        self.command.execute(self.options(port_name='test-mac-mac10.11', test='failures/expected/image.html'), [], self.tool)
-
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-mac-mac10.11/failures/expected/image-expected.txt')),
-            'original test-mac-mac10.11 result')
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-mac-mac10.10/failures/expected/image-expected.txt')),
-            'original test-mac-mac10.11 result')
-
-    def test_copy_baseline_to_multiple_immediate_predecessors(self):
-        # The test-win-win10 baseline is copied over to the test-linux-trusty
-        # and test-win-win7 baseline paths, since both of these are "immediate
-        # predecessors".
-        self._write(
-            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
-            'original test-win-win10 result')
-        self.assertFalse(self.tool.filesystem.exists(
-            self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')))
-
-        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
-
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt')),
-            'original test-win-win10 result')
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
-            'original test-win-win10 result')
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
-            'original test-win-win10 result')
-
-    def test_no_copy_existing_baseline(self):
-        # If a baseline exists already for an "immediate prdecessor" baseline
-        # directory, (e.g. test-linux-trusty), then no "immediate successor"
-        # baselines (e.g. test-win-win10) are copied over.
-        self._write(
-            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
-            'original test-win-win10 result')
-        self._write(
-            self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt'),
-            'original test-linux-trusty result')
-
-        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
-
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt')),
-            'original test-win-win10 result')
-        self.assertEqual(
-            self._read(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')),
-            'original test-linux-trusty result')
-
-    def test_no_copy_skipped_test(self):
-        # If a test is skipped on some platform, no baselines are copied over
-        # to that directory. In this example, the test is skipped on linux,
-        # so the test-win-win10 baseline is not copied over.
-        port = self.tool.port_factory.get('test-win-win10')
-        self._write(
-            self.baseline_path('platform/test-win-win10/failures/expected/image-expected.txt'),
-            'original test-win-win10 result')
-        self._write(
-            port.path_to_generic_test_expectations_file(),
-            ('[ Win ] failures/expected/image.html [ Failure ]\n'
-             '[ Linux ] failures/expected/image.html [ Skip ]\n'))
-
-        self.command.execute(self.options(port_name='test-win-win10', test='failures/expected/image.html'), [], self.tool)
-
-        self.assertFalse(
-            self.tool.filesystem.exists(self.baseline_path('platform/test-linux-trusty/failures/expected/image-expected.txt')))
-
-    def test_port_for_primary_baseline(self):
-        self.assertEqual(self.command._port_for_primary_baseline('test-linux-trusty').name(), 'test-linux-trusty')
-        self.assertEqual(self.command._port_for_primary_baseline('test-mac-mac10.11').name(), 'test-mac-mac10.11')
-
-    def test_port_for_primary_baseline_not_found(self):
-        with self.assertRaises(Exception):
-            self.command._port_for_primary_baseline('test-foo-foo4.7')
-
-
-class TestRebaselineTest(BaseTestCase):
-    command_constructor = RebaselineTest  # AKA webkit-patch rebaseline-test-internal
-
-    def setUp(self):
-        super(TestRebaselineTest, self).setUp()
-
-    @staticmethod
-    def options(**kwargs):
-        return optparse.Values(dict({
-            'builder': 'MOCK Mac10.11',
-            'port_name': None,
-            'test': 'userscripts/another-test.html',
-            'suffixes': 'txt',
-            'results_directory': None,
-            'build_number': None
-        }, **kwargs))
-
-    def test_baseline_directory(self):
-        command = self.command
-        self.assertMultiLineEqual(command._baseline_directory('MOCK Mac10.11'),
-                                  '/test.checkout/LayoutTests/platform/test-mac-mac10.11')
-        self.assertMultiLineEqual(command._baseline_directory('MOCK Mac10.10'),
-                                  '/test.checkout/LayoutTests/platform/test-mac-mac10.10')
-        self.assertMultiLineEqual(command._baseline_directory('MOCK Trusty'),
-                                  '/test.checkout/LayoutTests/platform/test-linux-trusty')
-        self.assertMultiLineEqual(command._baseline_directory('MOCK Precise'),
-                                  '/test.checkout/LayoutTests/platform/test-linux-precise')
-
-    def test_rebaseline_updates_expectations_file_noop(self):
-        self._zero_out_test_expectations()
-        self._write(
-            self.mac_expectations_path,
-            ('Bug(B) [ Mac Linux Win7 Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]\n'
-             'Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]\n'))
-        self._write('fast/dom/Window/window-postmessage-clone-really-deep-array.html', 'Dummy test contents')
-        self._write('fast/css/large-list-of-rules-crash.html', 'Dummy test contents')
-        self._write('userscripts/another-test.html', 'Dummy test contents')
-
-        self.command._rebaseline_test_and_update_expectations(self.options(suffixes='png,wav,txt'))
-
-        self.assertItemsEqual(self.tool.web.urls_fetched,
-                              [self.WEB_PREFIX + '/userscripts/another-test-actual.png',
-                               self.WEB_PREFIX + '/userscripts/another-test-actual.wav',
-                               self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
-        new_expectations = self._read(self.mac_expectations_path)
-        self.assertMultiLineEqual(
-            new_expectations,
-            ('Bug(B) [ Mac Linux Win7 Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]\n'
-             'Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]\n'))
-
-    def test_rebaseline_test(self):
-        self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX)
-        self.assertItemsEqual(self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
-
-    def test_rebaseline_test_with_results_directory(self):
-        self._write('userscripts/another-test.html', 'test data')
-        self._write(
-            self.mac_expectations_path,
-            ('Bug(x) [ Mac ] userscripts/another-test.html [ Failure ]\n'
-             'bug(z) [ Linux ] userscripts/another-test.html [ Failure ]\n'))
-        self.command._rebaseline_test_and_update_expectations(self.options(results_directory='/tmp'))
-        self.assertItemsEqual(self.tool.web.urls_fetched, ['file:///tmp/userscripts/another-test-actual.txt'])
-
-    def test_rebaseline_reftest(self):
-        self._write('userscripts/another-test.html', 'test data')
-        self._write('userscripts/another-test-expected.html', 'generic result')
-        OutputCapture().assert_outputs(
-            self, self.command._rebaseline_test_and_update_expectations, args=[self.options(suffixes='png')],
-            expected_logs='Cannot rebaseline image result for reftest: userscripts/another-test.html\n')
-        self.assertDictEqual(self.command.expectation_line_changes.to_dict(), {'remove-lines': []})
-
-    def test_rebaseline_test_internal_with_port_that_lacks_buildbot(self):
-        self.tool.executive = MockExecutive()
-
-        port = self.tool.port_factory.get('test-win-win7')
-        self._write(
-            port.host.filesystem.join(
-                port.layout_tests_dir(),
-                'platform/test-win-win10/failures/expected/image-expected.txt'),
-            'original win10 result')
-
-        oc = OutputCapture()
-        try:
-            options = optparse.Values({
-                'optimize': True,
-                'builder': 'MOCK Win10',
-                'port_name': None,
-                'suffixes': 'txt',
-                'verbose': True,
-                'test': 'failures/expected/image.html',
-                'results_directory': None,
-                'build_number': None
-            })
-            oc.capture_output()
-            self.command.execute(options, [], self.tool)
-        finally:
-            out, _, _ = oc.restore_output()
-
-        self.assertMultiLineEqual(
-            self._read(self.tool.filesystem.join(
-                port.layout_tests_dir(),
-                'platform/test-win-win10/failures/expected/image-expected.txt')),
-            'MOCK Web result, convert 404 to None=True')
-        self.assertFalse(self.tool.filesystem.exists(self.tool.filesystem.join(
-            port.layout_tests_dir(), 'platform/test-win-win7/failures/expected/image-expected.txt')))
-        self.assertMultiLineEqual(
-            out, '{"remove-lines": [{"test": "failures/expected/image.html", "port_name": "test-win-win10"}]}\n')
-
 
 class TestAbstractParallelRebaselineCommand(BaseTestCase):
     command_constructor = AbstractParallelRebaselineCommand
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/webkit_patch.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/webkit_patch.py
index 0968e2c..267ffed 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/webkit_patch.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/webkit_patch.py
@@ -29,10 +29,11 @@
 
 """Webkit-patch is a tool with multiple sub-commands with different purposes.
 
-Historically, it had commands related to dealing with bugzilla, and posting
-and comitting patches to WebKit. More recently, it has commands for printing
-expectations, fetching new test baselines, starting a commit-announcer IRC bot,
-etc. These commands don't necessarily have anything to do with each other.
+Historically, it had commands related to dealing with bugzilla and posting
+and committing patches to WebKit. More recently, it has commands for printing
+expectations, fetching new test baselines, etc.
+
+These commands don't necessarily have anything to do with each other.
 """
 
 import logging
@@ -43,6 +44,7 @@
 from webkitpy.tool.commands.analyze_baselines import AnalyzeBaselines
 from webkitpy.tool.commands.auto_rebaseline import AutoRebaseline
 from webkitpy.tool.commands.command import HelpPrintingOptionParser
+from webkitpy.tool.commands.copy_existing_baselines import CopyExistingBaselines
 from webkitpy.tool.commands.flaky_tests import FlakyTests
 from webkitpy.tool.commands.help_command import HelpCommand
 from webkitpy.tool.commands.layout_tests_server import LayoutTestsServer
@@ -51,12 +53,11 @@
 from webkitpy.tool.commands.queries import CrashLog
 from webkitpy.tool.commands.queries import PrintBaselines
 from webkitpy.tool.commands.queries import PrintExpectations
-from webkitpy.tool.commands.rebaseline import CopyExistingBaselinesInternal
 from webkitpy.tool.commands.rebaseline import Rebaseline
 from webkitpy.tool.commands.rebaseline import RebaselineExpectations
-from webkitpy.tool.commands.rebaseline import RebaselineTest
 from webkitpy.tool.commands.rebaseline_cl import RebaselineCL
 from webkitpy.tool.commands.rebaseline_server import RebaselineServer
+from webkitpy.tool.commands.rebaseline_test import RebaselineTest
 
 
 _log = logging.getLogger(__name__)
@@ -81,7 +82,7 @@
         self.commands = [
             AnalyzeBaselines(),
             AutoRebaseline(),
-            CopyExistingBaselinesInternal(),
+            CopyExistingBaselines(),
             CrashLog(),
             FlakyTests(),
             LayoutTestsServer(),
diff --git a/tools/ipc_fuzzer/message_replay/replay_process.cc b/tools/ipc_fuzzer/message_replay/replay_process.cc
index 10173728..325382e 100644
--- a/tools/ipc_fuzzer/message_replay/replay_process.cc
+++ b/tools/ipc_fuzzer/message_replay/replay_process.cc
@@ -17,7 +17,6 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/mojo_channel_switches.h"
 #include "ipc/ipc_channel_mojo.h"
-#include "ipc/ipc_descriptors.h"
 #include "mojo/edk/embedder/configuration.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "mojo/edk/embedder/incoming_broker_client_invitation.h"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dcfc5ee0..1bf8fe4 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2046,6 +2046,17 @@
   <int value="5" label="Permission denied"/>
 </enum>
 
+<enum name="BackgroundTaskId" type="int">
+  <int value="0" label="Test task (should not appear)"/>
+  <int value="1" label="Omaha"/>
+  <int value="2" label="GCM Background Task"/>
+  <int value="3" label="Notification service task"/>
+  <int value="4" label="Webview minidump uploading task"/>
+  <int value="5" label="Chrome minidump uploading task"/>
+  <int value="6" label="Offlining pages task"/>
+  <int value="7" label="Offline page prefetch task"/>
+</enum>
+
 <enum name="BackgroundTracingState" type="int">
   <int value="0" label="Scenario activation requested"/>
   <int value="1" label="Scenario successfully activated"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ceeaeb1..b398515 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -558,6 +558,48 @@
   </summary>
 </histogram>
 
+<histogram name="Android.BackgroundTaskScheduler.TaskCanceled"
+    enum="BackgroundTaskId">
+  <owner>fgorski@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>Records that a specific background task has been canceled.</summary>
+</histogram>
+
+<histogram name="Android.BackgroundTaskScheduler.TaskScheduled.Failure"
+    enum="BackgroundTaskId">
+  <owner>fgorski@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    Records that a specific background task has failed to be scheduled.
+  </summary>
+</histogram>
+
+<histogram name="Android.BackgroundTaskScheduler.TaskScheduled.Success"
+    enum="BackgroundTaskId">
+  <owner>fgorski@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    Records that a specific background task has been successfully scheduled.
+  </summary>
+</histogram>
+
+<histogram name="Android.BackgroundTaskScheduler.TaskStarted"
+    enum="BackgroundTaskId">
+  <owner>fgorski@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>Records that a specific background task has been started.</summary>
+</histogram>
+
+<histogram name="Android.BackgroundTaskScheduler.TaskStopped"
+    enum="BackgroundTaskId">
+  <owner>fgorski@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    Records that a specific background task has been stopped by Background Task
+    Scheduler before it was able to complete itself.
+  </summary>
+</histogram>
+
 <histogram name="Android.ChromeHome.DurationOpen" units="ms">
   <owner>mdjones@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
@@ -46794,6 +46836,33 @@
   </summary>
 </histogram>
 
+<histogram name="Omnibox.ClipboardSuggestionShownNumTimes">
+  <owner>mpearson@chromium.org</owner>
+  <summary>
+    Recorded every time the omnibox is focussed and a recent URL from the user's
+    clipboard is suggested.  The number emitted is the number of times the URL
+    has been suggested within the same session including the current time. Thus,
+    the third time it is shown, we'll emit a three to this histogram, and this
+    histogram will have previously seen emits of one and two.  If the clipboard
+    content was the same during a previous run of Chrome and this URL was
+    suggested during that run, those impressions are not counted.  Also, if the
+    clipboard content changes during a particular run of Chrome to another URL,
+    the omnibox is focused and that URL is suggested, then content changes back
+    and Chrome starts suggesting the older URL again, the counts start again
+    from scratch. Chrome only remembers the number of times the URL was shown
+    consecutively.
+
+    This value is useful to compare with the number of times a clipboard
+    suggestion has been shown when it is clicked.  This value can be obtained
+    from OmniboxEvent records in which the selected suggestion is from Clipboard
+    provider.  In those cases, look in the Clipboard provider's ProviderInfo
+    field for |times_returned_results_in_session|.  Note that at the time of
+    this writing that OmniboxEvent logs aren't recorded in incognito whereas
+    histograms are.  Thus, the total counts will not be comparable, though the
+    distributions should be.
+  </summary>
+</histogram>
+
 <histogram name="Omnibox.ClipboardSuggestionShownWithCurrentURL"
     enum="BooleanPresent">
   <owner>mpearson@chromium.org</owner>
@@ -66228,6 +66297,26 @@
   </summary>
 </histogram>
 
+<histogram name="Search.ContextualSearchTapLongWordSeen"
+    enum="ContextualSearchResultsSeen">
+  <owner>donnd@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <summary>
+    Whether results were seen for a Tap that was on a word considered long.
+    Recorded when the UX is hidden. Implemented for Android.
+  </summary>
+</histogram>
+
+<histogram name="Search.ContextualSearchTapShortWordSeen"
+    enum="ContextualSearchResultsSeen">
+  <owner>donnd@chromium.org</owner>
+  <owner>twellington@chromium.org</owner>
+  <summary>
+    Whether results were seen for a Tap that was on a word considered short.
+    Recorded when the UX is hidden. Implemented for Android.
+  </summary>
+</histogram>
+
 <histogram name="Search.ContextualSearchTapsSinceOpenDecided" units="taps">
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
diff --git a/tools/perf/benchmarks/dromaeo.py b/tools/perf/benchmarks/dromaeo.py
index a9a5fbbb..551db0c 100644
--- a/tools/perf/benchmarks/dromaeo.py
+++ b/tools/perf/benchmarks/dromaeo.py
@@ -141,6 +141,11 @@
   def Name(cls):
     return 'dromaeo.domcoreattr'
 
+  def GetExpectations(self):
+    class StoryExpectations(story.expectations.StoryExpectations):
+      def SetExpectations(self):
+        pass # http://dromaeo.com?dom-attr not disabled.
+    return StoryExpectations()
 
 @benchmark.Owner(emails=['yukishiino@chromium.org',
                          'bashi@chromium.org',
@@ -157,6 +162,12 @@
   def Name(cls):
     return 'dromaeo.domcoremodify'
 
+  def GetExpectations(self):
+    class StoryExpectations(story.expectations.StoryExpectations):
+      def SetExpectations(self):
+        pass # http://dromaeo.com?dom-modify not disabled.
+    return StoryExpectations()
+
 
 @benchmark.Owner(emails=['yukishiino@chromium.org',
                          'bashi@chromium.org',
@@ -173,6 +184,12 @@
   def Name(cls):
     return 'dromaeo.domcorequery'
 
+  def GetExpectations(self):
+    class StoryExpectations(story.expectations.StoryExpectations):
+      def SetExpectations(self):
+        pass # http://dromaeo.com?dom-query not disabled.
+    return StoryExpectations()
+
 
 @benchmark.Owner(emails=['yukishiino@chromium.org',
                          'bashi@chromium.org',
@@ -188,3 +205,9 @@
   @classmethod
   def Name(cls):
     return 'dromaeo.domcoretraverse'
+
+  def GetExpectations(self):
+    class StoryExpectations(story.expectations.StoryExpectations):
+      def SetExpectations(self):
+        pass # http://dromaeo.com?dom-traverse not disabled.
+    return StoryExpectations()
diff --git a/ui/app_list/views/app_list_item_view.cc b/ui/app_list/views/app_list_item_view.cc
index 488aef3..b3053fb 100644
--- a/ui/app_list/views/app_list_item_view.cc
+++ b/ui/app_list/views/app_list_item_view.cc
@@ -476,13 +476,13 @@
   title_->SetSubpixelRenderingEnabled(enable_aa);
   if (enable_aa) {
     title_->SetBackgroundColor(app_list::kLabelBackgroundColor);
-    title_->set_background(views::Background::CreateSolidBackground(
-        app_list::kLabelBackgroundColor));
+    title_->SetBackground(
+        views::CreateSolidBackground(app_list::kLabelBackgroundColor));
   } else {
     // In other cases, keep the background transparent to ensure correct
     // interactions with animations. This will temporarily disable subpixel AA.
     title_->SetBackgroundColor(0);
-    title_->set_background(NULL);
+    title_->SetBackground(nullptr);
   }
   title_->SchedulePaint();
 }
diff --git a/ui/app_list/views/search_box_view.cc b/ui/app_list/views/search_box_view.cc
index c7971ee..420aede 100644
--- a/ui/app_list/views/search_box_view.cc
+++ b/ui/app_list/views/search_box_view.cc
@@ -136,7 +136,7 @@
                                   views::ImageButton::ALIGN_MIDDLE);
   SetBackButtonLabel(false);
   content_container_->AddChildView(back_button_);
-  content_container_->set_background(new SearchBoxBackground());
+  content_container_->SetBackground(base::MakeUnique<SearchBoxBackground>());
 
   views::BoxLayout* layout =
       new views::BoxLayout(views::BoxLayout::kHorizontal, kPadding, 0,
diff --git a/ui/app_list/views/search_result_answer_card_view.cc b/ui/app_list/views/search_result_answer_card_view.cc
index 5991853..43ac790 100644
--- a/ui/app_list/views/search_result_answer_card_view.cc
+++ b/ui/app_list/views/search_result_answer_card_view.cc
@@ -62,15 +62,11 @@
 
  private:
   void UpdateBackgroundColor() {
-    views::Background* background = nullptr;
+    if (selected_)
+      SetBackground(views::CreateSolidBackground(kSelectedColor));
+    else if (state() == STATE_HOVERED || state() == STATE_PRESSED)
+      SetBackground(views::CreateSolidBackground(kHighlightedColor));
 
-    if (selected_) {
-      background = views::Background::CreateSolidBackground(kSelectedColor);
-    } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
-      background = views::Background::CreateSolidBackground(kHighlightedColor);
-    }
-
-    set_background(background);
     SchedulePaint();
   }
 
diff --git a/ui/app_list/views/search_result_list_view.cc b/ui/app_list/views/search_result_list_view.cc
index 3446a2a..44a58346 100644
--- a/ui/app_list/views/search_result_list_view.cc
+++ b/ui/app_list/views/search_result_list_view.cc
@@ -46,8 +46,8 @@
     results_container_->AddChildView(new SearchResultView(this));
   AddChildView(results_container_);
 
-  auto_launch_indicator_->set_background(
-      views::Background::CreateSolidBackground(kTimeoutIndicatorColor));
+  auto_launch_indicator_->SetBackground(
+      views::CreateSolidBackground(kTimeoutIndicatorColor));
   auto_launch_indicator_->SetVisible(false);
 
   AddChildView(auto_launch_indicator_);
diff --git a/ui/app_list/views/search_result_page_view.cc b/ui/app_list/views/search_result_page_view.cc
index 61d8fa5..20ab5e4 100644
--- a/ui/app_list/views/search_result_page_view.cc
+++ b/ui/app_list/views/search_result_page_view.cc
@@ -40,8 +40,8 @@
     SetBorder(base::MakeUnique<views::ShadowBorder>(
         GetShadowForZHeight(kSearchResultZHeight)));
     SetLayoutManager(new views::FillLayout());
-    content_view->set_background(
-        views::Background::CreateSolidBackground(kCardBackgroundColor));
+    content_view->SetBackground(
+        views::CreateSolidBackground(kCardBackgroundColor));
     AddChildView(content_view);
   }
 
diff --git a/ui/app_list/views/speech_view.cc b/ui/app_list/views/speech_view.cc
index 0625e615..419b9f4 100644
--- a/ui/app_list/views/speech_view.cc
+++ b/ui/app_list/views/speech_view.cc
@@ -121,8 +121,7 @@
   // actually has a single child of 'container' which has white background and
   // contains all components.
   views::View* container = new views::View();
-  container->set_background(
-      views::Background::CreateSolidBackground(SK_ColorWHITE));
+  container->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
 
   const gfx::ImageSkia& logo_image = delegate_->GetSpeechUI()->logo();
   if (!logo_image.isNull()) {
diff --git a/ui/app_list/views/start_page_view.cc b/ui/app_list/views/start_page_view.cc
index b41a9ffa..7206b6f6 100644
--- a/ui/app_list/views/start_page_view.cc
+++ b/ui/app_list/views/start_page_view.cc
@@ -62,7 +62,7 @@
       const std::string& custom_launcher_page_name)
       : selected_(false),
         custom_launcher_page_name_(custom_launcher_page_name) {
-    set_background(views::Background::CreateSolidBackground(kSelectedColor));
+    SetBackground(views::CreateSolidBackground(kSelectedColor));
   }
   ~CustomLauncherPageBackgroundView() override {}
 
@@ -133,8 +133,7 @@
     : contents_view_(contents_view),
       view_delegate_(view_delegate),
       all_apps_button_(all_apps_button) {
-  set_background(
-      views::Background::CreateSolidBackground(kLabelBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kLabelBackgroundColor));
   all_apps_button_->SetHoverStyle(TileItemView::HOVER_STYLE_ANIMATE_SHADOW);
   all_apps_button_->SetParentBackgroundColor(kLabelBackgroundColor);
   CreateAppsGrid(kNumStartPageTiles);
diff --git a/ui/app_list/views/tile_item_view.cc b/ui/app_list/views/tile_item_view.cc
index d2beee07..29da844 100644
--- a/ui/app_list/views/tile_item_view.cc
+++ b/ui/app_list/views/tile_item_view.cc
@@ -114,12 +114,12 @@
 }
 
 void TileItemView::UpdateBackgroundColor() {
-  views::Background* background = nullptr;
+  std::unique_ptr<views::Background> background;
   SkColor background_color = parent_background_color_;
 
   if (selected_) {
     background_color = kSelectedColor;
-    background = views::Background::CreateSolidBackground(background_color);
+    background = views::CreateSolidBackground(background_color);
   } else if (image_shadow_animator_) {
     if (state() == STATE_HOVERED || state() == STATE_PRESSED)
       image_shadow_animator_->animation()->Show();
@@ -127,7 +127,7 @@
       image_shadow_animator_->animation()->Hide();
   } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
     background_color = kHighlightedColor;
-    background = views::Background::CreateSolidBackground(background_color);
+    background = views::CreateSolidBackground(background_color);
   }
 
   // Tell the label what color it will be drawn onto. It will use whether the
@@ -135,7 +135,7 @@
   // rendering. Does not actually set the label's background color.
   title_->SetBackgroundColor(background_color);
 
-  set_background(background);
+  SetBackground(std::move(background));
   SchedulePaint();
 }
 
diff --git a/ui/arc/notification/arc_notification_content_view.cc b/ui/arc/notification/arc_notification_content_view.cc
index 312dcb9..a8c8254 100644
--- a/ui/arc/notification/arc_notification_content_view.cc
+++ b/ui/arc/notification/arc_notification_content_view.cc
@@ -213,10 +213,10 @@
     ArcNotificationContentView* owner)
     : message_center::PaddedButton(owner), owner_(owner) {
   if (owner_->item_) {
-    set_background(views::Background::CreateSolidBackground(
+    SetBackground(views::CreateSolidBackground(
         GetControlButtonBackgroundColor(owner_->item_->GetShownContents())));
   } else {
-    set_background(views::Background::CreateSolidBackground(
+    SetBackground(views::CreateSolidBackground(
         message_center::kControlButtonBackgroundColor));
   }
 }
@@ -720,13 +720,12 @@
     const SkColor current_color = gfx::Tween::ColorValueBetween(
         animation->GetCurrentValue(), start, target);
     if (settings_button_) {
-      settings_button_->set_background(
-          views::Background::CreateSolidBackground(current_color));
+      settings_button_->SetBackground(
+          views::CreateSolidBackground(current_color));
       settings_button_->SchedulePaint();
     }
     if (close_button_) {
-      close_button_->set_background(
-          views::Background::CreateSolidBackground(current_color));
+      close_button_->SetBackground(views::CreateSolidBackground(current_color));
       close_button_->SchedulePaint();
     }
   }
diff --git a/ui/arc/notification/arc_notification_view_unittest.cc b/ui/arc/notification/arc_notification_view_unittest.cc
index 81f8b477..3c81560 100644
--- a/ui/arc/notification/arc_notification_view_unittest.cc
+++ b/ui/arc/notification/arc_notification_view_unittest.cc
@@ -36,7 +36,7 @@
  public:
   TestNotificationContentsView() {
     SetFocusBehavior(FocusBehavior::ALWAYS);
-    set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+    SetBackground(views::CreateSolidBackground(kBackgroundColor));
     SetPreferredSize(gfx::Size(100, 100));
   }
   ~TestNotificationContentsView() override = default;
diff --git a/ui/base/cocoa/hover_button.h b/ui/base/cocoa/hover_button.h
index 1c5923f..b25e29aa 100644
--- a/ui/base/cocoa/hover_button.h
+++ b/ui/base/cocoa/hover_button.h
@@ -27,10 +27,15 @@
  @private
   // Tracking area for button mouseover states. Nil if not enabled.
   ui::ScopedCrTrackingArea trackingArea_;
+  BOOL mouseDown_;
 }
 
 @property(nonatomic) HoverState hoverState;
 
+// Common initialization called from initWithFrame: and awakeFromNib.
+// Subclassers should call [super commonInit].
+- (void)commonInit;
+
 // Text that would be announced by screen readers.
 - (void)setAccessibilityTitle:(NSString*)accessibilityTitle;
 
diff --git a/ui/base/cocoa/hover_button.mm b/ui/base/cocoa/hover_button.mm
index e763c0cd..ebcd582 100644
--- a/ui/base/cocoa/hover_button.mm
+++ b/ui/base/cocoa/hover_button.mm
@@ -10,14 +10,16 @@
 
 - (id)initWithFrame:(NSRect)frameRect {
   if ((self = [super initWithFrame:frameRect])) {
-    [self setTrackingEnabled:YES];
-    hoverState_ = kHoverStateNone;
-    [self updateTrackingAreas];
+    [self commonInit];
   }
   return self;
 }
 
 - (void)awakeFromNib {
+  [self commonInit];
+}
+
+- (void)commonInit {
   [self setTrackingEnabled:YES];
   self.hoverState = kHoverStateNone;
   [self updateTrackingAreas];
@@ -33,30 +35,53 @@
     self.hoverState = kHoverStateMouseOver;
 }
 
+- (void)mouseMoved:(NSEvent*)theEvent {
+  [self checkImageState];
+}
+
 - (void)mouseExited:(NSEvent*)theEvent {
   if (trackingArea_.get())
     self.hoverState = kHoverStateNone;
 }
 
-- (void)mouseMoved:(NSEvent*)theEvent {
-  [self checkImageState];
-}
-
 - (void)mouseDown:(NSEvent*)theEvent {
+  mouseDown_ = YES;
   self.hoverState = kHoverStateMouseDown;
+
   // The hover button needs to hold onto itself here for a bit.  Otherwise,
-  // it can be freed while |super mouseDown:| is in its loop, and the
-  // |checkImageState| call will crash.
+  // it can be freed while in the tracking loop below.
   // http://crbug.com/28220
   base::scoped_nsobject<HoverButton> myself([self retain]);
 
-  [super mouseDown:theEvent];
-  // We need to check the image state after the mouseDown event loop finishes.
-  // It's possible that we won't get a mouseExited event if the button was
-  // moved under the mouse during tab resize, instead of the mouse moving over
-  // the button.
-  // http://crbug.com/31279
-  [self checkImageState];
+  // Begin tracking the mouse.
+  if ([theEvent type] == NSLeftMouseDown) {
+    NSWindow* window = [self window];
+    NSEvent* nextEvent = nil;
+
+    // For the tracking loop ignore key events so that they don't pile up in
+    // the queue and get processed after the user releases the mouse.
+    const NSEventMask eventMask = (NSLeftMouseDraggedMask | NSLeftMouseUpMask |
+                                   NSKeyDownMask | NSKeyUpMask);
+
+    while ((nextEvent = [window nextEventMatchingMask:eventMask])) {
+      // Update the image state, which will change if the user moves the mouse
+      // into or out of the button.
+      [self checkImageState];
+
+      if ([nextEvent type] == NSLeftMouseUp) {
+        break;
+      }
+    }
+  }
+
+  // If the mouse is still over the button, it means the user clicked the
+  // button.
+  if (self.hoverState == kHoverStateMouseDown) {
+    [self performClick:nil];
+  }
+
+  // Clean up.
+  mouseDown_ = NO;
 }
 
 - (void)setAccessibilityTitle:(NSString*)accessibilityTitle {
@@ -108,8 +133,12 @@
   // Update the button's state if the button has moved.
   NSPoint mouseLoc = [[self window] mouseLocationOutsideOfEventStream];
   mouseLoc = [self convertPoint:mouseLoc fromView:nil];
-  self.hoverState = NSPointInRect(mouseLoc, [self bounds]) ?
-      kHoverStateMouseOver : kHoverStateNone;
+  BOOL mouseInBounds = NSPointInRect(mouseLoc, [self bounds]);
+  if (mouseDown_ && mouseInBounds) {
+    self.hoverState = kHoverStateMouseDown;
+  } else {
+    self.hoverState = mouseInBounds ? kHoverStateMouseOver : kHoverStateNone;
+  }
 }
 
 - (void)setHoverState:(HoverState)state {
diff --git a/ui/chromeos/ime/candidate_view.cc b/ui/chromeos/ime/candidate_view.cc
index 0544fb7e..26e78c0 100644
--- a/ui/chromeos/ime/candidate_view.cc
+++ b/ui/chromeos/ime/candidate_view.cc
@@ -86,8 +86,8 @@
         0x40);
     SkColor transparent_blakish = color_utils::AlphaBlend(
         SK_ColorTRANSPARENT, blackish, 0xE0);
-    shortcut_label->set_background(
-        views::Background::CreateSolidBackground(transparent_blakish));
+    shortcut_label->SetBackground(
+        views::CreateSolidBackground(transparent_blakish));
   }
   shortcut_label->SetElideBehavior(gfx::NO_ELIDE);
 
@@ -163,9 +163,8 @@
 
   if (orientation == ui::CandidateWindow::VERTICAL) {
     infolist_icon_ = new views::View;
-    infolist_icon_->set_background(
-        views::Background::CreateSolidBackground(theme.GetSystemColor(
-            ui::NativeTheme::kColorId_FocusedBorderColor)));
+    infolist_icon_->SetBackground(views::CreateSolidBackground(
+        theme.GetSystemColor(ui::NativeTheme::kColorId_FocusedBorderColor)));
     AddChildView(infolist_icon_);
   }
 }
@@ -204,9 +203,8 @@
   highlighted_ = highlighted;
   if (highlighted) {
     ui::NativeTheme* theme = GetNativeTheme();
-    set_background(
-        views::Background::CreateSolidBackground(theme->GetSystemColor(
-            ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
+    SetBackground(views::CreateSolidBackground(theme->GetSystemColor(
+        ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
     SetBorder(views::CreateSolidBorder(
         1,
         theme->GetSystemColor(ui::NativeTheme::kColorId_FocusedBorderColor)));
@@ -219,7 +217,7 @@
         view->SetHighlighted(false);
     }
   } else {
-    set_background(NULL);
+    SetBackground(nullptr);
     SetBorder(views::CreateEmptyBorder(1, 1, 1, 1));
   }
   SchedulePaint();
diff --git a/ui/chromeos/ime/candidate_window_view.cc b/ui/chromeos/ime/candidate_window_view.cc
index 9029c6c..77dd05a19 100644
--- a/ui/chromeos/ime/candidate_window_view.cc
+++ b/ui/chromeos/ime/candidate_window_view.cc
@@ -107,7 +107,7 @@
 
     SetLayoutManager(new views::FillLayout());
     AddChildView(label_);
-    set_background(views::Background::CreateSolidBackground(
+    SetBackground(views::CreateSolidBackground(
         color_utils::AlphaBlend(SK_ColorBLACK,
                                 GetNativeTheme()->GetSystemColor(
                                     ui::NativeTheme::kColorId_WindowBackground),
diff --git a/ui/chromeos/ime/infolist_window.cc b/ui/chromeos/ime/infolist_window.cc
index 3a85d8ed..6ca2381 100644
--- a/ui/chromeos/ime/infolist_window.cc
+++ b/ui/chromeos/ime/infolist_window.cc
@@ -152,14 +152,13 @@
 
 void InfolistEntryView::UpdateBackground() {
   if (entry_.highlighted) {
-    set_background(
-      views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
-          ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
+    SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+        ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
     SetBorder(views::CreateSolidBorder(
         1, GetNativeTheme()->GetSystemColor(
                ui::NativeTheme::kColorId_FocusedBorderColor)));
   } else {
-    set_background(NULL);
+    SetBackground(nullptr);
     SetBorder(views::CreateEmptyBorder(1, 1, 1, 1));
   }
   SchedulePaint();
@@ -179,9 +178,8 @@
   set_accept_events(false);
   set_margins(gfx::Insets());
 
-  set_background(
-      views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
-          ui::NativeTheme::kColorId_WindowBackground)));
+  SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_WindowBackground)));
   SetBorder(views::CreateSolidBorder(
       1, GetNativeTheme()->GetSystemColor(
              ui::NativeTheme::kColorId_MenuBorderColor)));
@@ -194,7 +192,7 @@
   caption_label->SetEnabledColor(GetNativeTheme()->GetSystemColor(
       ui::NativeTheme::kColorId_LabelEnabledColor));
   caption_label->SetBorder(views::CreateEmptyBorder(2, 2, 2, 2));
-  caption_label->set_background(views::Background::CreateSolidBackground(
+  caption_label->SetBackground(views::CreateSolidBackground(
       color_utils::AlphaBlend(SK_ColorBLACK,
                               GetNativeTheme()->GetSystemColor(
                                   ui::NativeTheme::kColorId_WindowBackground),
diff --git a/ui/login/account_picker/md_user_pod_row.js b/ui/login/account_picker/md_user_pod_row.js
index 5ba3286..9316cc9 100644
--- a/ui/login/account_picker/md_user_pod_row.js
+++ b/ui/login/account_picker/md_user_pod_row.js
@@ -3122,6 +3122,7 @@
       this.activatedPod_ = undefined;
       this.lastFocusedPod_ = undefined;
       this.mainPod_ = undefined;
+      this.smallPodsContainer.innerHTML = '';
 
       // Switch off animation
       Oobe.getInstance().toggleClass('flying-pods', false);
diff --git a/ui/message_center/views/message_center_button_bar.cc b/ui/message_center/views/message_center_button_bar.cc
index 5b8ef31..19538d1 100644
--- a/ui/message_center/views/message_center_button_bar.cc
+++ b/ui/message_center/views/message_center_button_bar.cc
@@ -83,8 +83,7 @@
       settings_button_(NULL),
       quiet_mode_button_(NULL) {
   SetPaintToLayer();
-  set_background(
-      views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor));
 
   ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
 
diff --git a/ui/message_center/views/message_center_view.cc b/ui/message_center/views/message_center_view.cc
index d760442..5a183bf 100644
--- a/ui/message_center/views/message_center_view.cc
+++ b/ui/message_center/views/message_center_view.cc
@@ -80,8 +80,7 @@
       focus_manager_(nullptr) {
   message_center_->AddObserver(this);
   set_notify_enter_exit_on_child(true);
-  set_background(views::Background::CreateSolidBackground(
-      kMessageCenterBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor));
 
   NotifierSettingsProvider* notifier_settings_provider =
       message_center_->GetNotifierSettingsProvider();
@@ -96,8 +95,8 @@
   scroller_->ClipHeightTo(kMinScrollViewHeight, max_height - button_height);
   scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
   scroller_->SetHorizontalScrollBar(new views::OverlayScrollBar(true));
-  scroller_->set_background(
-      views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
+  scroller_->SetBackground(
+      views::CreateSolidBackground(kMessageCenterBackgroundColor));
 
   scroller_->SetPaintToLayer();
   scroller_->layer()->SetFillsBoundsOpaquely(false);
diff --git a/ui/message_center/views/message_list_view.cc b/ui/message_center/views/message_list_view.cc
index 127f3fd..a5c77aa 100644
--- a/ui/message_center/views/message_list_view.cc
+++ b/ui/message_center/views/message_list_view.cc
@@ -44,8 +44,7 @@
   // because of the shadow of message view. Use an empty border instead
   // to provide this margin.
   gfx::Insets shadow_insets = MessageView::GetShadowInsets();
-  set_background(
-      views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor));
   SetBorder(views::CreateEmptyBorder(
       kMarginBetweenItems - shadow_insets.top(), /* top */
       kMarginBetweenItems - shadow_insets.left(), /* left */
diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc
index a951c10a..ac2dc30 100644
--- a/ui/message_center/views/message_view.cc
+++ b/ui/message_center/views/message_view.cc
@@ -73,8 +73,8 @@
 
   // Create the opaque background that's above the view's shadow.
   background_view_ = new views::View();
-  background_view_->set_background(
-      views::Background::CreateSolidBackground(kNotificationBackgroundColor));
+  background_view_->SetBackground(
+      views::CreateSolidBackground(kNotificationBackgroundColor));
   AddChildView(background_view_);
 
   focus_painter_ = views::Painter::CreateSolidFocusPainter(
diff --git a/ui/message_center/views/notification_button.cc b/ui/message_center/views/notification_button.cc
index 4fd8ac6a..c42b6d7 100644
--- a/ui/message_center/views/notification_button.cc
+++ b/ui/message_center/views/notification_button.cc
@@ -21,8 +21,7 @@
   SetFocusForPlatform();
   // Create a background so that it does not change when the MessageView
   // background changes to show touch feedback
-  set_background(views::Background::CreateSolidBackground(
-      kNotificationBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kNotificationBackgroundColor));
   set_notify_enter_exit_on_child(true);
   SetLayoutManager(
       new views::BoxLayout(views::BoxLayout::kHorizontal,
@@ -95,11 +94,10 @@
 
 void NotificationButton::StateChanged(ButtonState old_state) {
   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
-    set_background(views::Background::CreateSolidBackground(
+    SetBackground(views::CreateSolidBackground(
         message_center::kHoveredButtonBackgroundColor));
   } else {
-    set_background(views::Background::CreateSolidBackground(
-        kNotificationBackgroundColor));
+    SetBackground(views::CreateSolidBackground(kNotificationBackgroundColor));
   }
 }
 
diff --git a/ui/message_center/views/notification_view.cc b/ui/message_center/views/notification_view.cc
index 89fec2e..f65b597 100644
--- a/ui/message_center/views/notification_view.cc
+++ b/ui/message_center/views/notification_view.cc
@@ -623,8 +623,8 @@
 
     image_container_ = new views::View();
     image_container_->SetLayoutManager(new views::FillLayout());
-    image_container_->set_background(views::Background::CreateSolidBackground(
-        message_center::kImageBackgroundColor));
+    image_container_->SetBackground(
+        views::CreateSolidBackground(message_center::kImageBackgroundColor));
 
     image_view_ = new message_center::ProportionalImageView(ideal_size);
     image_container_->AddChildView(image_view_);
diff --git a/ui/message_center/views/notifier_settings_view.cc b/ui/message_center/views/notifier_settings_view.cc
index 3e541c7..f2fc81f 100644
--- a/ui/message_center/views/notifier_settings_view.cc
+++ b/ui/message_center/views/notifier_settings_view.cc
@@ -424,8 +424,7 @@
     provider_->AddObserver(this);
 
   SetFocusBehavior(FocusBehavior::ALWAYS);
-  set_background(
-      views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kMessageCenterBackgroundColor));
   SetPaintToLayer();
 
   title_label_ = new views::Label(
diff --git a/ui/message_center/views/padded_button.cc b/ui/message_center/views/padded_button.cc
index 08e3e6f..b81a3f4 100644
--- a/ui/message_center/views/padded_button.cc
+++ b/ui/message_center/views/padded_button.cc
@@ -22,8 +22,7 @@
   SetFocusPainter(views::Painter::CreateSolidFocusPainter(
       kFocusBorderColor,
       gfx::Insets(1, 2, 2, 2)));
-  set_background(
-      views::Background::CreateSolidBackground(kControlButtonBackgroundColor));
+  SetBackground(views::CreateSolidBackground(kControlButtonBackgroundColor));
   SetBorder(views::CreateEmptyBorder(gfx::Insets(kControlButtonBorderSize)));
   set_animate_on_state_change(false);
 
diff --git a/ui/message_center/views/toast_contents_view.cc b/ui/message_center/views/toast_contents_view.cc
index 785ffa6..7455eb1d 100644
--- a/ui/message_center/views/toast_contents_view.cc
+++ b/ui/message_center/views/toast_contents_view.cc
@@ -65,7 +65,7 @@
   // Sets the transparent background. Then, when the message view is slid out,
   // the whole toast seems to slide although the actual bound of the widget
   // remains. This is hacky but easier to keep the consistency.
-  set_background(views::Background::CreateSolidBackground(0, 0, 0, 0));
+  SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
 
   fade_animation_.reset(new gfx::SlideAnimation(this));
   fade_animation_->SetSlideDuration(kFadeInOutDuration);
diff --git a/ui/views/background.cc b/ui/views/background.cc
index 901aa03b..6cb17f106 100644
--- a/ui/views/background.cc
+++ b/ui/views/background.cc
@@ -85,40 +85,32 @@
   DISALLOW_COPY_AND_ASSIGN(BackgroundPainter);
 };
 
-Background::Background()
-    : color_(SK_ColorWHITE)
-{
-}
+Background::Background() : color_(SK_ColorWHITE) {}
 
-Background::~Background() {
-}
+Background::~Background() {}
 
 void Background::SetNativeControlColor(SkColor color) {
   color_ = color;
 }
 
-// static
-Background* Background::CreateSolidBackground(SkColor color) {
-  return new SolidBackground(color);
+std::unique_ptr<Background> CreateSolidBackground(SkColor color) {
+  return base::MakeUnique<SolidBackground>(color);
 }
 
-// static
-Background* Background::CreateThemedSolidBackground(
+std::unique_ptr<Background> CreateThemedSolidBackground(
     View* view,
     ui::NativeTheme::ColorId color_id) {
-  return new ThemedSolidBackground(view, color_id);
+  return base::MakeUnique<ThemedSolidBackground>(view, color_id);
 }
 
-// static
-Background* Background::CreateStandardPanelBackground() {
+std::unique_ptr<Background> CreateStandardPanelBackground() {
   // TODO(beng): Should be in NativeTheme.
   return CreateSolidBackground(SK_ColorWHITE);
 }
 
-// static
-Background* Background::CreateBackgroundPainter(
+std::unique_ptr<Background> CreateBackgroundFromPainter(
     std::unique_ptr<Painter> painter) {
-  return new BackgroundPainter(std::move(painter));
+  return base::MakeUnique<BackgroundPainter>(std::move(painter));
 }
 
 }  // namespace views
diff --git a/ui/views/background.h b/ui/views/background.h
index 81bb675..63d7847 100644
--- a/ui/views/background.h
+++ b/ui/views/background.h
@@ -45,31 +45,6 @@
   Background();
   virtual ~Background();
 
-  // Creates a background that fills the canvas in the specified color.
-  static Background* CreateSolidBackground(SkColor color);
-
-  // Creates a background that fills the canvas in the color specified by the
-  // view's NativeTheme and the given color identifier.
-  static Background* CreateThemedSolidBackground(
-      View* view,
-      ui::NativeTheme::ColorId color_id);
-
-  // Creates a background that fills the canvas in the specified color.
-  static Background* CreateSolidBackground(int r, int g, int b) {
-    return CreateSolidBackground(SkColorSetRGB(r, g, b));
-  }
-
-  // Creates a background that fills the canvas in the specified color.
-  static Background* CreateSolidBackground(int r, int g, int b, int a) {
-    return CreateSolidBackground(SkColorSetARGB(a, r, g, b));
-  }
-
-  // Creates Chrome's standard panel background
-  static Background* CreateStandardPanelBackground();
-
-  // Creates a Background from the specified Painter.
-  static Background* CreateBackgroundPainter(std::unique_ptr<Painter> painter);
-
   // Render the background for the provided view
   virtual void Paint(gfx::Canvas* canvas, View* view) const = 0;
 
@@ -89,6 +64,22 @@
   DISALLOW_COPY_AND_ASSIGN(Background);
 };
 
+// Creates a background that fills the canvas in the specified color.
+VIEWS_EXPORT std::unique_ptr<Background> CreateSolidBackground(SkColor color);
+
+// Creates a background that fills the canvas in the color specified by the
+// view's NativeTheme and the given color identifier.
+VIEWS_EXPORT std::unique_ptr<Background> CreateThemedSolidBackground(
+    View* view,
+    ui::NativeTheme::ColorId color_id);
+
+// Creates Chrome's standard panel background
+VIEWS_EXPORT std::unique_ptr<Background> CreateStandardPanelBackground();
+
+// Creates a Background from the specified Painter.
+VIEWS_EXPORT std::unique_ptr<Background> CreateBackgroundFromPainter(
+    std::unique_ptr<Painter> painter);
+
 }  // namespace views
 
 #endif  // UI_VIEWS_BACKGROUND_H_
diff --git a/ui/views/bubble/bubble_dialog_delegate.cc b/ui/views/bubble/bubble_dialog_delegate.cc
index 6845612..5688543b 100644
--- a/ui/views/bubble/bubble_dialog_delegate.cc
+++ b/ui/views/bubble/bubble_dialog_delegate.cc
@@ -303,9 +303,9 @@
 
   // When there's an opaque layer, the bubble border background won't show
   // through, so explicitly paint a background color.
-  set_background(layer() && layer()->fills_bounds_opaquely()
-                     ? Background::CreateSolidBackground(color())
-                     : nullptr);
+  SetBackground(layer() && layer()->fills_bounds_opaquely()
+                    ? CreateSolidBackground(color())
+                    : nullptr);
 }
 
 void BubbleDialogDelegateView::HandleVisibilityChanged(Widget* widget,
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
index e3edaf9..17c86ee 100644
--- a/ui/views/bubble/bubble_frame_view.cc
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -400,7 +400,7 @@
   SetBorder(std::move(border));
 
   // Update the background, which relies on the border.
-  set_background(new views::BubbleBackground(bubble_border_));
+  SetBackground(base::MakeUnique<views::BubbleBackground>(bubble_border_));
 }
 
 void BubbleFrameView::SetFootnoteView(View* view) {
@@ -412,8 +412,8 @@
   footnote_container_->SetLayoutManager(
       new BoxLayout(BoxLayout::kVertical, content_margins_.left(),
                     content_margins_.top(), 0));
-  footnote_container_->set_background(
-      Background::CreateSolidBackground(kFootnoteBackgroundColor));
+  footnote_container_->SetBackground(
+      CreateSolidBackground(kFootnoteBackgroundColor));
   footnote_container_->SetBorder(
       CreateSolidSidedBorder(1, 0, 0, 0, kFootnoteBorderColor));
   footnote_container_->AddChildView(view);
diff --git a/ui/views/button_drag_utils.cc b/ui/views/button_drag_utils.cc
index cb6b611..064a133 100644
--- a/ui/views/button_drag_utils.cc
+++ b/ui/views/button_drag_utils.cc
@@ -59,7 +59,7 @@
     button.SetTextShadows(gfx::ShadowValues(
         10, gfx::ShadowValue(gfx::Vector2d(0, 0), 2.0f, bg_color)));
   } else {
-    button.set_background(views::Background::CreateSolidBackground(bg_color));
+    button.SetBackground(views::CreateSolidBackground(bg_color));
     button.SetBorder(button.CreateDefaultBorder());
   }
   button.SetMaxSize(gfx::Size(kLinkDragImageMaxWidth, 0));
diff --git a/ui/views/color_chooser/color_chooser_view.cc b/ui/views/color_chooser/color_chooser_view.cc
index a6e1aba7..4daa497 100644
--- a/ui/views/color_chooser/color_chooser_view.cc
+++ b/ui/views/color_chooser/color_chooser_view.cc
@@ -347,7 +347,7 @@
 
 void ColorChooserView::SelectedColorPatchView::SetColor(SkColor color) {
   if (!background())
-    set_background(Background::CreateSolidBackground(color));
+    SetBackground(CreateSolidBackground(color));
   else
     background()->SetNativeControlColor(color);
   SchedulePaint();
@@ -362,7 +362,7 @@
     : listener_(listener) {
   DCHECK(listener_);
 
-  set_background(Background::CreateSolidBackground(SK_ColorLTGRAY));
+  SetBackground(CreateSolidBackground(SK_ColorLTGRAY));
   SetLayoutManager(new BoxLayout(BoxLayout::kVertical, kMarginWidth,
                                  kMarginWidth, kMarginWidth));
 
diff --git a/ui/views/controls/button/image_button.cc b/ui/views/controls/button/image_button.cc
index 364489c..49e6d5fc 100644
--- a/ui/views/controls/button/image_button.cc
+++ b/ui/views/controls/button/image_button.cc
@@ -64,9 +64,9 @@
     SchedulePaint();
 }
 
-void ImageButton::SetBackground(SkColor color,
-                                const gfx::ImageSkia* image,
-                                const gfx::ImageSkia* mask) {
+void ImageButton::SetBackgroundImage(SkColor color,
+                                     const gfx::ImageSkia* image,
+                                     const gfx::ImageSkia* mask) {
   if (image == NULL || mask == NULL) {
     background_image_ = gfx::ImageSkia();
     return;
diff --git a/ui/views/controls/button/image_button.h b/ui/views/controls/button/image_button.h
index e17a5ce..419a517 100644
--- a/ui/views/controls/button/image_button.h
+++ b/ui/views/controls/button/image_button.h
@@ -50,9 +50,9 @@
   virtual void SetImage(ButtonState state, const gfx::ImageSkia& image);
 
   // Set the background details.
-  void SetBackground(SkColor color,
-                     const gfx::ImageSkia* image,
-                     const gfx::ImageSkia* mask);
+  void SetBackgroundImage(SkColor color,
+                          const gfx::ImageSkia* image,
+                          const gfx::ImageSkia* mask);
 
   // Sets how the image is laid out within the button's bounds.
   void SetImageAlignment(HorizontalAlignment h_align,
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 6fb6e39e..50e17d9 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -440,13 +440,13 @@
     colors[STATE_NORMAL] = colors[STATE_HOVERED] = colors[STATE_PRESSED] =
         SK_ColorWHITE;
     label_->SetBackgroundColor(SK_ColorBLACK);
-    label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
+    label_->SetBackground(CreateSolidBackground(SK_ColorBLACK));
     label_->SetAutoColorReadabilityEnabled(true);
     label_->SetShadows(gfx::ShadowValues());
   } else {
     if (style() == STYLE_BUTTON)
       PlatformStyle::ApplyLabelButtonTextStyle(label_, &colors);
-    label_->set_background(nullptr);
+    label_->SetBackground(nullptr);
     label_->SetAutoColorReadabilityEnabled(false);
   }
 
diff --git a/ui/views/controls/button/md_text_button.cc b/ui/views/controls/button/md_text_button.cc
index 32773d2..55159d8 100644
--- a/ui/views/controls/button/md_text_button.cc
+++ b/ui/views/controls/button/md_text_button.cc
@@ -283,9 +283,9 @@
   }
 
   DCHECK_EQ(SK_AlphaOPAQUE, static_cast<int>(SkColorGetA(bg_color)));
-  set_background(Background::CreateBackgroundPainter(
-      Painter::CreateRoundRectWith1PxBorderPainter(bg_color, stroke_color,
-                                                   kInkDropSmallCornerRadius)));
+  SetBackground(
+      CreateBackgroundFromPainter(Painter::CreateRoundRectWith1PxBorderPainter(
+          bg_color, stroke_color, kInkDropSmallCornerRadius)));
   SchedulePaint();
 }
 
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index b85f07dc..c948d755 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -554,8 +554,8 @@
   if (!UseMd())
     return;
 
-  set_background(
-      Background::CreateBackgroundPainter(Painter::CreateSolidRoundRectPainter(
+  SetBackground(
+      CreateBackgroundFromPainter(Painter::CreateSolidRoundRectPainter(
           theme->GetSystemColor(
               ui::NativeTheme::kColorId_TextfieldDefaultBackground),
           FocusableBorder::kCornerRadiusDp)));
diff --git a/ui/views/controls/menu/menu_scroll_view_container.cc b/ui/views/controls/menu/menu_scroll_view_container.cc
index a0a74eb..08ba198 100644
--- a/ui/views/controls/menu/menu_scroll_view_container.cc
+++ b/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -304,7 +304,7 @@
                                     BubbleBorder::SMALL_SHADOW,
                                     SK_ColorWHITE);
   SetBorder(std::unique_ptr<Border>(bubble_border_));
-  set_background(new BubbleBackground(bubble_border_));
+  SetBackground(base::MakeUnique<BubbleBackground>(bubble_border_));
 }
 
 BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index aa480a2..6d97969 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -209,8 +209,7 @@
   DCHECK(!a_view->layer());
   if (ScrollsWithLayers()) {
     if (!a_view->background() && background_color_ != SK_ColorTRANSPARENT) {
-      a_view->set_background(
-          Background::CreateSolidBackground(background_color_));
+      a_view->SetBackground(CreateSolidBackground(background_color_));
     }
     a_view->SetPaintToLayer();
     a_view->layer()->SetScrollable(
@@ -226,12 +225,10 @@
 
 void ScrollView::SetBackgroundColor(SkColor color) {
   background_color_ = color;
-  contents_viewport_->set_background(
-      Background::CreateSolidBackground(background_color_));
+  contents_viewport_->SetBackground(CreateSolidBackground(background_color_));
   if (contents_ && ScrollsWithLayers() &&
       background_color_ != SK_ColorTRANSPARENT) {
-    contents_->set_background(
-        Background::CreateSolidBackground(background_color_));
+    contents_->SetBackground(CreateSolidBackground(background_color_));
   }
 }
 
@@ -752,8 +749,7 @@
 
   if (scroll_with_layers_enabled_) {
     background_color_ = SK_ColorWHITE;
-    contents_viewport_->set_background(
-        Background::CreateSolidBackground(background_color_));
+    contents_viewport_->SetBackground(CreateSolidBackground(background_color_));
   } else {
     // We may have transparent children who want to blend into the default
     // background.
diff --git a/ui/views/controls/table/table_header.cc b/ui/views/controls/table/table_header.cc
index a4e1117..dd72a41 100644
--- a/ui/views/controls/table/table_header.cc
+++ b/ui/views/controls/table/table_header.cc
@@ -227,7 +227,7 @@
 }
 
 void TableHeader::OnNativeThemeChanged(const ui::NativeTheme* theme) {
-  set_background(Background::CreateSolidBackground(
+  SetBackground(CreateSolidBackground(
       theme->GetSystemColor(ui::NativeTheme::kColorId_TableHeaderBackground)));
 }
 
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index ea4f91a2..70892160 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -1910,11 +1910,11 @@
 void Textfield::UpdateBackgroundColor() {
   const SkColor color = GetBackgroundColor();
   if (ui::MaterialDesignController::IsSecondaryUiMaterial()) {
-    set_background(Background::CreateBackgroundPainter(
-        Painter::CreateSolidRoundRectPainter(
+    SetBackground(
+        CreateBackgroundFromPainter(Painter::CreateSolidRoundRectPainter(
             color, FocusableBorder::kCornerRadiusDp)));
   } else {
-    set_background(Background::CreateSolidBackground(color));
+    SetBackground(CreateSolidBackground(color));
   }
   // Disable subpixel rendering when the background color is transparent
   // because it draws incorrect colors around the glyphs in that case.
diff --git a/ui/views/corewm/tooltip_aura.cc b/ui/views/corewm/tooltip_aura.cc
index a9433d4..949c322 100644
--- a/ui/views/corewm/tooltip_aura.cc
+++ b/ui/views/corewm/tooltip_aura.cc
@@ -123,13 +123,12 @@
   void SetBackgroundColor(SkColor background_color) {
     // Corner radius of tooltip background.
     const float kTooltipCornerRadius = 2.f;
-    views::Background* background =
-        CanUseTranslucentTooltipWidget()
-            ? views::Background::CreateBackgroundPainter(
-                  views::Painter::CreateSolidRoundRectPainter(
-                      background_color, kTooltipCornerRadius))
-            : views::Background::CreateSolidBackground(background_color);
-    set_background(background);
+    SetBackground(CanUseTranslucentTooltipWidget()
+                      ? views::CreateBackgroundFromPainter(
+                            views::Painter::CreateSolidRoundRectPainter(
+                                background_color, kTooltipCornerRadius))
+                      : views::CreateSolidBackground(background_color));
+
     // Force the text color to be readable when |background_color| is not
     // opaque.
     render_text_->set_subpixel_rendering_suppressed(
diff --git a/ui/views/examples/button_example.cc b/ui/views/examples/button_example.cc
index 353718d..bb4790a 100644
--- a/ui/views/examples/button_example.cc
+++ b/ui/views/examples/button_example.cc
@@ -37,7 +37,7 @@
 }
 
 void ButtonExample::CreateExampleView(View* container) {
-  container->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+  container->SetBackground(CreateSolidBackground(SK_ColorWHITE));
   BoxLayout* layout = new BoxLayout(BoxLayout::kVertical, 10, 10, 10);
   layout->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
   container->SetLayoutManager(layout);
diff --git a/ui/views/examples/dialog_example.cc b/ui/views/examples/dialog_example.cc
index 895db3c..9ec6b09 100644
--- a/ui/views/examples/dialog_example.cc
+++ b/ui/views/examples/dialog_example.cc
@@ -43,7 +43,7 @@
     Label* body = new Label(parent_->body_->text());
     body->SetMultiLine(true);
     body->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    body->set_background(Background::CreateSolidBackground(0, 255, 255));
+    body->SetBackground(CreateSolidBackground(SkColorSetRGB(0, 255, 255)));
     this->AddChildView(body);
 
     // Give the example code a way to change the body text.
diff --git a/ui/views/examples/examples_window.cc b/ui/views/examples/examples_window.cc
index 5f869477..d2654b4 100644
--- a/ui/views/examples/examples_window.cc
+++ b/ui/views/examples/examples_window.cc
@@ -140,7 +140,7 @@
     combobox_model_.SetExamples(std::move(examples));
     combobox_->ModelChanged();
 
-    set_background(Background::CreateStandardPanelBackground());
+    SetBackground(CreateStandardPanelBackground());
     GridLayout* layout = new GridLayout(this);
     SetLayoutManager(layout);
     ColumnSet* column_set = layout->AddColumnSet(0);
diff --git a/ui/views/examples/label_example.cc b/ui/views/examples/label_example.cc
index 5db8bbf..9507b13 100644
--- a/ui/views/examples/label_example.cc
+++ b/ui/views/examples/label_example.cc
@@ -160,8 +160,7 @@
 void LabelExample::AddCustomLabel(View* container) {
   View* control_container = new View();
   control_container->SetBorder(CreateSolidBorder(2, SK_ColorGRAY));
-  control_container->set_background(
-      Background::CreateSolidBackground(SK_ColorLTGRAY));
+  control_container->SetBackground(CreateSolidBackground(SK_ColorLTGRAY));
   GridLayout* layout = GridLayout::CreatePanel(control_container);
   control_container->SetLayoutManager(layout);
 
diff --git a/ui/views/examples/widget_example.cc b/ui/views/examples/widget_example.cc
index 93a152c..835272f 100644
--- a/ui/views/examples/widget_example.cc
+++ b/ui/views/examples/widget_example.cc
@@ -43,7 +43,7 @@
 };
 
 DialogExample::DialogExample() {
-  set_background(Background::CreateSolidBackground(SK_ColorGRAY));
+  SetBackground(CreateSolidBackground(SK_ColorGRAY));
   SetLayoutManager(new BoxLayout(BoxLayout::kVertical, 10, 10, 10));
   AddChildView(new Label(ASCIIToUTF16("Dialog contents label!")));
 }
@@ -105,7 +105,7 @@
   if (!widget->GetContentsView()) {
     View* contents = new View();
     contents->SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0, 0));
-    contents->set_background(Background::CreateSolidBackground(SK_ColorGRAY));
+    contents->SetBackground(CreateSolidBackground(SK_ColorGRAY));
     BuildButton(contents, "Close", CLOSE_WIDGET);
     widget->SetContentsView(contents);
   }
diff --git a/ui/views/focus/focus_traversal_unittest.cc b/ui/views/focus/focus_traversal_unittest.cc
index 352b4e4..6ca2228 100644
--- a/ui/views/focus/focus_traversal_unittest.cc
+++ b/ui/views/focus/focus_traversal_unittest.cc
@@ -300,8 +300,7 @@
   //     NativeButton      * THUMBNAIL_STAR_ID
   //     NativeButton      * THUMBNAIL_SUPER_STAR_ID
 
-  GetContentsView()->set_background(
-      Background::CreateSolidBackground(SK_ColorWHITE));
+  GetContentsView()->SetBackground(CreateSolidBackground(SK_ColorWHITE));
 
   Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
   GetContentsView()->AddChildView(cb);
@@ -311,8 +310,8 @@
 
   left_container_ = new PaneView();
   left_container_->SetBorder(CreateSolidBorder(1, SK_ColorBLACK));
-  left_container_->set_background(
-      Background::CreateSolidBackground(240, 240, 240));
+  left_container_->SetBackground(
+      CreateSolidBackground(SkColorSetRGB(240, 240, 240)));
   left_container_->set_id(LEFT_CONTAINER_ID);
   GetContentsView()->AddChildView(left_container_);
   left_container_->SetBounds(10, 35, 250, 200);
@@ -395,8 +394,8 @@
 
   right_container_ = new PaneView();
   right_container_->SetBorder(CreateSolidBorder(1, SK_ColorBLACK));
-  right_container_->set_background(
-      Background::CreateSolidBackground(240, 240, 240));
+  right_container_->SetBackground(
+      CreateSolidBackground(SkColorSetRGB(240, 240, 240)));
   right_container_->set_id(RIGHT_CONTAINER_ID);
   GetContentsView()->AddChildView(right_container_);
   right_container_->SetBounds(270, 35, 300, 200);
@@ -426,8 +425,8 @@
 
   View* inner_container = new View();
   inner_container->SetBorder(CreateSolidBorder(1, SK_ColorBLACK));
-  inner_container->set_background(
-      Background::CreateSolidBackground(230, 230, 230));
+  inner_container->SetBackground(
+      CreateSolidBackground(SkColorSetRGB(230, 230, 230)));
   inner_container->set_id(INNER_CONTAINER_ID);
   right_container_->AddChildView(inner_container);
   inner_container->SetBounds(100, 10, 150, 180);
@@ -439,8 +438,8 @@
 
   View* scroll_content = new View();
   scroll_content->SetBounds(0, 0, 200, 200);
-  scroll_content->set_background(
-      Background::CreateSolidBackground(200, 200, 200));
+  scroll_content->SetBackground(
+      CreateSolidBackground(SkColorSetRGB(200, 200, 200)));
   scroll_view->SetContents(scroll_content);
 
   static const char* const kTitles[] = {
@@ -495,7 +494,7 @@
 
   // Left bottom box with style checkboxes.
   contents = new View();
-  contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+  contents->SetBackground(CreateSolidBackground(SK_ColorWHITE));
   cb = new Checkbox(ASCIIToUTF16("Bold"));
   contents->AddChildView(cb);
   cb->SetBounds(10, 10, 50, 20);
@@ -530,7 +529,7 @@
 
   // Right bottom box with search.
   contents = new View();
-  contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+  contents->SetBackground(CreateSolidBackground(SK_ColorWHITE));
   text_field = new Textfield();
   contents->AddChildView(text_field);
   text_field->SetBounds(10, 10, 100, 20);
@@ -557,7 +556,7 @@
 
   contents = new View();
   contents->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
+  contents->SetBackground(CreateSolidBackground(SK_ColorBLUE));
   contents->set_id(THUMBNAIL_CONTAINER_ID);
   button = MdTextButton::Create(NULL, ASCIIToUTF16("Star"));
   contents->AddChildView(button);
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 9257c06..8fbd867 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -911,8 +911,8 @@
   PaintChildren(context);
 }
 
-void View::set_background(Background* b) {
-  background_.reset(b);
+void View::SetBackground(std::unique_ptr<Background> b) {
+  background_ = std::move(b);
 }
 
 void View::SetBorder(std::unique_ptr<Border> b) {
diff --git a/ui/views/view.h b/ui/views/view.h
index 9c78e196..0c2c9c2 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -540,12 +540,12 @@
   // the hierarchy beneath it.
   void Paint(const ui::PaintContext& parent_context);
 
-  // The background object is owned by this object and may be NULL.
-  void set_background(Background* b);
+  // The background object may be null.
+  void SetBackground(std::unique_ptr<Background> b);
   const Background* background() const { return background_.get(); }
   Background* background() { return background_.get(); }
 
-  // The border object is owned by this object and may be NULL.
+  // The border object may be null.
   virtual void SetBorder(std::unique_ptr<Border> b);
   const Border* border() const { return border_.get(); }
   Border* border() { return border_.get(); }
diff --git a/ui/views/window/dialog_client_view.cc b/ui/views/window/dialog_client_view.cc
index 1d3510f..e215d7b0 100644
--- a/ui/views/window/dialog_client_view.cc
+++ b/ui/views/window/dialog_client_view.cc
@@ -220,8 +220,8 @@
   const DialogDelegate* dialog = GetDialogDelegate();
 
   if (dialog && !dialog->ShouldUseCustomFrame()) {
-    set_background(views::Background::CreateSolidBackground(GetNativeTheme()->
-        GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
+    SetBackground(views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
+        ui::NativeTheme::kColorId_DialogBackground)));
   }
 }